#include <signal.h>

#include "upgrade.h"
#include "libubox/utils.h"
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

static error_code_msg_t upgrade_term_code_tab[] =
{
    {UPGRADE_TERM_UPGRADE_READY, "upgrade ready"},
    {UPGRADE_TERM_DOWNLOAD_FAIL, "download failed"},
    {UPGRADE_TERM_CHECK_FAIL, "check md5 failed"},
    {UPGRADE_TERM_NO_ONLINE_DEV, "no online devices of upgrade"},
    {UPGRADE_TERM_UPGRADE_TIMEOUT, "upgrade timeout failed"},
    {UPGRADE_TERM_EXECUTE_UPGRADE, "execute upgrade failed"},
    {UPGRADE_TERM_INIT_DEV_STATE_FAIL, "init devices state failed"},
    {UPGRADE_TERM_READ_FIRMWARE_FAIL, "read the firmware file failed"},
    {UPGRADE_TERM_CUT_BLOCK_FAIL, "cut block failed"},
    {UPGRADE_TERM_SEND_START_FAIL, "send start update fail"},
    {UPGRADE_TERM_SEND_BLOCK_FAIL, "send block fail"},
    {UPGRADE_TERM_ALREADY_RUN, "already run upgrade"},
    {UPGRADE_TERM_PARSE_FAILED, "json parse failed"},
    {UPGRADE_TERM_UPGRADE_ABORT, "upgrade abort"},
    {UPGRADE_TERM_INTERNAL_FAULT, "upgrade internal fault"},
    {UPGRADE_TERM_UPGRADE_DONE, "upgrade done"},
};

const char *dev_state_str[] =
{
    "DEV_INIT",         //初始状态
    "DEV_READY",            //设备已经ready
    "DEV_WAIT_START",       //发送了start update指令，等待设备ACK
    "DEV_STARTED",      //设备回复了start update的ACK
    "DEV_RECEIVING",        //开始向设备发送block
    "DEV_RECV_DONE",        //接收完成,收到设备最后一个block的ACK成功消息
    "DEV_CHKING",           //发送chk指令
    "DEV_CHK_OK",           //CHK的结果是OK
    "DEV_CHK_FAIL",     //CHK的结果是FAIL
    "DEV_UPGRADE_DONE",   //升级成功完成
    "DEV_TYPE_MISMATCH",  // 设备类型与firmware里面的类型不一致
    "DEV_UPGRADE_FAILED", //升级失败
    "DEV_NO_ACK",           //等待设备ACK超时
};

typedef struct pthread_upgrade_param_s
{
    upgrade_var_t *var;
    char *payload;
    char *SN;
    char *URL;
} pthread_upgrade_param_t;

static void upgrade_term_json_result(upgrade_var_t *var, char *sn, int mi, char *identifier, int code)
{
    char topic[TOPIC_MAX_LEN] = {0};
    char *err_msg = NULL;
    int i;
    cJSON *root = cJSON_CreateObject();
    if (root == NULL)
    {
        return;
    }
    cJSON_AddStringToObject(root, "sn", sn);
    cJSON_AddStringToObject(root, "identifier", identifier);
    cJSON_AddNumberToObject(root, "mi", mi);
    cJSON_AddNumberToObject(root, "timestamp", time(NULL));

    cJSON *tag_node = cJSON_CreateObject();
    cJSON_AddNumberToObject(tag_node, "Error_Code", code);
    for (i = 0; i < sizeof(upgrade_term_code_tab) / sizeof(upgrade_term_code_tab[0]); i++)
    {
        if ((int)upgrade_term_code_tab[i].code == code)
        {
            err_msg = upgrade_term_code_tab[i].msg;
            break;
        }
    }
    cJSON_AddStringToObject(tag_node, "Error_Msg", err_msg);

    cJSON_AddItemToObject(root, "tags", tag_node);

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

    snprintf(topic, TOPIC_MAX_LEN, "ipc/%s/%s/device/%s/data_filtered/service/%s", var->sn_str, "port", sn, "__upgrade");
    ipc_session_publish(var->session, topic, str, strlen(str));
    free(str);
}

static upgrade_term_code download_and_checksum(char *URL, char *MD5)
{
    upgrade_term_code err_code = UPGRADE_TERM_UPGRADE_READY;
    char *file;
    char firmware_path[256];
    int ret;

    ret = download_file("/tmp", URL, 10);
    if (ret != 0)
    {
        err_code = UPGRADE_TERM_DOWNLOAD_FAIL;
        dy_syslog(LOG_ERR, "upgrade nodes download failed");
        return err_code;
    }
    file = GetFileName(URL);
    sprintf(firmware_path, "/tmp/%s", file);
    ret = check_md5sum_file(firmware_path, MD5);
    if (ret != 0)
    {
        err_code = UPGRADE_TERM_CHECK_FAIL;
        dy_syslog(LOG_ERR, "upgrade nodes check md5 failed");
    }
    return err_code;
}

static char* generate_transparent_json(upgrade_var_t *var, node_cfg_t *node, template_cfg_t *ptemplate)
{
    char term_addr[16] = {0};
    cJSON *rs_data = cJSON_CreateObject();
    char topic[TOPIC_MAX_LEN] = {0};
    char *data_tmp = NULL;
    
    cJSON_AddStringToObject(rs_data, "data_b64", ""); //fill in scripte
    cJSON_AddNumberToObject(rs_data, "len", 0); // fill in script
    cJSON_AddNumberToObject(rs_data, "period", 0);
    cJSON_AddStringToObject(rs_data, "port", port_enum2char(node->port));
    cJSON_AddNumberToObject(rs_data, "mi", 0); //fill in script
    cJSON_AddStringToObject(rs_data, "src_identifier", "__transparent");
    cJSON_AddStringToObject(rs_data, "sn", node->sn);
    cJSON_AddStringToObject(rs_data, "dtu_sn", "");
    cJSON_AddNumberToObject(rs_data, "protocol", ptemplate->protocol);
    cJSON_AddNumberToObject(rs_data, "communication_timeout", ptemplate->communication_timeout);

    i82hexstr(node->term_addr, term_addr, 8);
    cJSON_AddStringToObject(rs_data, "term_addr", term_addr);
    cJSON_AddStringToObject(rs_data, "tcp_ip_addr", node->tcp_ip_addr);
    cJSON_AddNumberToObject(rs_data, "tcp_port", node->tcp_port);

    snprintf(topic, TOPIC_MAX_LEN, "ipc/%s/%s/device/%s/data/%s", var->sn_str, port_enum2char(node->port),
             node->sn, TOPIC_SEND_RGLT_SIGNAL_RAW_DATA);

    cJSON_AddStringToObject(rs_data, "topic", topic);

    // 以字符串的方式发送数据
    data_tmp = cJSON_Print(rs_data);

    return data_tmp;
}

static void *exec_upgrade_script(void *ptr)
{
    int i;
    int ret = 0;
    pthread_upgrade_param_t *param = (pthread_upgrade_param_t *)ptr;
    upgrade_var_t *var = param->var;
    node_cfg_t *node = NULL;

    pthread_detach(pthread_self());

    //遍历设备列表
    for (i = 0; i < var->nodes_cfg_table->node_cnt; i++)
    {
        if (strcmp(param->SN, var->nodes_cfg_table->node[i].sn) == 0)
        {
            char firmware_path[256];
            template_cfg_t *ptemplate = pp_find_template(var->template_table, var->nodes_cfg_table->node[i].template_id);
            dy_syslog(LOG_DEBUG, "script:%s", ptemplate->upgrade_script);
            if (ptemplate && strlen(ptemplate->upgrade_script) > 0)
            {
                notify_upgrade_run(var->session, &var->nodes_cfg_table->node[i]);
                node = &var->nodes_cfg_table->node[i];
                sprintf(firmware_path, "/tmp/%s", GetFileName(param->URL));
                if (strstr(ptemplate->upgrade_script, LUA_EXTEND))
                {
                    lua_State *lua;
                    char script_file[256] = {0};
                    char *pre_gen_json = NULL;

                    lua = luaL_newstate();
                    luaL_openlibs(lua);

                    if (luaL_dostring(lua, LUA_DDEFAULT_ENV))
                    {
                        dy_syslog(LOG_ERR, "luaL_loadstring %s error!!!", LUA_DDEFAULT_ENV);
                        ret = -1;
                    }

                    sprintf(script_file, "%s%s", PARSE_SCRIPT_DIR, ptemplate->upgrade_script);
                    /* luaL_dofile(lua, script_file); */
                    luaL_loadfile(lua, script_file);
                    lua_pcall(lua, 0, LUA_MULTRET, 0);
                    pre_gen_json = generate_transparent_json(var, &var->nodes_cfg_table->node[i], ptemplate);

                    lua_getglobal(lua, "upgrade_node");
                    lua_pushstring(lua, param->payload);
                    lua_pushstring(lua, var->sn_str);
                    lua_pushstring(lua, firmware_path);
                    lua_pushstring(lua, pre_gen_json);
                    dy_syslog(LOG_DEBUG, "calling upgrade_node function in script:%s", script_file);
                    ret = lua_pcall(lua, 4, 1, 0);
                    if (ret)
                    {
                        const char *err_msg = lua_tostring(lua, -1);
                        dy_syslog(LOG_ERR, "lua error:%s", err_msg);
                        lua_pop(lua, 1);
                        goto out;
                    }

                    if (!lua_isnumber(lua, -1))
                    {
                        dy_syslog(LOG_ERR, "function upgrade_node must return a number");
                    }

                    ret = lua_tointeger(lua, -1);
                    lua_pop(lua, 1);
                    if (ret != 0)
                    {
                        dy_syslog(LOG_ERR, "error_code:%d ", ret);
                    }
                    lua_pop(lua, 1);
                }
                else if (strstr(ptemplate->upgrade_script, PYTHON_EXTEND))
                {
                }
            }

            break;
        }
    }
out:
    notify_upgrade_done(var->session, node);
    var->state = UPGRADE_IDLE;
    free(param->SN);
    free(param->URL);
    free(param->payload);
    free(param);
    pthread_exit((void *)&ret);
}

void msg_upgrade_node(upgrade_var_t *var, ipc_msg_t *mqtt_msg)
{
    int err_code = 0;
    int mi = 0;
    cJSON *root = NULL;
    char *SN = NULL, *identifier = NULL;
    char *URL = NULL;
    char *MD5 = NULL;
    char *action = NULL;
    static pthread_t upgrade_thread = (pthread_t) - 1;

    root = cJSON_Parse(mqtt_msg->payload);
    if (!root)
    {
        err_code = UPGRADE_TERM_PARSE_FAILED;
        goto out;
    }
    GET_JSON_VALUE_INT(root, "mi", mi);
    GET_JSON_VALUE_DY_STRING(root, "sn", SN);
    GET_JSON_VALUE_DY_STRING(root, "identifier", identifier);
    GET_JSON_VALUE_DY_STRING(root, "URL", URL);
    GET_JSON_VALUE_DY_STRING(root, "Action", action);
    GET_JSON_VALUE_DY_STRING(root, "MD5", MD5);

    if (strcmp(action, "start") == 0)
    {
        if (var->state != UPGRADE_IDLE)
        {
            err_code = UPGRADE_TERM_ALREADY_RUN;
            dy_syslog(LOG_ERR, "upgrade nodes already run upgrade");
            goto out;

        }
        var->state = UPGRADING_NODE;

        /* 下载并验证firmware的MD5信息 */
        err_code = download_and_checksum(URL, MD5);
        if (err_code != 0)
        {
            dy_syslog(LOG_ERR, "download or check sum error, err_code:%d", err_code);
            goto out;
        }

        // MD5验证通过，调用脚本执行升级动作
        {
            pthread_upgrade_param_t *param = calloc(1, sizeof(pthread_upgrade_param_t));
            if (param == NULL)
            {
                err_code = UPGRADE_TERM_INTERNAL_FAULT;
                dy_syslog(LOG_ERR, "calloc failed");
                goto out;
            }
            param->var = var;
            param->SN = strdup(SN);
            param->URL = strdup(URL);
            param->payload = strdup(mqtt_msg->payload);

            err_code = pthread_create(&upgrade_thread, NULL, exec_upgrade_script, (void *)param);
            if (err_code)
            {
                dy_syslog(LOG_ERR, "pthread create failed:%d", err_code);
                err_code = UPGRADE_TERM_INTERNAL_FAULT;
                goto out;
            }
        }
    }
    else if (strcmp(action, "stop") == 0)
    {
        if (var->state == UPGRADING_NODE)
        {
            node_cfg_t *node = NULL;
            int i = 0;
            for (i = 0; i < var->nodes_cfg_table->node_cnt; i++)
            {
                if (strcmp(SN, var->nodes_cfg_table->node[i].sn) == 0)
                {
                    node = &var->nodes_cfg_table->node[i];
                }
            }
            pthread_kill(upgrade_thread, 0);
            var->state = UPGRADE_IDLE;
            notify_upgrade_done(var->session, node);
        }
        err_code = UPGRADE_TERM_UPGRADE_ABORT;
        goto out;
    }
out:
    upgrade_term_json_result(var, SN, mi, identifier, err_code);
    if (SN)
    {
        free(SN);
    }
    if (identifier)
    {
        free(identifier);
    }
    if (URL)
    {
        free(URL);
    }
    if (action)
    {
        free(action);
    }
    if (MD5)
    {
        free(MD5);
    }
    if (root)
    {
        cJSON_Delete(root);
    }
    if (err_code != 0)
    {
        var->state = UPGRADE_IDLE;
    }
}

