#include "protocol.h"
#include "dy_utils/dy_common.h"
#include "dy_utils/dy_ipc.h"
#include "dy_utils/hash_intptr.h"
#include "dy_utils/modbus_utils.h"
#include "dy_utils/period_service.h"
#include "dy_utils/bitmap.h"
#include "protocol.h"
#include "common.h"
#include "define.h"

const char *get_tty_from_port(gw_port_e port)
{
    const char *board_name = get_board_name();

    if (strcmp(board_name, BOARD_WOOLINK_MT7628) == 0)
    {
        switch (port)
        {
        case RS485_1:
            return "/dev/ttyS1";
            break;
        case RS485_2:
        case RS232_1:
            return "/dev/ttyS0";
            break;

        /* For DAYUN MT7628 board, with CH348Q USB to serial converter: */
        case RS485_5:
            return "/dev/ttysym1";
            break;
        case RS485_6:
            return "/dev/ttysym2";
            break;
        case RS485_7:
            return "/dev/ttysym3";
            break;
        case RS485_8:
            return "/dev/ttysym4";
            break;

        case RS485_9:
            return "/dev/ttysym5";
            break;
        case RS485_10:
            return "/dev/ttysym6";
            break;
        case RS485_11:
            return "/dev/ttysym7";
            break;
        case RS485_12:
            return "/dev/ttysym8";
            break;
        default:
            break;
        }
    }
    else if (strcmp(board_name, BOARD_WOOLINK_MT7621) == 0)
    {
        switch (port)
        {
        case RS485_1:
            return "/dev/ttyFTDI0";
            break;
        case RS485_2:
            return "/dev/ttyFTDI1";
            break;
        case RS485_3:
            return "/dev/ttyFTDI2";
            break;
        case RS485_4:
            return "/dev/ttyFTDI3";
            break;
        default:
            break;
        }
    }
    else if (strcmp(board_name, "default-string-default-string") == 0)
    {
        // x86 iot box
        switch (port)
        {
        case RS485_1:
            return "/dev/ttyS2";
            break;
        case RS485_2:
            return "/dev/ttyS3";
            break;
        case RS485_3:
            return "/dev/ttyS4";
            break;
        case RS485_4:
            return "/dev/ttyS5";
            break;
        default:
            break;
        }
    }
    else if (strncmp(board_name, "WLOSANY_", 0x8) == 0) {
        /* WLOSANY_ stands for WooLinkOS Anywhere */
        switch (port) {
        case RS485_1:
            return "/dev/ttylnx1";
        case RS485_2:
            return "/dev/ttylnx2";
        case RS485_3:
            return "/dev/ttylnx3";
        case RS485_4:
            return "/dev/ttylnx4";
        case RS485_5:
            return "/dev/ttylnx5";
        case RS485_6:
            return "/dev/ttylnx6";
        case RS485_7:
            return "/dev/ttylnx7";
        case RS485_8:
            return "/dev/ttylnx8";
        case RS485_9:
            return "/dev/ttylnx9";
        case RS485_10:
            return "/dev/ttylnx10";
        case RS485_11:
            return "/dev/ttylnx11";
        case RS485_12:
            return "/dev/ttylnx12";
        default:
                break;
        }
    }
    else if (strstr(board_name, "BAU-B30-003") == board_name)
    {
        switch (port)
        {
        case RS485_6:
                return "/dev/ttyS4";
        case RS485_7:
                return "/dev/ttyS3";
        case RS485_8:
                return "/dev/ttyS7";
        case RS485_9:
                return "/dev/ttyS8";
        default:
                break;
        }
    }
    else
    {
        switch (port)
        {
        case RS485_1:
            return "/dev/ttymxc1";
            break;
        case RS485_2:
            return "/dev/ttymxc2";
            break;
        case RS485_3:
            return "/dev/ttymxc3";
            break;
        case RS485_4:
            return "/dev/ttymxc4";
            break;

        /* For WooLink2F6280-W-4G, BOARD_WOOLink2F6280_W_4G */
        case RS485_5:
            return "/dev/ttysym1";
            break;
        case RS485_6:
            return "/dev/ttysym2";
            break;
        case RS485_7:
            return "/dev/ttysym3";
            break;
        case RS485_8:
            return "/dev/ttysym4";
            break;

        case RS485_9:
            return "/dev/ttysym5";
            break;
        case RS485_10:
            return "/dev/ttysym6";
            break;
        case RS485_11:
            return "/dev/ttysym7";
            break;
        case RS485_12:
            return "/dev/ttysym8";
            break;

        default:
            break;
        }
    }

    return NULL;
}

int change_name_to_dev(char *name,char *devName)
{
    const char *board_name = get_board_name();

    if((board_name == NULL)||(strlen(board_name)<=0))
    {
         return -1;
    }
    else
    {
        gw_port_e port = port_char2enum(name);
        const char *p = get_tty_from_port(port);
        if (p == NULL)
        {
            proto_syslog(LOG_ERR, "uart name to dev error, %s", name);
            return  -1;
        }
        memcpy(devName,p,strlen(p));
    }
    return 0;
}

static int load_uart_par(char *uart, uart_par_t *p_par)
{
    int re = 0;
    char *cfg_f = DEV_PORT_CFG_FILE;
    char *f_data = read_file_data(cfg_f);
    if (f_data == NULL)
    {
        proto_syslog(LOG_ERR, "load uart config file error, file:%s", cfg_f);
        return -1;
    }

    cJSON *root = json_parse_string_with_comments(f_data);
    if (!root)
    {
        proto_syslog(LOG_ERR, "parse file to json obj error, file:%s", cfg_f);
        re = -2;
        goto load_uart_par_exit;
    }

    cJSON *tmp_json = cJSON_GetObjectItemCaseSensitive(root, "dev_cfg");
    if (!tmp_json)
    {
        proto_syslog(LOG_ERR, "invalid dev_cfg");
        re = -3;
        goto load_uart_par_exit;
    }

    cJSON *dev_json = cJSON_GetObjectItemCaseSensitive(tmp_json, "uart");
    if (!dev_json)
    {
        proto_syslog(LOG_ERR, "invalid uart");
        re = -4;
        goto load_uart_par_exit;
    }

    cJSON *attr_arr_json = cJSON_GetObjectItemCaseSensitive(dev_json, "attr");
    if (!attr_arr_json)
    {
        proto_syslog(LOG_ERR, "invalid attr");
        re = -6;
        goto load_uart_par_exit;
    }
    int cnt = cJSON_GetArraySize(attr_arr_json);
    cJSON *target_json = NULL;
    for (int i = 0; i < cnt; i++)
    {
        tmp_json = cJSON_GetArrayItem(attr_arr_json, i);
        target_json = cJSON_GetObjectItemCaseSensitive(tmp_json, "name");
        if (strcmp(target_json->valuestring, uart) == 0)
        {
            target_json = tmp_json;
            break;
        }
        target_json = NULL;
    }

    if (target_json == NULL)
    {
        proto_syslog(LOG_ERR, "no available config was found, %s", cfg_f);
        re = -7;
        goto load_uart_par_exit;
    }
    
    tmp_json = cJSON_GetObjectItemCaseSensitive(target_json, "baud");
    if (tmp_json == NULL)
    {
        re = -9;
        goto load_uart_par_exit;
    }
    p_par->baud = tmp_json->valueint;

    tmp_json = cJSON_GetObjectItemCaseSensitive(target_json, "data_bit");
    if (tmp_json == NULL)
    {
        re = -9;
        goto load_uart_par_exit;
    }
    p_par->data_bit = tmp_json->valueint;

    tmp_json = cJSON_GetObjectItemCaseSensitive(target_json, "stop_bit");
    if (tmp_json == NULL)
    {
        re = -10;
        goto load_uart_par_exit;
    }
    p_par->stop_bit = tmp_json->valueint;

    tmp_json = cJSON_GetObjectItemCaseSensitive(target_json, "parity");
    if (tmp_json == NULL)
    {
        re = -11;
        goto load_uart_par_exit;
    }
    p_par->parity = tmp_json->valuestring[0];

    if(change_name_to_dev(uart,p_par->f_name)<0)
    {
        re = -12;
        goto load_uart_par_exit;
    }
        

load_uart_par_exit:
    free(f_data);
    if (root != NULL)
    {
        cJSON_Delete(root);
    }
    return re;
}

int extract_uart_par(char *info, uart_par_t *p_par)
{
    int re = load_uart_par(info, p_par);
    if (re == 0)
    {
        proto_syslog(LOG_INFO, "extract uart par[%s, %d, %c, %d, %d]", p_par->f_name, p_par->baud, p_par->parity, p_par->data_bit, p_par->stop_bit);
    }
    else
    {
        proto_syslog(LOG_ERR, "extract uart par error,Please check whether exist [%s] in you config",info);
    }
    return re;
}

int is_bmser_BAU()
{
    const char *board_name = get_board_name();
    if(strcmp(board_name, BOARD_NAME_BAU_B30_003) == 0)
    {
        return 1;
    }
    return 0;
}

modbus_t *modbus_proto_init(modbus_proto *if_info)
{
    modbus_t *ctx = NULL;
    uart_par_t p_par = {0};
    char modbus_name[32] = {0};
    switch (if_info->type)
    {
    case MDBUS_RTU:
    {
        /*
        这部分代码是需要协能的虚拟串口服务，虚拟串口服务是通过udp转发到mcu的串口(串口1到串口5分别对应本地ip的端口12000-120004)
        */
        int port = 0;
        char ip[32] = {0};
        if (is_bmser_BAU())
        {
            port = is_virtual_device_and_to_dup(if_info->dev_or_ip, ip); // 将RS485_1到RS485_5转为rtu over udp
            if (port > 0)
            {
                proto_syslog(LOG_NOTICE, "%s is virtual device[udp->%s:%d]", if_info->dev_or_ip, ip, port);
                ctx = modbus_new_rtu_udp(ip, port);
            }
        }
        if (port <= 0)
        {
            if (0 == extract_uart_par(if_info->dev_or_ip, &p_par))
            {
                if (dev_is_open(p_par.f_name))
                {
                    proto_syslog(LOG_WARNING, "warning!!!!! another process is using the %s", p_par.f_name);
                }
                ctx = modbus_new_rtu(p_par.f_name, p_par.baud, p_par.parity, p_par.data_bit, p_par.stop_bit);
            }
        }
        snprintf(modbus_name, sizeof(modbus_name), "%s", if_info->dev_or_ip);
    }
    break;

    case MDBUS_RTU_OVER_TCP:
        ctx = modbus_new_rtu_tcp(if_info->dev_or_ip, if_info->port);
        snprintf(modbus_name, sizeof(modbus_name), "%s:%d", if_info->dev_or_ip, if_info->port);
        break;

    case MDBUS_TCP:
        ctx = modbus_new_tcp(if_info->dev_or_ip, if_info->port);
        snprintf(modbus_name, sizeof(modbus_name), "%s:%d", if_info->dev_or_ip, if_info->port);
        break;

    case MDBUS_TCP_PI:
        break;
    
    case MDBUS_RTU_RCRC:
    {
        if (0 == extract_uart_par(if_info->dev_or_ip, &p_par))
        {
            if (dev_is_open(p_par.f_name))
            {
                proto_syslog(LOG_WARNING, "warning!!!!! another process is using the %s", p_par.f_name);
            }
            ctx = modbus_new_rtu_rcrc(p_par.f_name, p_par.baud, p_par.parity, p_par.data_bit, p_par.stop_bit);
        }
        snprintf(modbus_name, sizeof(modbus_name), "%s", if_info->dev_or_ip);
    }
        break;

    default:
        break;
    }

    if (ctx != NULL)
    {
        modbus_set_response_timeout(ctx, 5, 0);
        int ret = modbus_connect(ctx);
        if (ret != 0)
        {
            modbus_free(ctx);
            ctx = NULL;
            proto_syslog(LOG_ERR, "modbus_connect error, ret = %d", ret);
        }
        else
        {
            // modbus_set_error_recovery(ctx, MODBUS_ERROR_RECOVERY_LINK);
            modbus_set_name(ctx, modbus_name);
            // modbus_set_debug(ctx, true);
            modbus_set_error_recovery(ctx, MODBUS_ERROR_RECOVERY_PROTOCOL);
        }
        
    }
    else
    {
        proto_syslog(LOG_ERR, "modbus proto init error !!!");
    }
    return ctx;
}

int modbus_proto_deinit(modbus_t *ctx)
{
    modbus_close(ctx);
    modbus_free(ctx);
    return 0;
}

int modbus_proto_read(modbus_t *ctx, uint8_t dev_addr, uint8_t fun_code, uint16_t reg_addr, uint16_t reg_sum, void *ack_buff)
{
    int ret = 0;
    if (0 != modbus_set_slave(ctx, dev_addr))
    {
        proto_syslog(LOG_ERR, "modbus set slave addr error,ragnge:(0-247)");
        return -99;
    }
    switch (fun_code)
    {
    case MODBUS_FC_READ_COILS:
        ret = modbus_read_bits(ctx, reg_addr, reg_sum, (uint8_t *)ack_buff);
        break;

    case MODBUS_FC_READ_DISCRETE_INPUTS:
        ret = modbus_read_input_bits(ctx, reg_addr, reg_sum, (uint8_t *)ack_buff);
        break;

    case MODBUS_FC_READ_HOLDING_REGISTERS:
        ret = modbus_read_registers(ctx, reg_addr, reg_sum, (uint16_t *)ack_buff);
        if (ret > 0)
        {
            ret = ret << 1; // 寄存器个数转为字节数
        }
        break;

    case MODBUS_FC_READ_INPUT_REGISTERS:
        ret = modbus_read_input_registers(ctx, reg_addr, reg_sum, (uint16_t *)ack_buff);
        if (ret > 0)
        {
            ret = ret << 1; // 寄存器个数转为字节数
        }
        break;

    default:
        ret = -2;
        break;
    }
    
    return ret;
}

dlt645_hd_t *dlt645_proto_init(dlt645_proto_t *if_info, dlt645_protocal_t dlt645_ver)
{
    dlt645_hd_t *hd = NULL;
    uart_par_t p_par = {0};

    switch (if_info->type)
    {
    case DLT645_TCP:
        hd = new_dlt645_tcp(if_info->dev_or_ip, if_info->port, dlt645_ver);
        if (hd == NULL)
        {
            proto_syslog(LOG_ERR, "new_dlt645_tcp error !!!");
        }
        break;

    case DLT645_UART:
    {

        int port = 0;
        char ip[32] = {0};
        if (is_bmser_BAU())
        {
            port = is_virtual_device_and_to_dup(if_info->dev_or_ip, ip);
            if (port > 0)
            {
                hd = new_dlt645_udp(ip, port, dlt645_ver);
                if (hd == NULL)
                {
                    proto_syslog(LOG_ERR, "new_dlt645_tcp error !!!");
                }
            }
        }
        if (port <= 0)
        {
            if (0 == extract_uart_par(if_info->dev_or_ip, &p_par))
            {
                if (dev_is_open(p_par.f_name))
                {
                    proto_syslog(LOG_WARNING, "warning!!!!! another process is using the %s", p_par.f_name);
                }
                hd = new_dlt645_uart(p_par.f_name, p_par.baud, p_par.data_bit, p_par.stop_bit, p_par.parity, dlt645_ver);
                if (hd == NULL)
                {
                    proto_syslog(LOG_ERR, "new_dlt645_uart error !!!");
                }
            }
        }
    }

    default:
        break;
    }

    if (hd != NULL)
    {
        if (0 != dlt645_connect(hd))
        {
            proto_syslog(LOG_ERR, "dlt645_connect error !!!");
            dlt645_free(hd);
            hd = NULL;
            return NULL;
        }
        dlt645_set_name(hd, if_info->dev_or_ip);
    }
    return hd;
}

int dlt645_proto_deinit(dlt645_hd_t *hd)
{
    dlt645_free(hd);
    return 0;
}

int dlt645_proto_read(dlt645_hd_t *hd, uint8_t *bcd_addr,int (*get_raw_data)(char *data,int len,void *contex),void *contex, dlt645_poll_info_t *it, void *buff, uint32_t max_len)
{
    return dlt645_read_data(hd, bcd_addr,get_raw_data,contex,it, buff, max_len);
}

//
ups_hd_t *ups_proto_init(ups_proto_t *if_info)
{
    ups_hd_t *hd = NULL;
    uart_par_t p_par = {0};

    switch (if_info->type)
    {
    case UPS_TCP:
        // hd = new_ups_tcp(if_info->dev_or_ip, if_info->port, dlt645_ver);
        // if (hd == NULL)
        // {
        //     proto_syslog(LOG_ERR, "new_dlt645_tcp error !!!");
        // }
        break;

    case UPS_UART:
        if (0 == extract_uart_par(if_info->dev_or_ip, &p_par))
        {
            if(dev_is_open(p_par.f_name))
            {
                proto_syslog(LOG_WARNING, "warning!!!!! another process is using the %s", p_par.f_name);
            }
            hd = new_ups_uart(p_par.f_name, p_par.baud, p_par.data_bit, p_par.stop_bit, p_par.parity);
        }

    default:
        break;
    }

    if (hd != NULL)
    {
        if (0 != ups_connect(hd))
        {
            proto_syslog(LOG_ERR, "ups connect error !!!");
            ups_free(hd);
            hd = NULL;
            return NULL;
        }
        ups_set_name(hd, if_info->dev_or_ip);
    }
    return hd;
}

custom_hd_t *custom_proto_init(custom_proto_t *if_info)
{
    custom_hd_t *hd = NULL;
    uart_par_t p_par = {0};

    switch (if_info->type)
    {
    case CUS_TCP:
        // hd = new_ups_tcp(if_info->dev_or_ip, if_info->port, dlt645_ver);
        // if (hd == NULL)
        // {
        //     proto_syslog(LOG_ERR, "new_dlt645_tcp error !!!");
        // }
        break;

    case CUS_UART:
        if (0 == extract_uart_par(if_info->dev_or_ip, &p_par))
        {
            if(dev_is_open(p_par.f_name))
            {
                proto_syslog(LOG_WARNING, "warning!!!!! another process is using the %s", p_par.f_name);
            }
            hd = new_custom_uart(p_par.f_name, p_par.baud, p_par.data_bit, p_par.stop_bit, p_par.parity);
        }
        break;
    default:
        break;
    }

    if (hd != NULL)
    {
        if (0 != custom_connect(hd))
        {
            proto_syslog(LOG_ERR, "custom connect error !!!");
            custom_free(hd);
            hd = NULL;
            return hd;
        }
        custom_set_name(hd, if_info->dev_or_ip);
    }
    return hd;
}

int custom_proto_deinit(custom_hd_t *hd)
{
    custom_free(hd);
    return 0;
}

ydt1363_hd_t *ydt1363_proto_init(ydt1363_proto_t *if_info)
{
    ydt1363_hd_t *hd = NULL;
    uart_par_t p_par = {0};

    switch (if_info->type)
    {
    case YDT1363_TCP:
        // hd = new_ups_tcp(if_info->dev_or_ip, if_info->port, dlt645_ver);
        // if (hd == NULL)
        // {
        //     proto_syslog(LOG_ERR, "new_dlt645_tcp error !!!");
        // }
        break;

    case YDT1363_UART:
        if (0 == extract_uart_par(if_info->dev_or_ip, &p_par))
        {
            if(dev_is_open(p_par.f_name))
            {
                proto_syslog(LOG_WARNING, "warning!!!!! another process is using the %s", p_par.f_name);
            }
            hd = new_ydt1363_uart(p_par.f_name, p_par.baud, p_par.data_bit, p_par.stop_bit, p_par.parity);
        }
        break;
    default:
        break;
    }

    if (hd != NULL)
    {
        if (0 != ydt1363_connect(hd))
        {
            proto_syslog(LOG_ERR, "ydt1363 connect error !!!");
            ydt1363_free(hd);
            hd = NULL;
            return hd;
        }
        ydt1363_set_name(hd, if_info->dev_or_ip);
    }
    return hd;
}

int ydt1363_proto_deinit(ydt1363_hd_t *hd)
{
    ydt1363_free(hd);
    return 0;
}

//
