/****************HEADER FILE******************/
#include "rs485_common.h"
/**********************************************/


/******************INTERN MACRO*****************/
/**************************************************/


/**************INTERN FUNCTION DECLERATION**********/
static int rs485_port_cfg_parse(rs485_ports_cfg_t *cfgs, nodes_cfg_table_t * pnodcfg, const char *str);
/**************************************************/
static int del_space(char *src)
{
	char *pTmp = src;
	unsigned int iSpace = 0;
	
	while (*src != '\0')
	{
		if (*src != ' ')
		{
			*pTmp++ = *src;
		}
		else
		{
			iSpace++;
		}
		
		src++;
	}
	
	*pTmp = '\0';
	return iSpace;
}

/****************EXTERN FUNCTION******************/
void rs485_load_config(rs485_var_t *var)
{
    char *json_str = NULL;
    json_str = read_file_data(RS485_PORT_CFG);
    if (!json_str)
    {
        return ;
    }
    rs485_port_cfg_parse(&var->rs485_ports_cfg, var->nodes_cfg_table, json_str);
    free(json_str);
}


/**************************************************/


/****************INTERN FUNCTION******************/
static int rs485_port_special_cfg_parse(rs485_ports_cfg_t *cfgs, const char *str)
{
    char buff[128] = {0};
    int ret        = -1;
    int i = 0, j = 0;
    int size = 0;

    if (!str || !cfgs)
    {
        return -1;
    }

    cJSON *root = cJSON_Parse(str);
    if (!root)
    {
        return -1;
    }

    cJSON *nodes = cJSON_GetObjectItem(root, "rs485_cfg");
    if (!nodes)
    {
        ret = -1;
        goto __cleanup;
    }

    size = cJSON_GetArraySize(nodes);
    if (size > cfgs->port_num) {
        dbg_syslog(LOG_ERR, "Invalid number of special rs485 cfgs: %d", size);
        ret = -1;
        goto __cleanup;
    }

    for (i = 0; i < size ; i++)
    {
        cJSON *node = cJSON_GetArrayItem(nodes, i);
        if (!node)
        {
            ret = i + 1;
            goto __cleanup;
        }
        GET_JSON_VALUE_STRING(node, "port", buff);
        if (strlen(buff) > 0)
        {
        	gw_port_e port = port_char2enum(buff);
			for(j=0; j < size; j++)
			{
				rs485_port_cfg_t *cfg = &cfgs->rs485_port_cfg[j];
				if(port == cfg->port)
				{
					GET_JSON_VALUE_STRING(node, "frame_header", buff);
		            del_space(buff);
		            cfg->frame_header_len = strlen(buff) / 2;
		            if (cfg->frame_header_len > 0)
		            {
		                cfg->frame_header = calloc(1, cfg->frame_header_len);
		                str2hex((unsigned char *) cfg->frame_header, buff, cfg->frame_header_len);
		            }

		            GET_JSON_VALUE_STRING(node, "frame_tail", buff);
		            del_space(buff);
		            cfg->frame_tail_len = strlen(buff) / 2;
		            if (cfg->frame_tail_len > 0)
		            {
		                cfg->frame_tail = calloc(1, cfg->frame_tail_len);
		                str2hex((unsigned char *) cfg->frame_tail, buff, cfg->frame_tail_len);
		            }

					dbg_syslog(LOG_INFO, "port %d speed %d bits %d stop %d parity %d protocol %d frame_header_len %d frame_tail_len %d", cfg->port, cfg->speed, cfg->bits, cfg->stop, cfg->parity, cfg->protocol, cfg->frame_header_len, cfg->frame_tail_len);
				}
			}
        }
    }
    ret = 0;

__cleanup:
    cJSON_Delete(root);
    return ret;
}

void rs485_load_special_config(rs485_var_t *var)
{
    char *json_str = NULL;
    json_str = read_file_data("/app/config/serial_special_cfg.json");
    if (!json_str)
    {
        return ;
    }
    rs485_port_special_cfg_parse(&var->rs485_ports_cfg, json_str);
    free(json_str);
}

/**************************************************/

static int load_remote_rs485(rs485_port_cfg_t * pcfgs, nodes_cfg_table_t * pnodcfg, int numvirt)
{
    int idx, numv, count;
    int frame_tout = -1;

    numv = 0;
    if (pnodcfg == NULL)
        return 0;

    do { /* override ext_data setting via environment variables */
        const char * fout;
        fout = getenv("REMOTE_CHANNEL_FRAMEOUT");
        if (fout != NULL) {
            errno = 0;
            frame_tout = (int) strtol(fout, NULL, 0);
            if (errno != 0 || frame_tout < 0)
                frame_tout = -1;
            dbg_syslog(LOG_ERR, "Using env REMOTE_CHANNEL_FRAMEOUT: %d", frame_tout);
        }
    } while (0);

    count = pnodcfg->node_cnt;
    for (idx = 0; idx < count; ++idx) {
        int jdx, repeat;
        node_cfg_t * ncfg;
        rs485_port_cfg_t * pcfg;

        ncfg = &(pnodcfg->node[idx]);
        if (ncfg->port < REMOTE_CH_01 || ncfg->port > REMOTE_CH_20)
            continue;

        if (ncfg->tcp_port <= 0 || ncfg->tcp_port >= 65536 ||
            ncfg->tcp_ip_addr[0] == '\0') {
            dbg_syslog(LOG_ERR, "Error, invalid remote rs485 tcp_ip_addr/port => %s:%d",
                ncfg->tcp_ip_addr, ncfg->tcp_port);
            continue;
        }

        repeat = 0;
        for (jdx = 0; jdx < numv; ++jdx) {
            pcfg = &pcfgs[jdx];
            if (pcfg->port == ncfg->port) {
                repeat = 1;
                break;
            }
        }
        if (repeat)
            continue;

        pcfg = &pcfgs[numv];
        pcfg->port = ncfg->port;
        pcfg->speed = 9600;
        pcfg->stop = 1;
        pcfg->parity = 0;
        pcfg->bits = 8;
        pcfg->protocol = 0;
        pcfg->sleep_before_read = 0;
        pcfg->remote_port = ncfg->tcp_port;
        pcfg->frame_timeout = (frame_tout < 0) ? 200 : frame_tout; /* 默认值200ms */
        if (frame_tout < 0 && ncfg->ext_data && ncfg->ext_data[0]) {
            int timo = -1;
            cJSON * fout = NULL;
            cJSON * extd = NULL;

            /* extract frame_timeout setting from `ext_data */
            extd = cJSON_Parse(ncfg->ext_data);
            if (extd != NULL)
                fout = cJSON_GetObjectItem(extd, "frame_timeout");
            if (fout && cJSON_IsNumber(fout))
                timo = (int) fout->valuedouble;

            if (timo >= 0) {
                pcfg->frame_timeout = timo;
                dbg_syslog(LOG_INFO, "frame timeout for (%s:%d) channel-%d: %d",
                    ncfg->tcp_ip_addr, ncfg->tcp_port, (int) (ncfg->port - REMOTE_CH_01), timo);
            }
            if (extd)
                cJSON_Delete(extd);
        } while (0);
        strncpy(pcfg->remote_ipaddr, ncfg->tcp_ip_addr, sizeof(pcfg->remote_ipaddr) - 0x1);
        pcfg->disable = 0;

        numv++;
        if (numv >= numvirt)
            break;
    }
    return numv;
}

/****************INTERN FUNCTION******************/
static int rs485_port_cfg_parse(rs485_ports_cfg_t *cfgs, nodes_cfg_table_t * pnodcfg, const char *str)
{
    char buff[128] = {0};
    int ret        = -1;
    int i          = 0;
    int size       = 0;
    int num_remote = 0;
    rs485_port_cfg_t *cfg;

    if (!str || !cfgs)
    {
        return -1;
    }

    cJSON *root = cJSON_Parse(str);
    if (!root)
    {
        return -1;
    }

    GET_JSON_VALUE_INT(root, "mi", cfgs->mi);
    cJSON *nodes = cJSON_GetObjectItem(root, "rs485_cfg");
    if (!nodes)
    {
        ret = -1;
        goto __cleanup;
    }

    size = cJSON_GetArraySize(nodes);
    if (size <= 0 || size > TTYMXC_CNT) {
        dbg_syslog(LOG_ERR, "Invalid number of rs485 cfgs: %d", size);
        size = 0;
    }

    num_remote = REMOTE_CH_MAX_NUM;
    cfgs->port_num = (size + num_remote);
    cfgs->rs485_port_cfg = (rs485_port_cfg_t *) calloc(
        (size_t) (cfgs->port_num + 1),
        sizeof(rs485_port_cfg_t));
    if (cfgs->rs485_port_cfg == NULL) {
        dbg_syslog(LOG_ERR, "failed to allocate rs485_port_cfgs: %d", size);
        ret = -1;
        goto __cleanup;
    }

    /* disable all remote RS485 */
    for (i = 0; i < num_remote; ++i) {
        rs485_port_cfg_t * portc;
        portc = &(cfgs->rs485_port_cfg[size + i]);
        portc->disable = 1;
    }
    cfgs->port_num = size + load_remote_rs485(&(cfgs->rs485_port_cfg[size]), pnodcfg, num_remote);

    for (i = 0; i < size ; i++)
    {
        cJSON *node = cJSON_GetArrayItem(nodes, i);
        if (!node)
        {
            ret = i + 1;
            goto __cleanup;
        }
        cfg = &cfgs->rs485_port_cfg[i];
        GET_JSON_VALUE_STRING(node, "port", buff);
        if (strlen(buff) > 0)
        {
            cfg->port = port_char2enum(buff);
        }
        else
        {
            cfg->port = 0;
        }
        GET_JSON_VALUE_STRING(node, "protocol", buff);
        if (strlen(buff) > 0)
        {
            cfg->protocol = protocol_char2enum(buff);
        }
        else
        {
            cfg->protocol = 0;
        }
        GET_JSON_VALUE_STRING(node, "sub_template_id", cfg->sub_template_id);

        GET_JSON_VALUE_INT(node, "speed", cfg->speed);
        GET_JSON_VALUE_INT(node, "stop", cfg->stop);
        GET_JSON_VALUE_INT(node, "parity", cfg->parity);
        GET_JSON_VALUE_INT(node, "bits", cfg->bits);
        GET_JSON_VALUE_INT(node, "disable", cfg->disable);
        GET_JSON_VALUE_STRING(node, "bridging_port", cfg->bridging_port);

        cfg->remote_port = -1;
        cfg->remote_ipaddr[0] = '\0';
        cfg->msg_tail_len = 0;
        cfg->msg_head_len = 0;
        if (cJSON_GetObjectItem(node, "msg_head") != NULL)
        {
            GET_JSON_VALUE_STRING(node, "msg_head", buff);
            if (strlen(buff) > 0)
            {
                cfg->msg_head_len = strlen(buff)/2;
                str2hex((unsigned char *) cfg->msg_head, buff, cfg->msg_head_len);
            }
        }

        if (cJSON_GetObjectItem(node, "msg_tail") != NULL)
        {
            GET_JSON_VALUE_STRING(node, "msg_tail", buff);
            if (strlen(buff) > 1)
            {
                cfg->msg_tail_len = strlen(buff)/2;
                str2hex((unsigned char *) cfg->msg_tail, buff, cfg->msg_tail_len);
            }
        }
        if (cJSON_GetObjectItem(node, "frame_timeout") != NULL)
            GET_JSON_VALUE_INT(node, "frame_timeout", cfg->frame_timeout);
        else
            cfg->frame_timeout = 200; //默认值 200ms
        if (cJSON_GetObjectItem(node, "sleep_before_read") != NULL)
        {
            GET_JSON_VALUE_INT(node, "sleep_before_read", cfg->sleep_before_read);
        }
        GET_JSON_VALUE_INT(node, "sniffer_mode", cfg->sniffer_mode);
        dbg_syslog(LOG_INFO, "cnt %d %d port %d speed %d bits %d stop %d parity %d protocol %d sub_template_id %s disable %d sleep_before_read %d sniffer_mode %d bridging_port %s", size, i, cfg->port, cfg->speed, cfg->bits, cfg->stop, cfg->parity, cfg->protocol, cfg->sub_template_id, cfg->disable, cfg->sleep_before_read, cfg->sniffer_mode, cfg->bridging_port);
    }
    ret = 0;

__cleanup:
    cJSON_Delete(root);
    return ret;
}
/**************************************************/

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
    {
        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 "/dev/tty_unknown";
}

int set_gpio_for_port(const char *product_name, gw_port_e port)
{
    int gpio = -1;

    if (strcmp(product_name, PRODUCT_WOOLINK_3002_W) == 0 || strcmp(product_name, PRODUCT_WooLink1002_W) == 0)
    {
        switch (port)
        {
        case RS485_1:
            gpio = 3;
            break;
        case RS485_2:
        case RS232_1:
            gpio = 0;
            break;
        default:
            break;
        }
    }

    if (gpio != -1)
    {
        char buf[256] = {0};
        char tty_name[8];
        const char *dev_name = get_tty_from_port(port);

        snprintf(buf, 256, "echo %d > /sys/class/gpio/export", gpio);
        system(buf);
        snprintf(buf, 256, "echo out > /sys/class/gpio/gpio%d/direction", gpio);
        system(buf);
        snprintf(buf, 256, "echo 0 > /sys/class/gpio/gpio%d/value", gpio);
        system(buf);

        sscanf(dev_name, "/%*[^/]/%s", tty_name);
        snprintf(buf, 256, "echo \"%s %d\" > /proc/uart_gpio", tty_name, gpio);
        system(buf);
    }

    return gpio;
}

int rs485_port_map(int rs485_port)
{
    int port = 0;

    switch (rs485_port)
    {
    case RS485_1:
        port = 0;
        break;
    case RS485_2:
        port = 1;
        break;
    case RS485_3:
        port = 2;
        break;
    case RS485_4:
        port = 3;
        break;

    case RS485_5:
        port = 4;
        break;
    case RS485_6:
        port = 5;
        break;
    case RS485_7:
        port = 6;
        break;
    case RS485_8:
        port = 7;
        break;

#if 0
    case REMOTE_CH_01 .. REMOTE_CH_20:
        port = 0x8 + (int) (rs485_port - REMOTE_CH_01);
        break;
#else
    case REMOTE_CH_01:
        port = 8;
        break;

    case REMOTE_CH_02:
        port = 9;
        break;

    case REMOTE_CH_03:
        port = 10;
        break;

    case REMOTE_CH_04:
        port = 11;
        break;

    case REMOTE_CH_05:
        port = 12;
        break;

    case REMOTE_CH_06:
        port = 13;
        break;

    case REMOTE_CH_07:
        port = 14;
        break;

    case REMOTE_CH_08:
        port = 15;
        break;

    case REMOTE_CH_09:
        port = 16;
        break;

    case REMOTE_CH_10:
        port = 17;
        break;

    case REMOTE_CH_11:
        port = 18;
        break;

    case REMOTE_CH_12:
        port = 19;
        break;

    case REMOTE_CH_13:
        port = 20;
        break;

    case REMOTE_CH_14:
        port = 21;
        break;

    case REMOTE_CH_15:
        port = 22;
        break;

    case REMOTE_CH_16:
        port = 23;
        break;

    case REMOTE_CH_17:
        port = 24;
        break;

    case REMOTE_CH_18:
        port = 25;
        break;

    case REMOTE_CH_19:
        port = 26;
        break;

    case REMOTE_CH_20:
        port = 27;
        break;

    case RS485_9:
        port = 28;
        break;
    case RS485_10:
        port = 29;
        break;
    case RS485_11:
        port = 30;
        break;
    case RS485_12:
        port = 31;
        break;
#endif
    }

    return port;
}

int rs485_port_remap(int port)
{
    int rs485_port = RS485_1;

    switch (port)
    {
    case 0:
        rs485_port = RS485_1;
        break;
    case 1:
        rs485_port = RS485_2;
        break;
    case 2:
        rs485_port = RS485_3;
        break;
    case 3:
        rs485_port = RS485_4;
        break;

    case 4:
        rs485_port = RS485_5;
        break;
    case 5:
        rs485_port = RS485_6;
        break;
    case 6:
        rs485_port = RS485_7;
        break;
    case 7:
        rs485_port = RS485_8;
        break;

#if 0
    case 8 .. 27:
        rs485_port = REMOTE_CH_01 + (port - 8);
        break;
#else
    case 8:
        rs485_port = REMOTE_CH_01;
        break;
    case 9:
        rs485_port = REMOTE_CH_02;
        break;
    case 10:
        rs485_port = REMOTE_CH_03;
        break;
    case 11:
        rs485_port = REMOTE_CH_04;
        break;
    case 12:
        rs485_port = REMOTE_CH_05;
        break;
    case 13:
        rs485_port = REMOTE_CH_06;
        break;
    case 14:
        rs485_port = REMOTE_CH_07;
        break;
    case 15:
        rs485_port = REMOTE_CH_08;
        break;
    case 16:
        rs485_port = REMOTE_CH_09;
        break;
    case 17:
        rs485_port = REMOTE_CH_10;
        break;
    case 18:
        rs485_port = REMOTE_CH_11;
        break;
    case 19:
        rs485_port = REMOTE_CH_12;
        break;
    case 20:
        rs485_port = REMOTE_CH_13;
        break;
    case 21:
        rs485_port = REMOTE_CH_14;
        break;
    case 22:
        rs485_port = REMOTE_CH_15;
        break;
    case 23:
        rs485_port = REMOTE_CH_16;
        break;
    case 24:
        rs485_port = REMOTE_CH_17;
        break;
    case 25:
        rs485_port = REMOTE_CH_18;
        break;
    case 26:
        rs485_port = REMOTE_CH_19;
        break;
    case 27:
        rs485_port = REMOTE_CH_20;
        break;

    case 28:
        rs485_port = RS485_9;
        break;
    case 29:
        rs485_port = RS485_10;
        break;
    case 30:
        rs485_port = RS485_11;
        break;
    case 31:
        rs485_port = RS485_12;
        break;
#endif
    }

    return rs485_port;
}
