#include "mozen_tunnel.h"
#include <string.h>
// ·Ö°ü

/*
* Initialize tunnel
* tunnel: tunnel context
* prot: protocol context
* mtu: maximum transmission unit
* user_context: user context
* returns: none
*/
void mozen_tunnel_init(mozen_tunnel_t *tunnel, mozen_protocol_t *prot, uint16_t mtu, void *user_context) 
{
    if (!tunnel) return;
    memset(tunnel, 0, sizeof(mozen_tunnel_t));
    tunnel->prot = prot;
    tunnel->mtu = mtu;
    tunnel->user_context = user_context;
}

/*
 * Send a response to the tunnel command
 * tunnel: tunnel context
 * target_cmd: command id
 * is_ok: true if the command was successful, false otherwise
 * returns: none
*/
void mozen_tunnel_set_callbacks(mozen_tunnel_t *tunnel, 
                                mozen_tunnel_on_stream_start_t on_start,
                                mozen_tunnel_on_stream_data_t on_data,
                                mozen_tunnel_on_stream_finish_t on_finish) 
{
    if (!tunnel) return;
    tunnel->on_start = on_start;
    tunnel->on_data = on_data;
    tunnel->on_finish = on_finish;
}

/*
* Send a response to the tunnel command
* tunnel: tunnel context
* target_cmd: command id
* is_ok: true if the command was successful, false otherwise
* returns: none
*/
static void mozen_tunnel_send_response(mozen_tunnel_t *tunnel, uint8_t target_cmd, bool is_ok) 
{
    if (!tunnel || !tunnel->prot) return;
    
    uint8_t res_buf[sizeof(mozen_tunnel_header_t) + 2];
    mozen_tunnel_header_t *res_header = (mozen_tunnel_header_t *)res_buf;
    
    res_header->tunnel_type = TUNNEL_TYPE_DATA_TRANSFER;
    res_header->target_cmd = target_cmd;
    res_buf[sizeof(mozen_tunnel_header_t)] = is_ok ? 'o' : 'N';
    res_buf[sizeof(mozen_tunnel_header_t) + 1] = is_ok ? 'k' : 'G';
    // tunnel messages go through IRQ buffer context to avoid disrupting main buffer?
    // The previous implementation used the default send context.
    mozen_protocol_send_frame(tunnel->prot, MOZEN_TUNNEL_COMMAND_ID, res_buf, sizeof(res_buf), true);
}

/*
* Handle a received tunnel frame
* tunnel: tunnel context
* data: received data
* length: received data length
* returns: none
*/
static void mozen_tunnel_abort(mozen_tunnel_t *tunnel) 
{
    if (tunnel->is_active) 
    {
        if (tunnel->on_finish) 
        {
            tunnel->on_finish(tunnel->user_context, false);
        }
        tunnel->is_active = false;
    }
}

/*
* Handle a received tunnel frame
* tunnel: tunnel context
* data: received data
* length: received data length
* returns: none
*/
void mozen_tunnel_handle_command(mozen_tunnel_t *tunnel, const uint8_t *data, uint16_t length) 
{
    if (!tunnel || length < sizeof(mozen_tunnel_header_t)) return;

    const mozen_tunnel_header_t *tunnel_header = (const mozen_tunnel_header_t *)data;
    const uint8_t *payload = data + sizeof(mozen_tunnel_header_t);
    uint16_t payload_len = length - sizeof(mozen_tunnel_header_t);

    switch (tunnel_header->tunnel_type) 
    {
        case TUNNEL_TYPE_HANDSHAKE:  // handshake request
        {
            if (payload_len < sizeof(uint32_t) + sizeof(uint16_t))  return; 
            
            mozen_tunnel_abort(tunnel); // Abort any ongoing stream

            tunnel->is_active = true;
            tunnel->target_cmd = tunnel_header->target_cmd;
            
            memcpy(&tunnel->total_data_len, payload, sizeof(uint32_t));
            memcpy(&tunnel->total_packets, payload + sizeof(uint32_t), sizeof(uint16_t));
            
            tunnel->next_packet_index = 1;
            tunnel->received_data_len = 0;

            // Prepare and send handshake response
            uint8_t res_buf[sizeof(mozen_tunnel_handshake_res_t)];
            mozen_tunnel_handshake_res_t *res = (mozen_tunnel_handshake_res_t *)res_buf;
            res->header.tunnel_type = TUNNEL_TYPE_RESPONSE;
            res->header.target_cmd = tunnel_header->target_cmd;
            res->status = 0x6B6F; // 'ok' (little-endian: 'o' is 0x6F, 'k' is 0x6B)
            res->mtu = tunnel->mtu; 
            
            if (tunnel->prot) 
            {
                mozen_protocol_send_frame(tunnel->prot, MOZEN_TUNNEL_COMMAND_ID, (uint8_t *)res, sizeof(mozen_tunnel_handshake_res_t), true);
            }
            break;
        }

        case TUNNEL_TYPE_DATA_TRANSFER: // data transfer
        {
            if (!tunnel->is_active || tunnel_header->target_cmd != tunnel->target_cmd) return;
            if (payload_len < sizeof(uint16_t)) return;

            uint16_t packet_index;
            memcpy(&packet_index, payload, sizeof(uint16_t));

            if (packet_index != tunnel->next_packet_index) 
            {
                mozen_tunnel_send_response(tunnel, tunnel->target_cmd, false);
                mozen_tunnel_abort(tunnel);
                return;
            }

            const uint8_t *chunk_data = payload + sizeof(uint16_t);
            uint16_t chunk_len = payload_len - sizeof(uint16_t);

            bool accepted = true;

            if (packet_index == 1) 
            {
                if (tunnel->on_start) 
                {
                    accepted = tunnel->on_start(tunnel->user_context, tunnel->target_cmd, chunk_data, chunk_len);
                }
            } 
            else 
            {
                if (tunnel->on_data) 
                {
                    accepted = tunnel->on_data(tunnel->user_context, chunk_data, chunk_len);
                }
            }

            if (!accepted) 
            {
                mozen_tunnel_send_response(tunnel, tunnel->target_cmd, false);
                mozen_tunnel_abort(tunnel);
                return;
            }

            tunnel->received_data_len += chunk_len;
            tunnel->next_packet_index++;

            mozen_tunnel_send_response(tunnel, tunnel->target_cmd, true);

            if (tunnel->next_packet_index > tunnel->total_packets) 
            {
                if (tunnel->received_data_len == tunnel->total_data_len) 
                {
                    if (tunnel->on_finish) 
                    {
                        tunnel->on_finish(tunnel->user_context, true);
                    }
                } 
                else 
                {
                    if (tunnel->on_finish) 
                    {
                        tunnel->on_finish(tunnel->user_context, false);
                    }
                }
                tunnel->is_active = false;
            }
            break;
        }
    }
}
