/**
 * @file app_common.c
 * @brief This is app_common file
 * @version 1.0
 * @date 2024-08-19
 *
 * @copyright Copyright 2024-2024 Tuya Inc. All Rights Reserved.
 *
 */

#include "app_common.h"
#include "tal_system.h"
#include "tal_gpio.h"
#include "tal_uart.h"
#include "tal_i2c.h"
#include "tal_sw_timer.h"
#include "tal_memory.h"
#include "tal_log.h"
#include "tal_utc.h"
#include "tal_rtc.h"
#include "tal_oled.h"
#include "tal_bluetooth_mesh_device.h"
#include "tal_uart_protocol.h"
#include "tal_ble_rssi_test.h"
#include "ty_ble_ota_adapt.h"
#include "app_config.h"
#include "tal_mesh_factory_test.h"
#include "tuya_sdk_test.h"

/***********************************************************************
 ********************* constant ( macro and enum ) *********************
 **********************************************************************/
#define tal_main_debug(...)     TAL_PR_DEBUG(__VA_ARGS__)

#define USE_MESH_ADVANCED_ABILITY_1 1

#define DP_ID_ONOFF         0x01
#define DP_ID_MODE          0x02
#define DP_ID_LIGHTNESS     0x03
#define DP_ID_TEMPTURE      0x04
#define DP_ID_HSL           0x05
#define DP_ID_SCENE         0x06

/***********************************************************************
 ********************* struct ******************************************
 **********************************************************************/


/***********************************************************************
 ********************* variable ****************************************
 **********************************************************************/
STATIC UINT8_T gatt_connect_state = 0;
STATIC TIMER_ID app_mdev_test_timer_id = NULL;

STATIC UINT8_T onoff_data  = 0x01;
STATIC UINT8_T mode  = 0;
STATIC UINT32_T lightness_dp = 1000;
STATIC UINT32_T temp_dp = 500;

/***********************************************************************
 ********************* function ****************************************
 **********************************************************************/
VOID_T tuya_log_output_cb(IN CONST CHAR_T *str)
{
    extern VOID_T tkl_system_log_output(CONST UINT8_T *buf, UINT32_T size);
    tkl_system_log_output((VOID_T*)str, strlen((CHAR_T*)str));
}

VOID_T app_ble_connect_state_set(UINT8_T state)
{
    gatt_connect_state = state;
}

UINT8_T app_ble_connect_state_get(VOID_T)
{
    return gatt_connect_state;
}

OPERATE_RET tuya_firmware_config(VOID_T)
{
    UINT8_T pid[] = PRODUCTKEY;
    UINT8_T firmware_key[] = PRODUCTKEY;
    UINT8_T firmware_name[] = BUILD_FIRMNAME;
    UINT8_T firmware_version[] = FW_VERSION;
    UINT16_T mesh_category = MESH_CATEGORY;

    tal_mesh_factory_firmware_info_set(firmware_name, firmware_version);
    tal_mesh_gatt_ota_version_set(FW_VERSION_HEX);
    tal_firmware_infor_set(IS_FIRMWARE_KEY, pid, firmware_key, FW_VERSION_HEX, mesh_category, NEED_PUBLISH_ADDR);
    return OPRT_OK;
}

STATIC VOID_T tuya_uart_irq_rx_cb(TUYA_UART_NUM_E port_num, VOID_T *buff, UINT16_T len)
{
//    TAL_PR_HEXDUMP_DEBUG("uart_recv", buff, len);
    if (port_num == 0) {
        tal_uart_receive_common_data(buff, len);
    }
}

/* default not use,see the function definition in the .h file.*/
#if USE_MESH_ADVANCED_ABILITY_1
OPERATE_RET app_mesh_vendor_get_recv(UINT8_T dp_id, UINT8_T *dp_type, UINT8_T *dp_len, UINT8_T *dp_data)
{
    switch (dp_id) {
        case DP_ID_ONOFF:
            *dp_type = DP_TYPE_BOOL;
            *dp_len = 1;
            dp_data[0] = onoff_data;
        break;
        case DP_ID_MODE:
            *dp_type = DP_TYPE_ENUM;
            *dp_len = 1;
            dp_data[0] = mode;
        break;
        case DP_ID_LIGHTNESS:
            *dp_type = DP_TYPE_VALUE;
            *dp_len = 4;
            dp_data[0] = (lightness_dp >> 24) & 0xFF;
            dp_data[1] = (lightness_dp >> 16) & 0xFF;
            dp_data[2] = (lightness_dp >> 8) & 0xFF;
            dp_data[3] = lightness_dp & 0xFF;
        break;
        case DP_ID_TEMPTURE:
            *dp_type = DP_TYPE_VALUE;
            *dp_len = 4;
            dp_data[0] = (temp_dp >> 24) & 0xFF;
            dp_data[1] = (temp_dp >> 16) & 0xFF;
            dp_data[2] = (temp_dp >> 8) & 0xFF;
            dp_data[3] = temp_dp & 0xFF;
        break;
        default:
            return OPRT_NOT_FOUND;
    }

    return OPRT_OK;
}
#endif

OPERATE_RET app_mesh_data_recv(TAL_MESH_ACCESS_MSG_T *msg_raw, TAL_MESH_NET_PARAM_T *net_param)
{
    TAL_MESH_GENERIC_ONOFF_STATUS_T     generic_onoff_status;
    TAL_MESH_LIGHT_LIGHTNESS_STATUS_T   lightness_status;
    TAL_MESH_LIGHT_CTL_STATUS_T         light_ctl_status;
    TAL_MESH_LIGHT_CTL_TEMP_STATUS_T    light_ctl_temp_status;

    switch (msg_raw->opcode) {
        case TAL_MESH_OPCODE_ON_OFF_GET:
            generic_onoff_status.present = onoff_data;
            tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_ON_OFF_STAT, (UINT8_T*)&generic_onoff_status, 1);
            break;
        case TAL_MESH_OPCODE_ON_OFF_SET:
        case TAL_MESH_OPCODE_ON_OFF_SET_UNACK: {
            TAL_MESH_GENERIC_ONOFF_SET_T *generic_onoff_set = (TAL_MESH_GENERIC_ONOFF_SET_T*)msg_raw->data;
            onoff_data = generic_onoff_set->onoff;
            if (TAL_MESH_OPCODE_ON_OFF_SET == msg_raw->opcode) {
                generic_onoff_status.present = generic_onoff_set->onoff;
                tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_ON_OFF_STAT, (UINT8_T*)&generic_onoff_status, 1);
            }
#if TUYA_SDK_TEST
            tal_sdk_test_mesh_data_write(1, 1, &onoff_data, 1);
#endif
            }
            break;
        case TAL_MESH_OPCODE_LIGHTNESS_GET:
            lightness_status.present = (UINT16_T)((float)lightness_dp * 65535 / 1000);
            tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_LIGHTNESS_STAT, (UINT8_T*)&lightness_status, 2);
            break;
        case TAL_MESH_OPCODE_LIGHTNESS_SET:
        case TAL_MESH_OPCODE_LIGHTNESS_SET_UNACK: {
            TAL_MESH_LIGHT_LIGHTNESS_SET_T *lightness_set = (TAL_MESH_LIGHT_LIGHTNESS_SET_T*)msg_raw->data;
            lightness_dp = lightness_set->lightness * 1000 / 65535;
            if (TAL_MESH_OPCODE_LIGHTNESS_SET == msg_raw->opcode) {
                lightness_status.present = lightness_set->lightness;
                tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_LIGHTNESS_STAT, (UINT8_T*)&lightness_status, 2);
            }

            UINT8_T dp_data[4] = {0};
            dp_data[0] = 0;
            dp_data[1] = 0;
            dp_data[2] = lightness_dp >> 8;
            dp_data[3] = lightness_dp & 0xFF;
#if TUYA_SDK_TEST
            tal_sdk_test_mesh_data_write(3, 2, dp_data, 4);
#endif
            }
            break;
        case TAL_MESH_OPCODE_LIGHT_CTL_GET:
            light_ctl_status.present_lightness = (UINT16_T)((float)lightness_dp * 65535 / 1000);
            light_ctl_status.present_temp = (UINT16_T)((float)temp_dp * 19200 / 1000 + 800);
            tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_LIGHT_CTL_STAT, (UINT8_T*)&light_ctl_status, 4);
            break;
        case TAL_MESH_OPCODE_LIGHT_CTL_SET:
        case TAL_MESH_OPCODE_LIGHT_CTL_SET_UNACK: {
                // NOT USE
            }
            break;
        case TAL_MESH_OPCODE_LIGHT_CTL_TEMP_GET:
            light_ctl_temp_status.present_temp = (UINT16_T)((float)temp_dp * 19200 / 1000 + 800);
            light_ctl_temp_status.present_delta_uv = light_ctl_temp_status.present_temp;
            tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_LIGHT_CTL_TEMP_STAT, (UINT8_T*)&light_ctl_temp_status, 4);
            break;
        case TAL_MESH_OPCODE_LIGHT_CTL_TEMP_SET:
        case TAL_MESH_OPCODE_LIGHT_CTL_TEMP_SET_UNACK: {
            TAL_MESH_LIGHT_CTL_TEMP_SET_T *light_ctl_temp_set = (TAL_MESH_LIGHT_CTL_TEMP_SET_T*)msg_raw->data;
            temp_dp = ((float)light_ctl_temp_set->temp - 800) * 1000 / 19200;
            if (TAL_MESH_OPCODE_LIGHT_CTL_TEMP_SET == msg_raw->opcode) {
                light_ctl_temp_status.present_temp = light_ctl_temp_set->temp;
                light_ctl_temp_status.present_delta_uv = light_ctl_temp_set->temp;
                tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_LIGHT_CTL_TEMP_STAT, (UINT8_T*)&light_ctl_temp_status, 4);
            }

            UINT8_T dp_data[4] = {0};
            dp_data[0] = 0;
            dp_data[1] = 0;
            dp_data[2] = temp_dp >> 8;
            dp_data[3] = temp_dp & 0xFF;
#if TUYA_SDK_TEST
            tal_sdk_test_mesh_data_write(4, 2, dp_data, 4);
#endif
            }
            break;
  // vendor model cmd-----------------
        case TAL_MESH_OPCODE_READ:
            break;
        case TAL_MESH_OPCODE_WRITE:
        case TAL_MESH_OPCODE_WRITE_UNACK:
            if (msg_raw->data_len < 2) {
                mode = msg_raw->data[0];
#if TUYA_SDK_TEST
                tal_sdk_test_mesh_data_write(2, 4, msg_raw->data, 1);
#endif
                if (TAL_MESH_OPCODE_WRITE == msg_raw->opcode) {
                    tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_DATA, msg_raw->data, msg_raw->data_len);
                }
            } else {
                if (0x02 == msg_raw->data[0]) {
                    tal_utc_date_t date = {0};
                    UINT32_T unix_time = 0;
                    INT16_T time_zone = 0;
                    if (msg_raw->data[2] & 0x04) {
                        unix_time += (msg_raw->data[3] << 24);
                        unix_time += (msg_raw->data[4] << 16);
                        unix_time += (msg_raw->data[5] << 8);
                        unix_time += msg_raw->data[6] & 0xFF;
                    }
                    if (msg_raw->data[2] & 0x02) {
                        time_zone += (msg_raw->data[7] << 8);
                        time_zone += msg_raw->data[8] & 0xFF;
                    }

                    tal_rtc_time_set(unix_time);
                    tal_utc_timestamp2date(unix_time, &date, false);
                    tal_utc_set_time_zone(time_zone);

                    date.year -= 2000;
#if TUYA_SDK_TEST
                    tal_sdk_test_get_time_rsp(&date);
#endif
                } else if (0x01 == msg_raw->data[0]) {
                    if (0x06 == msg_raw->data[1]) {
                        UINT8_T dp_len = msg_raw->data[3];
#if TUYA_SDK_TEST
                        tal_sdk_test_mesh_data_write(6, 3, &msg_raw->data[4], dp_len);
#endif
                    }
                    if (TAL_MESH_OPCODE_WRITE == msg_raw->opcode) {
                        tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_DATA, msg_raw->data, msg_raw->data_len);
                    }
                }
#if NEED_PUBLISH_ADDR
                else if (0x86 == msg_raw->data[0]) {
                    UINT8_T rsp_data[3] = {0x86, 0x01, 0x00};
                    tal_main_debug("PUBLISH_ADDR_RECV_SUCCESS");
                    tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_DATA, rsp_data, 3);
                }
#endif
            }
        break;

/*  default not use,see the function definition in the .h file.*/
#if USE_MESH_ADVANCED_ABILITY_1
        case TAL_MESH_OPCODE_WRITE_WITH_TID: {
            if (0x01 == msg_raw->data[1]) {
                if (0x06 == msg_raw->data[2]) {
                    UINT8_T dp_len = msg_raw->data[4];
#if TUYA_SDK_TEST
                    tal_sdk_test_mesh_data_write(6, 3, &msg_raw->data[5], dp_len);
#endif
                }
            }
            UINT8_T data[2] = {0};
            data[0] = msg_raw->data[0]; // TID
            data[1] = 0x00;
            tal_mesh_data_send(net_param->dst_addr, net_param->src_addr, TAL_MESH_OPCODE_STATUS, data, 2);
        }
        break;
#endif

        default:
        break;
    }

    return OPRT_OK;
}

VOID_T app_ble_data_recv(TAL_BLE_EVT_PARAMS_T* p_event)
{
    switch (p_event->type) {
        case TAL_BLE_EVT_PERIPHERAL_CONNECT:
            tal_main_debug("BLE connect");
            app_ble_connect_state_set(1);
        break;
        case TAL_BLE_EVT_DISCONNECT:
            tal_main_debug("BLE disconnect reason:%x", p_event->ble_event.disconnect.reason);
            app_ble_connect_state_set(0);
        break;
        case TAL_BLE_EVT_ADV_REPORT:
            tal_rssi_test_ble_adv_recv(p_event->ble_event.adv_report.p_data, p_event->ble_event.adv_report.data_len, p_event->ble_event.adv_report.peer_addr.addr, p_event->ble_event.adv_report.rssi);
        break;
        case TAL_BLE_EVT_WRITE_REQ:
            tal_ble_ota_data_recv(p_event->ble_event.write_report.report.p_data, p_event->ble_event.write_report.report.len);
        break;
        case TAL_BLE_EVT_MTU_REQUEST:
//            tal_main_debug("TAL_BLE_EVT_MTU_REQUEST:%d", p_event->ble_event.exchange_mtu.mtu);
        break;

        default:
        break;
    }
}

/*  // Older versions of testing methods.
VOID_T app_mdev_test_get_rssi_callback(TIMER_ID timer_id, VOID_T *arg)
{
    int rssi = 0;
    tal_rssi_base_test_stop(TY_MDEV_RSSI);

    if (OPRT_OK == ty_rssi_base_test_get_rssi_avg(&rssi, TY_MDEV_RSSI)) {
        tal_main_debug("mdev:%d", rssi);
   }
}
*/

OPERATE_RET tuya_init_first(VOID_T)
{
    tuya_firmware_config();
    TAL_UART_CFG_T tal_uart_cfg = {
        .rx_buffer_size = 256,
        .open_mode = 0,
        .base_cfg = {
#if ENABLE_LOG
            .baudrate = 115200,  //使用115200输出log，防止9600波特率打log阻塞应用
#else
            .baudrate = 9600,  //注意!!!! 出厂时需使用9600波特率，授权上位机默认波特率为9600
#endif
            .parity = TUYA_UART_PARITY_TYPE_NONE,
            .databits = TUYA_UART_DATA_LEN_8BIT,
            .stopbits = TUYA_UART_STOP_LEN_1BIT,
            .flowctrl = TUYA_UART_FLOWCTRL_NONE,
        }
    };

    tal_uart_init(TUYA_UART_NUM_0, &tal_uart_cfg);
    tal_uart_rx_reg_irq_cb(TUYA_UART_NUM_0, tuya_uart_irq_rx_cb);
#if ENABLE_LOG
    tal_log_create_manage_and_init(TAL_LOG_LEVEL_DEBUG, 256, tuya_log_output_cb);
#endif

    tal_rtc_init();
    return OPRT_OK;
}

OPERATE_RET tuya_init_second(VOID_T)
{
    tal_main_debug("/************sdk init***********/");
    return OPRT_OK;
}

OPERATE_RET tuya_init_third(VOID_T)
{
    tal_element_register(0);
    tal_model_register(0, TAL_MODEL_ID_GENERIC_ONOFF_SERVER);
    tal_model_register(0, TAL_MODEL_ID_VENDOR_SERVER);
    tal_model_register(0, TAL_MODEL_ID_LIGHT_LIGHTNESS_SERVER);
    tal_model_register(0, TAL_MODEL_ID_LIGHT_CTL_SERVER);
    tal_model_register(0, TAL_MODEL_ID_LIGHT_CTL_TEMP_SERVER);
    tal_model_register(0, TAL_MODEL_ID_LIGHT_HSL_SERVER);

    tal_mesh_msg_recv_cb_init(app_mesh_data_recv);
    tal_mesh_ble_recv_cb_init(app_ble_data_recv);

/*  default not use,see the function definition in the .h file.*/
#if USE_MESH_ADVANCED_ABILITY_1
    tal_mesh_advanced_ability_1(1);
    tal_mesh_vendor_get_cb_init(app_mesh_vendor_get_recv);
    tal_mesh_heartbeat_onoff_info_set(1, &onoff_data, 1);
#endif
    return OPRT_OK;
}

OPERATE_RET tuya_init_last(VOID_T)
{
/*  // Older versions of testing methods.
    if (0 == tal_get_if_prov_success()) {
        tal_rssi_base_test_start(TY_MDEV_RSSI);
        tal_sw_timer_create(app_mdev_test_get_rssi_callback, NULL, &app_mdev_test_timer_id);
        tal_sw_timer_start(app_mdev_test_timer_id, 2000, TAL_TIMER_ONCE);
    }
*/

#if TUYA_SDK_TEST
    TUYA_IIC_BASE_CFG_T i2c_cfg = {
        .role = TUYA_IIC_MODE_MASTER,
        .speed = TUYA_IIC_BUS_SPEED_400K,
        .addr_width = TUYA_IIC_ADDRESS_7BIT,
    };
    tal_i2c_init(TUYA_I2C_NUM_0, &i2c_cfg);

    if (OPRT_OK == tal_oled_init()) {
        tal_oled_clear();
        UINT8_T oled_data[] = "TuyaOS Mesh";
        tal_oled_show_string(12, 1, (void*)oled_data, 16);
    }
#endif
    return OPRT_OK;
}

OPERATE_RET tuya_main_loop(VOID_T)
{
    return OPRT_OK;
}

VOID_T tal_mesh_state_callback(TAL_MESH_NET_STATE_T state)
{
    tal_main_debug("mesh_state:%d", state);
    switch (state) {
        case TAL_MESH_PROVISION_SUCCESS:
            // Use to update mesh network transmit param, if developer don't know how to set, please don't change!
            tkl_mesh_network_transmit_set(7, 0);
            tkl_mesh_network_relay_retransmit_set(4, 0);
        break;
        default:
        break;
    }
}

