#include "app_mozen_handler.h"
#include "mozen_tunnel.h"
#include "mx_log.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "app_calibration.h"
#include "main.h"
#include "cJSON.h"

// Define logs if not using MX_LOG directly
#ifndef MX_LOGD
#define MX_LOGD MOZEN_LOGD
#define MX_LOGI MOZEN_LOGI
#define MX_LOGW MOZEN_LOGW
#define MX_LOGE MOZEN_LOGE
#endif

#define MX_PTO_TAG "MOZEN_APP"

volatile uint16_t g_sensor_frames_to_send = 0;
volatile uint8_t g_sensor_data_type = SENSOR_BIT_16;

static mozen_protocol_t *s_prot_instance = NULL;
static mozen_tunnel_t s_tunnel_instance;

// --- Tunnel Stream State ---
static struct 
{
    bool     is_streaming_write;
    uint8_t* write_ptr;
    uint8_t  target_cmd;
    uint8_t  target_sub_class; 
} s_stream_state;

// --- Response Utilities ---
#pragma pack(1)
typedef struct 
{
    uint8_t target_id;
    uint8_t sub_class;
    uint8_t operation;
} mozen_cmd_header_t;
#pragma pack()

typedef void (*cmd_sub_handler_t)(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len);

typedef struct 
{
    uint8_t target_id;
    cmd_sub_handler_t handler;
} cmd_handler_entry_t;

// --- Forward Declarations ---
static void param_handle_report_mode(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len);
static void param_handle_device_info(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len);
static void param_handle_cali_display(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len);
static void prod_handle_sys_cmd(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len);
static void prod_handle_map_cali_cmd(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len); 

// --- param setters ---
static const cmd_handler_entry_t s_param_handlers[] = 
{
    {MOZEN_PARAM_TGT_REPORT_MODE, param_handle_report_mode}, // 0x01
    {MOZEN_PARAM_TGT_DEVICE_INFO, param_handle_device_info}, // 0x02
    {MOZEN_PARAM_TGT_CALI_DISPLAY, param_handle_cali_display}, // 0x03
};

// ---  Production Test Targets ---
static const cmd_handler_entry_t s_prod_handlers[] = 
{
    {MOZEN_PROD_TGT_SYS, prod_handle_sys_cmd},
    {MOZEN_PROD_TGT_MAP_CALI, prod_handle_map_cali_cmd},
};

// --- Init ---
mozen_protocol_t g_mozen_prot; // Global mozen protocol instance
#define RX_BUFFER_SIZE 1024  // RX buffer size
#define TX_BUFFER_SIZE 1024  // TX buffer size
static uint8_t s_rx_buf[RX_BUFFER_SIZE]; // RX buffer
static uint8_t s_tx_buf_main[TX_BUFFER_SIZE]; // TX buffer
static uint8_t s_tx_buf_irq[TX_BUFFER_SIZE]; // TX buffer

// --- Legacy Compatibility Functions ---
static uint8_t is_legacy_config_payload(const uint8_t *data, uint16_t len) 
{
    if ((data == NULL) || (len < 3U)) return 0U;
    if ((data[0] != 0x3DU) && (data[0] != 0x3FU)) return 0U;
    if ((data[1] != '{') || (data[len - 1U] != '}')) return 0U;
    
    return 1U;
}

static int handle_old_protocol_device_status(uint8_t *data, uint16_t length) 
{
    if ((data == NULL) || (length < 2U)) return -1;
    
    if (data[0] == 0x3D) // "=" 设置操作
    {
        cJSON *root = cJSON_Parse((char *)&data[1]);
        if (!root) 
        {
            MX_LOGE(MX_PTO_TAG, "old protocol json parse failed");
            return -1;
        }
        
        MX_LOGD(MX_PTO_TAG, "old protocol json parsed");

        // 1. 从应用层获取当前状态副本进行更新
        app_device_status_t new_status = *app_device_status_get();

        // 2. 解析公共开关字段
        cJSON *led_sw = cJSON_GetObjectItem(root, "led_sw");
        if (led_sw) new_status.led_sw = (uint8_t)led_sw->valueint;

        cJSON *tx_sw = cJSON_GetObjectItem(root, "tx_sw");
        if (tx_sw)
        {
            int tx_sw_val = tx_sw->valueint;
            if (new_status.sensor_tx_sw == 1 && tx_sw_val == 0)
            {
                new_status.tx_stopping = 1;
            }
            new_status.sensor_tx_sw = (uint8_t)tx_sw_val;
        }

		cJSON *pick_sw = cJSON_GetObjectItem(root, "pick_sw");
        if (pick_sw) new_status.output_pick = (uint8_t)pick_sw->valueint;
        // printf("pick_sw: %d\n", new_status.output_pick);    

        // 3. 解析数学标定参数 (math / math_full)
        cJSON *math_arr = cJSON_GetObjectItem(root, "math");
        cJSON *extra_flag = cJSON_GetObjectItem(root, "math_full");

        if (math_arr && cJSON_IsArray(math_arr))
        {
            int size = cJSON_GetArraySize(math_arr);
            app_math_cali_t temp_params;

            if (app_math_get_temp_params(&temp_params))
            {
                if (size >= 1) {
                    temp_params.max_trigger_res_value = (uint16_t)cJSON_GetArrayItem(math_arr, 0)->valueint;
                }
                if (size >= 2) {
                    temp_params.min_trigger_res_value = (uint16_t)cJSON_GetArrayItem(math_arr, 1)->valueint;
                }

                if (extra_flag && extra_flag->valueint && size >= 5) {
                    temp_params.max_display_value = (uint16_t)cJSON_GetArrayItem(math_arr, 4)->valueint;
                }

                app_math_set_temp_params(&temp_params);
            }
        }
        
        // 4. 解析抗蠕变参数
        cJSON *creep_strength = cJSON_GetObjectItem(root, "creep_strength");
        cJSON *creep_level = cJSON_GetObjectItem(root, "creep_level");
        
        if (creep_strength || creep_level) 
        {
            app_creep_params temp_creep;
            app_creep_read_params(&temp_creep);
            
            if (creep_strength) {
                temp_creep.creep_strength = (uint8_t)creep_strength->valueint;
            }
            if (creep_level) {
                temp_creep.creep_level = (uint8_t)creep_level->valueint;
            }
            
            app_creep_write_temp_params(&temp_creep);
        }

        // 5. Flash 持久化处理
        cJSON *flash_cmd = cJSON_GetObjectItem(root, "flash");
        if(flash_cmd && cJSON_IsString(flash_cmd)) 
        {
            if (strcmp(flash_cmd->valuestring, "creep_write") == 0) 
            {
                app_creep_params temp_creep;
                app_creep_read_params(&temp_creep);
                app_creep_save_params(&temp_creep);
            }
        }

        // 6. 将更新后的 Legacy 状态回写到应用层
        app_device_status_set(&new_status);
        
        cJSON_Delete(root);
    }
    return 0;
}

static bool app_mozen_raw_frame_handler(void *context, const uint8_t *raw_frame, uint16_t length) 
{
    mozen_frame_header_t header;
    if (length < sizeof(mozen_frame_header_t) + 2) return false;
    
    memcpy(&header, raw_frame, sizeof(mozen_frame_header_t));
    if (header.command_id == MOZEN_CMD_ID_PARAM_CONFIG) // 0x02
    {
        uint16_t full_frame_len = header.frame_length;
        uint16_t data_len = full_frame_len - sizeof(mozen_frame_header_t);
        const uint8_t *data_ptr = raw_frame + sizeof(mozen_frame_header_t);
        uint16_t received_crc;
        memcpy(&received_crc, raw_frame + full_frame_len, sizeof(uint16_t));
        
        uint16_t calc_crc = mozen_protocol_checksum(raw_frame, full_frame_len);
        if ((calc_crc == received_crc) || is_legacy_config_payload(data_ptr, data_len)) 
        {
            handle_old_protocol_device_status((uint8_t *)data_ptr, data_len);
            return true; // Handled
        }
    }
    return false; // Not handled, fallback to standard parsing
}

// --- Command Handlers ---
/*
* @brief  handle sensor data
* @param  context: context
* @param  cmd_id: command id
* @param  data: data
* @param  length: length
* @retval none
*/
static void handle_sensor_data(void *context, uint8_t cmd_id, const uint8_t *data, uint16_t length) 
{
    if (length < 3) 
    {
        MX_LOGE(MX_PTO_TAG, "sensor command length invalid: %u", (unsigned int)length);
        return;
    }
    uint8_t new_data_type = data[0];
    if (new_data_type != g_sensor_data_type)
    {
        g_sensor_data_type = new_data_type;
        app_math_cali_t temp_params;
        bool got_params = false;

        got_params = app_math_get_temp_params(&temp_params);

        if (got_params)
        {
           temp_params.max_display_value = 4095;
           temp_params.sensor_data_type = g_sensor_data_type;

           app_math_set_temp_params(&temp_params);
        }
    }
    g_sensor_frames_to_send = *(uint16_t*)(&data[1]);
}

// --- Response Utilities ---
/*
* @brief  send mozen command response
* @param  cmd_id: command id
* @param  req_header: request header
* @param  payload: payload
* @param  payload_len: payload length
* @retval none
*/
static void send_mozen_cmd_response(uint8_t cmd_id, const mozen_cmd_header_t* req_header, const uint8_t* payload, uint16_t payload_len) 
{
    static uint8_t response_buf[1024]; 
    mozen_cmd_header_t* res_header = (mozen_cmd_header_t*)response_buf;
    res_header->target_id = req_header->target_id;
    res_header->sub_class = req_header->sub_class;
    res_header->operation = req_header->operation;
    if (payload && payload_len > 0) 
    {
        memcpy(response_buf + sizeof(mozen_cmd_header_t), payload, payload_len);
    }
    mozen_protocol_send_frame(s_prot_instance, cmd_id, response_buf, sizeof(mozen_cmd_header_t) + payload_len, false);
}

/*
* @brief  send mozen command ok or ng
* @param  cmd_id: command id
* @param  req_header: request header
* @param  success: success or not
* @retval none
*/
static inline void send_mozen_cmd_ok_ng(uint8_t cmd_id, const mozen_cmd_header_t* req_header, bool success) 
{
    uint8_t payload[2] = { success ? 'o' : 'N', success ? 'k' : 'G' };
    send_mozen_cmd_response(cmd_id, req_header, payload, 2);
}

// --- Parameter Config Handlers (Cmd ID: 0x02) ---
/*
* @brief  handle report mode
* @param  header: header
* @param  payload: payload
* @param  payload_len: payload length
* @retval none
*/
static void param_handle_report_mode(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) 
{
    if (header->sub_class == 0x01 && header->operation == 0x01 && payload_len >= 1) 
    {
        if (payload[0] == 0x00) g_is_active_reporting = false;
        else if (payload[0] == 0x01) g_is_active_reporting = true;
        send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, true);
    }
}
/*
* @brief  handle device info
* @param  header: header
* @param  payload: payload
* @param  payload_len: payload length
* @retval none
*/
static void param_handle_device_info(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) 
{
    if (header->operation == 0x00) 
    {
        app_dev_info dev_info;
        char sensor_size_text[sizeof(dev_info.sensor_size) + 1U] = {0};
        char protocol_ver_text[sizeof(dev_info.protocol_ver) + 1U] = {0};

        if (app_device_get_info(&dev_info)) 
        {
            memcpy(sensor_size_text, dev_info.sensor_size, sizeof(dev_info.sensor_size));
            memcpy(protocol_ver_text, dev_info.protocol_ver, sizeof(dev_info.protocol_ver));

            uint8_t resp_payload[256];
            resp_payload[0] = 'o';
            resp_payload[1] = 'k';
            int json_len = snprintf((char *)(resp_payload + 2),
                                    sizeof(resp_payload) - 2,
                                    "{\"PN\":\"%s\",\"SN\":\"%s\",\"SV\":\"%s\",\"HVI\":\"%s\",\"XY\":\"%s\",\"Protocol\":\"%s\"}",
                                    dev_info.pn, dev_info.sn, dev_info.sw_ver, dev_info.hw_ver, sensor_size_text, protocol_ver_text);
            if (json_len > 0) {
                send_mozen_cmd_response(MOZEN_CMD_ID_PARAM_CONFIG, header, resp_payload, 2 + (uint16_t)json_len);
            } else {
                send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, false);
            }
        } 
        else 
        {
            send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, false);
        }
    }
}
/*
* @brief  handle calibration display
* @param  header: header
* @param  payload: payload
* @param  payload_len: payload length
* @retval none
*/
static void param_handle_cali_display(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) 
{
    if (header->operation == 0x00) 
    {
        uint8_t success = 0;
        uint16_t data_len = 0;
        uint8_t resp_payload[256];
        
        if (header->sub_class == 0x01) 
        {
            success = app_math_read_temp_params(resp_payload + 2, &data_len);
        } 
        else if (header->sub_class == 0x02) 
        {
            success = app_math_read_solidified_params(resp_payload + 2, &data_len);
        }
        
        if (success) 
        {
            resp_payload[0] = 'o'; resp_payload[1] = 'k';
            send_mozen_cmd_response(MOZEN_CMD_ID_PARAM_CONFIG, header, resp_payload, 2 + data_len);
        } 
        else 
        {
            send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, false);
        }
    } 
    else if (header->operation == 0x01) 
    {
        uint8_t success = 0;
        if (header->sub_class == 0x01) 
        {
            success = app_math_write_temp_params(payload, payload_len);
        } 
        else if (header->sub_class == 0x02) 
        {
            success = app_math_save_params(payload, payload_len);
        } 
        else if (header->sub_class == 0x03) 
        {
            success = app_math_clear_params();
            app_calibration_invalidate_runtime();
        }
        send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, success);
    }
}
/*
* @brief  handle param config
* @param  context: context
* @param  cmd_id: cmd id
* @param  data: data
* @param  length: length
* @retval none
*/
static void handle_param_config(void *context, uint8_t cmd_id, const uint8_t *data, uint16_t length) 
{
    if (length < sizeof(mozen_cmd_header_t)) return;
    
    const mozen_cmd_header_t* cmd_header = (const mozen_cmd_header_t*)data;
    const uint8_t* cmd_payload = data + sizeof(mozen_cmd_header_t);
    uint16_t payload_len = length - sizeof(mozen_cmd_header_t);

    for (size_t i = 0; i < sizeof(s_param_handlers) / sizeof(s_param_handlers[0]); i++) 
    {
        if (s_param_handlers[i].target_id == cmd_header->target_id) 
        {
            s_param_handlers[i].handler(cmd_header, cmd_payload, payload_len);
            break;
        }
    }
}
// --- Production Test Handlers (Cmd ID: 0xAA) ---

/*
* @brief  handle system command
* @param  header: header
* @param  payload: payload
* @param  payload_len: payload length
* @retval none
*/
static void prod_handle_sys_cmd(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) 
{
    if (header->operation == 0x01) 
    {
        if (header->sub_class == 0x02) 
        {
            if(payload_len > 0) 
            {
                g_is_creep_enable = (payload[0] == 0x01);
                send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, true);
            }
        } 
        else if (header->sub_class == 0x01) 
        {
            app_dev_info dev_info;
            if (payload_len == 0U || payload_len >= sizeof(dev_info.sn)) 
            {
                send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false);
                return;
            }
            if (!app_device_get_info(&dev_info)) 
            {
                send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false);
                return;
            }
            memset(dev_info.sn, 0, sizeof(dev_info.sn));
            memcpy(dev_info.sn, payload, payload_len);
            if (!app_device_set_info(&dev_info)) 
            {
                send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false);
                return;
            }
            send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, true);
        }
    }
}

/*
* @brief  handle map calibration
* @param  header: header
* @param  payload: payload
* @param  payload_len: payload length
* @retval none
*/
static void prod_handle_map_cali_cmd(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) 
{
    uint8_t resp_payload[260];
    
    // First, handle map temp params (operation 0x01, sub_class 0x3F / 0x3D) which return no response
    // First, handle map temp params (operation 0x01, sub_class 0x3F / 0x3D) which return no response
    if (header->operation == 0x01) 
    {
        if (header->sub_class == 0x3F) 
        {
            app_map_write_temp_params(payload, payload_len);
            return;
        } 
        else if (header->sub_class == 0x3D)
        {
            app_map_save_params(payload, payload_len);
            return;
        }
    }

    switch (header->sub_class) 
    {
        case 0x4F:
            if (header->operation == 0x00) 
            {
                uint16_t d_len;
                if(app_pressure_read_temp_params(resp_payload + 2, &d_len)) 
                {
                    resp_payload[0] = 'o'; resp_payload[1] = 'k';
                    send_mozen_cmd_response(MOZEN_CMD_ID_PROD_TEST, header, resp_payload, 2 + d_len);
                } else {
                    send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false);
                }
            } else if (header->operation == 0x01) {
                bool success = app_pressure_write_temp_params(payload, payload_len);
                send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, success);
            }
            break;
        case 0x4D:
            if (header->operation == 0x00) 
            {
                uint16_t d_len;
                if(app_pressure_read_solidified_params(resp_payload + 2, &d_len)) 
                {
                    resp_payload[0] = 'o'; resp_payload[1] = 'k';
                    send_mozen_cmd_response(MOZEN_CMD_ID_PROD_TEST, header, resp_payload, 2 + d_len);
                } else {
                    send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false);
                }
            } else if (header->operation == 0x01) 
            {
                bool success = app_pressure_save_params(payload, payload_len);
                send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, success);
            }
            break;
        case 0x3F:
        case 0x3D:
            if (header->operation == 0x00) 
            {
                uint32_t total_data_len = app_map_data_size();
                uint16_t total_packets = (total_data_len + 240 - 1) / 240;
                memcpy(resp_payload, &total_data_len, sizeof(uint32_t));
                memcpy(resp_payload + 4, &total_packets, sizeof(uint16_t));
                send_mozen_cmd_response(MOZEN_CMD_ID_PROD_TEST, header, resp_payload, 6);
            } 
            else if (header->operation == 0x02) 
            {
                if (payload_len < 2) 
                {
                    send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false);
                    break;
                }
                uint16_t packet_index;
                memcpy(&packet_index, payload, sizeof(uint16_t));
                uint32_t total_data_len = app_map_data_size();
                uint32_t offset = (packet_index - 1) * 240;

                if (offset >= total_data_len) 
                {
                    send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false);
                    break;
                }
                uint16_t chunk_len = 240;
                if (offset + chunk_len > total_data_len) chunk_len = total_data_len - offset;

                memcpy(resp_payload, &packet_index, sizeof(uint16_t));
                memcpy(resp_payload + 2, app_map_data_ptr() + offset, chunk_len);
                send_mozen_cmd_response(MOZEN_CMD_ID_PROD_TEST, header, resp_payload, 2 + chunk_len);
            }
            break;
        case 0x00:
            if (header->operation == 0x01) 
            {
                bool success = app_pressure_clear_params();
                success = success && app_map_clear_params();
                send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, success);
                app_calibration_invalidate_runtime();
            }
            break;
    }
}  

/* 
*   This function handles the production test command.
*   It checks the target ID and calls the appropriate handler function.
*   @param context The context pointer.
*   @param cmd_id The command ID.
*   @param data The command data.
*   @param length The length of the command data.
*/
static void handle_production_test(void *context, uint8_t cmd_id, const uint8_t *data, uint16_t length) 
{
    if (length < sizeof(mozen_cmd_header_t)) return;

    const mozen_cmd_header_t* cmd_header = (const mozen_cmd_header_t*)data;
    const uint8_t* cmd_payload = data + sizeof(mozen_cmd_header_t);
    uint16_t payload_len = length - sizeof(mozen_cmd_header_t);

    for (size_t i = 0; i < sizeof(s_prod_handlers) / sizeof(s_prod_handlers[0]); i++) 
    {
        if (s_prod_handlers[i].target_id == cmd_header->target_id) {
            s_prod_handlers[i].handler(cmd_header, cmd_payload, payload_len);
            break;
        }
    }
}

/*
*  This function handles the tunnel command.
*  It calls the tunnel handle command function with the command data.
*  @param context The context pointer.
*  @param cmd_id The command ID.
*  @param data The command data.
*  @param length The length of the command data.
*/
static void handle_tunnel_command(void *context, uint8_t cmd_id, const uint8_t *data, uint16_t length) 
{
    mozen_tunnel_handle_command(&s_tunnel_instance, data, length);
}

// --- Tunnel Callbacks ---
/*
*  This function is called when a stream starts.
*  It checks if the target command is production test and the sub class is 0x3F or 0x3D.
*  If so, it sets the stream state to streaming write and initializes the write pointer.
*  @param context The context pointer.
*  @param target_cmd The target command.
*  @param first_chunk_data The first chunk of data.
*  @param chunk_len The length of the chunk.
*  @return true if the stream is started, false otherwise.
*/
static bool tunnel_on_stream_start(void *context, uint8_t target_cmd, const uint8_t *first_chunk_data, uint16_t chunk_len) 
{
    if (target_cmd == MOZEN_CMD_ID_PROD_TEST) {
        const mozen_cmd_header_t* pt_header = (const mozen_cmd_header_t*)first_chunk_data;
        if (chunk_len >= sizeof(mozen_cmd_header_t) && pt_header->target_id == MOZEN_PROD_TGT_MAP_CALI && pt_header->operation == 0x01) 
        {
            if (pt_header->sub_class == 0x3F || pt_header->sub_class == 0x3D) 
            {
                s_stream_state.is_streaming_write = true;
                s_stream_state.target_sub_class = pt_header->sub_class;
                s_stream_state.target_cmd = target_cmd;
                s_stream_state.write_ptr = app_map_data_ptr();
                
                uint16_t data_offset = sizeof(mozen_cmd_header_t);
                uint16_t data_to_write = chunk_len - data_offset;
                uint8_t* end_ptr = (app_map_data_ptr() + app_map_data_size());
                
                if (s_stream_state.write_ptr + data_to_write > end_ptr) {
                    data_to_write = end_ptr - s_stream_state.write_ptr;
                }
                
                if (data_to_write > 0) {
                    memcpy(s_stream_state.write_ptr, first_chunk_data + data_offset, data_to_write);
                    s_stream_state.write_ptr += data_to_write;
                }
                return true;
            }
        }
    }
    return false;
}

/*
*  This function is called when data is received during a stream.
*  It checks if the stream is in streaming write mode and writes the data to the map data buffer.
*  @param context The context pointer.
*  @param data The data.
*  @param len The length of the data.
*  @return true if the data is handled, false otherwise.
*/
static bool tunnel_on_stream_data(void *context, const uint8_t *data, uint16_t len) 
{
    if (s_stream_state.is_streaming_write) {
        uint8_t* end_ptr = (app_map_data_ptr() + app_map_data_size());
        uint16_t data_to_write = len;
        
        if (s_stream_state.write_ptr >= end_ptr) {
            data_to_write = 0;
        } else if (s_stream_state.write_ptr + data_to_write > end_ptr) {
            data_to_write = end_ptr - s_stream_state.write_ptr;
        }
        
        if (data_to_write > 0) {
            memcpy(s_stream_state.write_ptr, data, data_to_write);
            s_stream_state.write_ptr += data_to_write;
        }
        return true;
    }
    return false;
}

/*
*  This function is called when a stream finishes.
*  It checks if the stream is in streaming write mode and saves the map parameters if the target command is production test and the sub class is 0x3F or 0x3D.
*  @param context The context pointer.
*  @param success The success flag.
*  @return None.
*/
static void tunnel_on_stream_finish(void *context, bool success) 
{
    if (s_stream_state.is_streaming_write && success) 
    {
        uint32_t expected_bytes = app_map_data_size();
        uint32_t written_bytes = (uint32_t)(s_stream_state.write_ptr - app_map_data_ptr());

        if (written_bytes >= expected_bytes)
        {
            if (s_stream_state.target_cmd == MOZEN_CMD_ID_PROD_TEST && s_stream_state.target_sub_class == 0x3D)
            {
                app_map_save_params(app_map_data_ptr(), (uint16_t)expected_bytes);
                app_calibration_request_reset_effect();
            }
        }
    }
    s_stream_state.is_streaming_write = false;
    s_stream_state.write_ptr = NULL;
}

// --- Init ---

/*
* @brief: Initialize the mozen handler.
* @param tx_fn: The function to send data.
* @return None.
*/
void app_mozen_init(mozen_tx_fn_t tx_fn)
{
    mozen_protocol_init(&g_mozen_prot, s_rx_buf, sizeof(s_rx_buf),
                        s_tx_buf_main, s_tx_buf_irq, sizeof(s_tx_buf_main),
                        tx_fn, NULL);
    app_mozen_handler_init(&g_mozen_prot);
}

/*
* @brief: Handle raw frame from mozen protocol
* @param: prot - Protocol instance
* @return: None
*/
void app_mozen_handler_init(mozen_protocol_t *prot)
{
    s_prot_instance = prot;
    
    // Register commands
    mozen_protocol_register_handler(prot, MOZEN_CMD_ID_SENSOR_DATA, handle_sensor_data);
    mozen_protocol_register_handler(prot, MOZEN_CMD_ID_PARAM_CONFIG, handle_param_config);
    mozen_protocol_register_handler(prot, MOZEN_CMD_ID_PROD_TEST, handle_production_test);
    mozen_protocol_register_handler(prot, MOZEN_TUNNEL_COMMAND_ID, handle_tunnel_command);

    // Legacy handler
    mozen_protocol_set_raw_handler(prot, app_mozen_raw_frame_handler);

    // Init tunnel
    mozen_tunnel_init(&s_tunnel_instance, prot, 240, NULL);
    mozen_tunnel_set_callbacks(&s_tunnel_instance, tunnel_on_stream_start, tunnel_on_stream_data, tunnel_on_stream_finish);
}
