#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>

#include "common.h"

int data_report_poll(yuzhi_var_t *var)
{
    TIMER_CONFIRM(var->report_timer);
    char *data = NULL;
    int ret = -1;
    cJSON *reply = NULL;
    cJSON *items = NULL;
    cJSON *item = NULL;
    char *tmp = NULL;
    int i;
    int j;

    reply = cJSON_CreateObject();
    if (reply == NULL)
    {
        goto out;
    }
    cJSON_AddStringToObject(reply, "type", "changeall");
    items = cJSON_CreateArray();

    for (i = 0; i < var->prod_cnt; i++)
    {
        yuzhi_prod_t *prod = &var->prods[i];
        if (prod->plctype == 5)
        {
            s7_create_data_report(var, prod, items);
        }
    }

    cJSON_AddItemToObject(reply, "watchs", items);
    if (cJSON_GetArraySize(items) > 0)
    {
        tmp = cJSON_Print(reply);
        dy_syslog(LOG_DEBUG, "report:%s", tmp);
        printf("report:%s\n", tmp);
        dy_mqtt_session_publish(var->session, YUZHI_REPORTALL, tmp, strlen(tmp));
    }

out:
    free(tmp);
    cJSON_Delete(reply);
    return ret;
}

static int hb_send(yuzhi_var_t *var)
{
    char *str = NULL;
    char *buf = NULL;
    char exec_cmd[256] = {0};
    char *lbsinfo = NULL;
    cJSON *root = cJSON_CreateObject();
    if (root == NULL)
    {
        return -1;
    }

    cJSON_AddStringToObject(root, "serialno", var->sn_str);
    system_reply("factory get | awk -F'=' '/^MAC=/{print $2}'", &buf);
    if (buf != NULL)
    {
        cJSON_AddStringToObject(root, "macaddr", buf);
        free(buf);
    }

    sprintf(exec_cmd, "%s%s", NET_BASH_PATH, "fetch_port_status.sh");
    system_reply(exec_cmd, &buf);
    if (buf != NULL)
    {
        cJSON *net = NULL;
        char tmp[64];
        net = cJSON_Parse(buf);

        cJSON *lan = cJSON_GetObjectItem(net, "lan");
        if (lan != NULL)
        {
            GET_JSON_VALUE_STRING(lan, "subnet", tmp);
            char *ip = strtok(tmp, "/");
            char *mask = strtok(NULL, "/");
            int m = atoi(mask);
            unsigned int ip_num = 0;

            for (int i = m, j = 31; i > 0; i--, j--)
            {
                ip_num += (1 << j);
            }
            sprintf(tmp, "%hhu.%hhu.%hhu.%hhu", (ip_num >> 24) & 0xff, (ip_num >> 16) & 0xff, (ip_num >> 8) & 0xff, ip_num & 0xff);

            cJSON_AddStringToObject(root, "lanip", ip);
            cJSON_AddStringToObject(root, "lanmask", tmp);
        }
        cJSON *wan = cJSON_GetObjectItem(net, "wan");
        if (wan != NULL)
        {
            GET_JSON_VALUE_STRING(wan, "subnet", tmp);
            char *ip = strtok(tmp, "/");
            char *mask = strtok(NULL, "/");
            int m = atoi(mask);
            unsigned int ip_num = 0;

            for (int i = m, j = 31; i > 0; i--, j--)
            {
                ip_num += (1 << j);
            }
            sprintf(tmp, "%hhu.%hhu.%hhu.%hhu", (ip_num >> 24) & 0xff, (ip_num >> 16) & 0xff, (ip_num >> 8) & 0xff, ip_num & 0xff);

            cJSON_AddStringToObject(root, "wanip", ip);
            cJSON_AddStringToObject(root, "wanmask", tmp);
        }
        cJSON_Delete(net);
        free(buf);
    }

    cJSON_AddStringToObject(root, "platform", "Lnxall Gateway");
    cJSON_AddStringToObject(root, "remoteserviceenabled", "0");

    sprintf(exec_cmd, "%s%s", NET_BASH_PATH, "fetch_lbs.sh");
    system_reply(exec_cmd, &lbsinfo);
    if (lbsinfo == NULL)
    {
        lbsinfo = "120.127701,30.284924";
    }

    cJSON_AddStringToObject(root, "lbsinfo", lbsinfo);

    str = (cJSON_Print(root));
    cJSON_Delete(root);

    dy_mqtt_session_publish(var->session, YUZHI_HB, str, strlen(str));
    free(str);
    return 0;
}

static int plc_status_send(yuzhi_var_t *var)
{
    yuzhi_prod_t *prod = NULL;
    int i;
    char *str = NULL;

    for (i = 0; i < var->prod_cnt; i++)
    {
        prod = &var->prods[i];
        if (prod->plctype == 5)
        {
            int status = 0;
            char buf[8];
            cJSON *root = cJSON_CreateObject();
            if (root == NULL)
            {
                return -1;
            }

            cJSON_AddStringToObject(root, "devicekey", prod->devicekey);
            cJSON_AddStringToObject(root, "serialno", var->sn_str);
            if (prod->cpuStatus == 0x00)
            {
                status = -1;
            }
            else if (prod->cpuStatus == 0x08)
            {
                status = 2;
            }
            else if (prod->cpuStatus == 0x04)
            {
                status = 0;
            }
            sprintf(buf, "%d", status);
            cJSON_AddStringToObject(root, "status", buf);
            str = (cJSON_Print(root));
            cJSON_Delete(root);
            dy_syslog(LOG_DEBUG, "status:%s", str);

            dy_mqtt_session_publish(var->session, YUZHI_PLCSTATUS, str, strlen(str));
            free(str);
        }
    }
}

static int status_poll(yuzhi_var_t *var)
{
    TIMER_CONFIRM(var->status_poll_timer);
    hb_send(var);
    plc_status_send(var);

    return 0;
}

static void yuzhi_loop(yuzhi_var_t *var)
{
    int ret = -1, maxfd;
    fd_set rset;
    struct timeval timeout;

    while (1)
    {
        SELECT_INIT();
        timeout.tv_usec = 0;
        timeout.tv_sec = 2;
        SELECT_ADD_FD(var->status_poll_timer);
        SELECT_ADD_FD(var->report_timer);
        ret = select(maxfd + 1, &rset, 0, 0, &timeout);
        if (ret < 0)
        {
            dy_syslog(LOG_DEBUG, "errno %d\n", errno);
            if (errno == EINTR)
            {
                continue;
            }
            else
            {
                break;
            }
        }
        else if (ret > 0)
        {
            if (var->status_poll_timer > 0 && FD_ISSET(var->status_poll_timer, &rset))
            {
                FD_CLR(var->status_poll_timer, &rset);
                status_poll(var);
            }
            if (var->report_timer > 0 && FD_ISSET(var->report_timer, &rset))
            {
                FD_CLR(var->report_timer, &rset);
                data_report_poll(var);
            }
        }
    }
}

static int yuzhi_load_dev_cfg(yuzhi_var_t *var)
{
    char *data = NULL;
    int ret = -1;
    cJSON *root = NULL;
    cJSON *items = NULL;
    cJSON *item = NULL;
    int prod_cnt = 0;
    int i = 0;

    data = read_file_data(YUZHI_DEV_CFG);
    if (data == NULL)
    {
        goto out;
    }

    root = cJSON_Parse(data);
    if (!root)
    {
        goto out;
    }

    items = cJSON_GetObjectItem(root, "products");
    if (!items)
    {
        dy_syslog(LOG_ERR, "get products failed");
        goto out;
    }

    prod_cnt = cJSON_GetArraySize(items);
    if (prod_cnt == 0)
    {
        ret = 0;
        dy_syslog(LOG_INFO, "No product Channel");
        var->prod_cnt = prod_cnt;
        goto out;
    }
    var->prod_cnt = prod_cnt;

    var->prods = calloc(prod_cnt, sizeof(yuzhi_prod_t));
    if (var->prods == NULL)
    {
        dy_syslog(LOG_ERR, "alloc failed");
        goto out;
    }

    /*
    {
        "products": [{
            "connecttype": "3",
            "plcmodel": "SiemensPLC",
            "plctype": "5",
            "ipaddress": "192.168.200.220",
            "macaddr": "00:1C:06:27:8B:7E",
            "port": "0",
            "netaddr": "1"
        }]
    }
    */
    for (i = 0; i < prod_cnt; i++)
    {
        char buf[32];
        int j = 0;
        int k = 0;

        item = cJSON_GetArrayItem(items, i);
        yuzhi_prod_t *prod = &var->prods[i];

        GET_JSON_VALUE_STRING(item, "connecttype", buf);
        sscanf(buf, "%d", &prod->connecttype);
        GET_JSON_VALUE_STRING(item, "plcmodel", prod->plcmodel);
        GET_JSON_VALUE_STRING(item, "ipaddress", prod->ip);
        GET_JSON_VALUE_STRING(item, "plctype", buf);
        sscanf(buf, "%d", &prod->plctype);
        GET_JSON_VALUE_STRING(item, "macaddr", prod->mac);
        GET_JSON_VALUE_STRING(item, "port", buf);
        sscanf(buf, "%d", &prod->port);
        GET_JSON_VALUE_STRING(item, "netaddr", buf);
        sscanf(buf, "%d", &prod->netaddr);

        if (prod->connecttype == 3)
        {
            // 以太网
            for (j = 0; j < strlen(prod->mac); j++)
            {
                char c = prod->mac[j];
                if (c >= '0' && c <= '9')
                {
                    prod->devicekey[k] = c;
                    k++;
                }
                else if (c >= 'A' && c <= 'F')
                {
                    prod->devicekey[k] = c;
                    k++;
                }
                else if (c >= 'a' && c <= 'f')
                {
                    prod->devicekey[k] = c - 'a' + 'A';
                    k++;
                }
            }
        }

        prod->var = var;
    }

out:
    free(data);
    cJSON_Delete(root);
    return ret;
}

static int yuzhi_load_cfg(yuzhi_var_t *var)
{
    char *data = NULL;
    int ret = -1;
    cJSON *root = NULL;
    cJSON *login = NULL;
    cJSON *prod_login = NULL;

    data = read_file_data(YUZHI_CFG_FILE);
    if (data == NULL)
    {
        goto out;
    }

    root = cJSON_Parse(data);
    if (!root)
    {
        goto out;
    }

    login = cJSON_GetObjectItem(root, "model_login");
    if (login)
    {
        GET_JSON_VALUE_DY_STRING(login, "url", var->login_url);
        GET_JSON_VALUE_DY_STRING(login, "member", var->login_member);
        GET_JSON_VALUE_DY_STRING(login, "password", var->login_password);
    }

    prod_login = cJSON_GetObjectItem(root, "product_login");
    if (prod_login)
    {
        GET_JSON_VALUE_DY_STRING(prod_login, "url", var->prod_login_url);
    }

    ret = yuzhi_load_dev_cfg(var);
out:
    free(data);
    cJSON_Delete(root);
    return ret;
}

static int yuzhi_handle_change_all(yuzhi_var_t *var, mqtt_message_t *mqtt_msg)
{
    char filename[128] = {0};

    cJSON *root = cJSON_Parse(mqtt_msg->payload);
    if (root != NULL)
    {
        char devkey[32] = {0};

        GET_JSON_VALUE_STRING(root, "devicekey", devkey);
        snprintf(filename, 128, "%s%s.json", YUZHI_DATA_CFG, devkey);
        write_file_data(filename, mqtt_msg->payload, mqtt_msg->payloadLen);
    }
    cJSON_Delete(root);
    system("sync;sleep 1;/etc/init.d/yuzhi restart");

    return 0;
}

static int yuzhi_handle_edit_all(yuzhi_var_t *var, mqtt_message_t *mqtt_msg)
{
    cJSON *root = cJSON_Parse(mqtt_msg->payload);
    if (root != NULL)
    {
        char devkey[32] = {0};
        int i;

        GET_JSON_VALUE_STRING(root, "devicekey", devkey);

        for (i = 0; i < var->prod_cnt; i++)
        {
            yuzhi_prod_t *prod = &var->prods[i];
            if (strcmp(prod->devicekey, devkey) == 0)
            {
                cJSON *watches = cJSON_GetObjectItem(root, "watchdatas");
                if (watches)
                {
                    if (prod->plctype == 5)
                    {
                        //Siemens
                        yuzhi_edit_s7_data(var, prod, watches);
                    }
                }
            }
        }
    }
    cJSON_Delete(root);
}

static int yuzhi_mqtt_handle_recv_msg(void *obj, mqtt_message_t *mqtt_msg)
{
    yuzhi_var_t *var = (yuzhi_var_t *)obj;

    dy_syslog(LOG_DEBUG, " MQTT client: received MQTT topic:%s payload length:%d",
              mqtt_msg->topic, mqtt_msg->payloadLen);
    if (strstr(mqtt_msg->topic, YUZHI_CHANGEALL))
    {
        yuzhi_handle_change_all(var, mqtt_msg);
    }
    else if (strstr(mqtt_msg->topic, YUZHI_EDITALL))
    {
        yuzhi_handle_edit_all(var, mqtt_msg);
    }
}

static int yuzhi_mqtt_handle_internal_recv_msg(void *obj, mqtt_message_t *mqtt_msg)
{
    yuzhi_var_t *var = (yuzhi_var_t *)obj;

    dy_syslog(LOG_DEBUG, " MQTT client: received MQTT topic:%s payload length:%d",
              mqtt_msg->topic, mqtt_msg->payloadLen);

    cJSON *reply = cJSON_Parse(mqtt_msg->payload);
    if (!reply)
    {
        return -1;
    }
    cJSON_AddNumberToObject(reply, "timestamp", time(NULL));
    cJSON_AddStringToObject(reply, "serialno", var->sn_str);

    char *tmp = cJSON_Print(reply);
    dy_syslog(LOG_DEBUG, "report:%s", tmp);
    dy_mqtt_session_publish(var->session, YUZHI_REPORTALL, tmp, strlen(tmp));
    free(tmp);
}

static int yuzhi_subscribe_all(yuzhi_var_t *var)
{
    mqtt_session_t *mqtt_session = var->session;
    char topic[TOPIC_MAX_LEN] = {0};

    snprintf(topic, TOPIC_MAX_LEN, "%s%s", YUZHI_CHANGEALL, var->sn_str);
    dy_mqtt_session_subscribe(mqtt_session, topic);
    snprintf(topic, TOPIC_MAX_LEN, "%s%s", YUZHI_EDITALL, var->sn_str);
    dy_mqtt_session_subscribe(mqtt_session, topic);
}

static int yuzhi_prod_login(yuzhi_var_t *var)
{
    char cmd[4096] = {0};
    int ret = 0;
    int i = 0;
    char *buff = NULL;
    cJSON *root = NULL;
    // curl -X GET 'https://www.jzt2025.com/device/initProducts?products=\[\{
    //"connecttype":"3","plcmodel":"SiemensPLC","plctype":"5","ipaddress":"192.168.200.220","macaddr":"00:1C:06:27:8B:7E","port":"0","netaddr":"1"\}\]
    //&serialno=218804032222'

    ret += snprintf(cmd + ret, 4096 - ret, "curl -s -k -X GET '%s?products=\\[", var->prod_login_url);
    for (i = 0; i < var->prod_cnt; i++)
    {
        yuzhi_prod_t *prod = &var->prods[i];
        if (i != 0)
        {
            ret += snprintf(cmd + ret, 4096 - ret, ",");
        }
        ret += snprintf(cmd + ret, 4096 - ret, "\\{\"connecttype\":\"%d\",\"plcmodel\":\"%s\",\"plctype\":\"%d\",\"ipaddress\":\"%s\",\"macaddr\":\"%s\",\"port\":\"%d\",\"netaddr\":\"%d\"\\}",
                        prod->connecttype, prod->plcmodel, prod->plctype, prod->ip, prod->mac, prod->port, prod->netaddr);
    }
    ret += snprintf(cmd + ret, 4096 - ret, "\\]&serialno=%s'", var->sn_str);

    dy_syslog(LOG_DEBUG, "cmd:%s", cmd);
    system_reply(cmd, &buff);

    if (buff != NULL)
    {
        dy_syslog(LOG_DEBUG, buff);
        root = cJSON_Parse(buff);
        if (!root)
        {
            dy_syslog(LOG_ERR, "buff is not a json");
            goto out;
        }
    }
    else
    {
        dy_syslog(LOG_ERR, "buff is empty, cmd:%s", cmd);
        ret = -1;
        goto out;
    }

out:
    free(buff);
    cJSON_Delete(root);
    return ret;
}

static int yuzhi_login(yuzhi_var_t *var)
{
    char cmd[1024] = {0};
    char *buff = NULL;
    int ret = -1;
    cJSON *root = NULL;
    char *host = NULL;
    int port;
    char *user = NULL;
    char *pass = NULL;
    char *lbsinfo = NULL;

    sprintf(cmd, "%s%s", NET_BASH_PATH, "fetch_lbs.sh");
    system_reply(cmd, &lbsinfo);
    if (lbsinfo == NULL)
    {
        lbsinfo = "120.127701,30.284924";
    }

    sprintf(cmd, "curl -s -k -X GET '%s?info=\\{\"serialno\":\"%s\",\"lbsinfo\":\"%s\"\\}&member=%s&password=%s'", var->login_url, var->sn_str, lbsinfo, var->login_member, var->login_password);
    dy_syslog(LOG_DEBUG, "%s", cmd);

    // {"code":200,"message":null,"host":"mqtt.jzt2025.com","port":1883,"username":"jzt2025","password":"!qaZ@wsX3edC4rfV"}
    system_reply(cmd, &buff);

    if (buff != NULL)
    {
        int code;
        dy_syslog(LOG_DEBUG, buff);
        root = cJSON_Parse(buff);
        if (!root)
        {
            dy_syslog(LOG_ERR, "buff is not a json");
            goto out;
        }

        GET_JSON_VALUE_INT(root, "code", code);

        if (code == 200)
        {
            GET_JSON_VALUE_DY_STRING(root, "host", host);
            GET_JSON_VALUE_INT(root, "port", port);
            GET_JSON_VALUE_DY_STRING(root, "username", user);
            GET_JSON_VALUE_DY_STRING(root, "password", pass);

            char clientId[MAX_CLIENT_ID_LEN] = {0};

            snprintf(clientId, MAX_CLIENT_ID_LEN, "lnxall_%s", var->sn_str);
            var->session = dy_mqtt_session_new(clientId);
            if (var->session == NULL)
            {
                dy_syslog(LOG_ERR, "create mqtt session failed");
                goto out;
            }

            dy_mqtt_session_set_address(var->session, host, port, user, pass);
            dy_mqtt_session_set_opts(var->session, DEFAULT_IPC_QOS, KEEP_ALIVE_MAX);
            dy_mqtt_session_set_callbacks(var->session, yuzhi_mqtt_handle_recv_msg, NULL);

            dy_mqtt_session_init(var->session, (void *)var);
            yuzhi_subscribe_all(var);

            ret = yuzhi_prod_login(var);
        }
    }
    else
    {
        dy_syslog(LOG_ERR, "buff is empty, cmd:%s", cmd);
        ret = -1;
        goto out;
    }

out:
    free(buff);
    free(host);
    free(user);
    free(pass);
    cJSON_Delete(root);
    return ret;
}

// 建立与内部broker之间的MQTT连接
static int yuzhi_mqtt_client_init(yuzhi_var_t *var)
{
    char clientId[MAX_CLIENT_ID_LEN] = {0};
    char topic[TOPIC_MAX_LEN] = {0};

    snprintf(clientId, MAX_CLIENT_ID_LEN, "INT_yuzhi_%s", var->sn_str);
    var->session_internal = dy_mqtt_session_new(clientId);
    if (var->session_internal == NULL)
        return -1;

    dy_mqtt_session_set_address(var->session_internal, INTERNAL_BROKER_ADDR, INTERNAL_BROKER_PORT, INTERNAL_BROKER_USER, INTERNAL_BROKER_PASS);
    dy_mqtt_session_set_opts(var->session_internal, DEFAULT_IPC_QOS, KEEP_ALIVE_MAX);
    dy_mqtt_session_set_callbacks(var->session_internal, yuzhi_mqtt_handle_internal_recv_msg, NULL);

    dy_mqtt_session_init(var->session_internal, (void *)var);

    snprintf(topic, TOPIC_MAX_LEN, "yuzhi/#");
    dy_mqtt_session_subscribe(var->session_internal, topic);
}

static int yuzhi_creat_conn(yuzhi_var_t *var)
{
    int i = 0;
    yuzhi_prod_t *prod = NULL;
    pthread_t s7_read_thread;
    int has_s7 = 0;

    for (i = 0; i < var->prod_cnt; i++)
    {
        prod = &var->prods[i];
        if (prod->plctype == 5)
        {
            //Siemens
            prod->s7_plc_client = calloc(1, sizeof(s7_plc_client_t));
            prod->s7_plc_client->ip_addr = prod->ip;
            prod->s7_plc_client->rack = prod->port;
            prod->s7_plc_client->slot = prod->netaddr;
            prod->s7_plc_client->prod = prod;
            s7_plc_create_conn(prod->s7_plc_client);
            yuzhi_load_s7_data_cfg(var, prod);
            has_s7++;
        }
    }

    if (has_s7 > 0)
    {
        pthread_create(&s7_read_thread, NULL, s7_run_loop, (void *)var);
    }
}

static int yuzhi_init(yuzhi_var_t *var)
{
    int ret = 0;

    get_board_sn(var->sn_str);
    yuzhi_mqtt_client_init(var);
    yuzhi_load_cfg(var);
    while (1)
    {
        ret = yuzhi_login(var);
        if (ret == -1)
        {
            dy_syslog(LOG_ERR, "LOGIN failed");
            sleep(2);
        }
        else
        {
            dy_syslog(LOG_DEBUG, "Login success");
            break;
        }
    }

    yuzhi_creat_conn(var);
    var->status_poll_timer = my_timer_create();
    if (var->status_poll_timer <= 0)
    {
        dy_syslog(LOG_INFO, "[ERR] create status check timer fail, %d", var->status_poll_timer);
    }

    if (var->status_poll_timer > 0)
    {
        my_timer_set(var->status_poll_timer, 10, 60 * 1000);
    }

    var->report_timer = my_timer_create();
    if (var->report_timer <= 0)
    {
        dy_syslog(LOG_INFO, "[ERR] create status check timer fail, %d", var->report_timer);
    }

    if (var->report_timer > 0)
    {
        my_timer_set(var->report_timer, 2, 1000);
    }

    dy_syslog(LOG_INFO, "init done");

    return 0;
}

int main(int argc, char *argv[])
{
    yuzhi_var_t var = {0};

    openlog("yuzhi", LOG_PID, LOG_DAEMON);

    memset(&var, 0, sizeof(var));

    yuzhi_init(&var);
    yuzhi_loop(&var);

    return 0;
}
