#include <stdio.h>
#include <sys/resource.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/timerfd.h>
#include <errno.h>
#include <syslog.h>
#include <string.h>

#include "dy_common.h"
#include "cJSON.h"
#include "dy_pp.h"

int object_property_info_parse(object_property_table_t **p_tab, cJSON *item)
{
    int cnt = 0;
    object_property_table_t *tab = NULL;
    int i;
    cJSON *tag = NULL;

    if (!p_tab)
    {
        dy_syslog(LOG_ERR, "Invalid parameters");
        return -1;
    }

    if (item)
    {
        cnt = cJSON_GetArraySize(item);
    }
    tab = calloc(1, sizeof(object_property_table_t) + cnt * sizeof(object_property_cfg_t));
    if (tab == NULL)
    {
        dy_syslog(LOG_ERR, "malloc failed, cnt:%d", cnt);
        return -1;
    }
    tab->propertyCnt = cnt;

    for (i = 0; i < cnt; i++)
    {
        tag = cJSON_GetArrayItem(item, i);
        GET_JSON_VALUE_STRING(tag, "identifier", tab->property[i].identifier);
        GET_JSON_VALUE_DY_STRING(tag, "desc", tab->property[i].desc);
        GET_JSON_VALUE_DY_STRING(tag, "ext_data", tab->property[i].ext_data);
        cJSON *dataType = cJSON_GetObjectItem(tag, "dataType");
        if (dataType) {
            char buff[128];

            buff[0] = '\0'; buff[1] = '\0';
            GET_JSON_VALUE_STRING(dataType, "type", buff);
            tab->property[i].raw_data_type = object_datatype_char2enum(buff);
            cJSON *limit = cJSON_GetObjectItem(dataType, "specs");
            if (limit) {
                buff[0] = '0'; buff[1] = '\0';
                GET_JSON_VALUE_STRING(limit, "max", buff);
                tab->property[i].max = (double) atoi(buff);

                buff[0] = '0'; buff[1] = '\0';
                GET_JSON_VALUE_STRING(limit, "min", buff);
                tab->property[i].min = (double) atoi(buff);
            }
        }
    }

    tab->propertyCnt = cnt;
    *p_tab = tab;
    return 0;
}

static int object_property_in_service_parse(object_property_table_t **p_tab, cJSON *item, int output)
{
    if (!p_tab)
    {
        dy_syslog(LOG_ERR, "Invalid parameters");
        return -1;
    }

    object_property_table_t *table = NULL;
    int cnt = 0;
    int m;

    if (item)
    {
        cnt = cJSON_GetArraySize(item);
    }
    table = calloc(1, sizeof(object_property_table_t) + cnt * sizeof(object_property_cfg_t));
    if (table == NULL)
    {
        dy_syslog(LOG_ERR, "malloc failed, cnt:%d", cnt);
        return -1;
    }

    for (m = 0; m < cnt; m++)
    {
        cJSON *param = cJSON_GetArrayItem(item, m);
        if (param)
        {
            char buff[128];
            int calc = 1;

            GET_JSON_VALUE_STRING(param, "identifier", table->property[m].identifier);
            GET_JSON_VALUE_DY_STRING(param, "desc", table->property[m].desc);
            GET_JSON_VALUE_INT(param, "calc", calc);
            if (calc == 0)
            {
                table->property[m].need_calc = false;
            }
            else
            {
                table->property[m].need_calc = true;
            }
            cJSON *dataType = cJSON_GetObjectItem(param, "dataType");
            if (dataType)
            {
                GET_JSON_VALUE_STRING(dataType, "type", buff);
                table->property[m].raw_data_type = object_datatype_char2enum(buff);
                cJSON *limit = cJSON_GetObjectItem(dataType, "specs");
                if (limit)
                {
                    GET_JSON_VALUE_STRING(limit, "max", buff);
                    table->property[m].max = atoi(buff);
                    GET_JSON_VALUE_STRING(limit, "min", buff);
                    table->property[m].min = atoi(buff);
                }
            }
            if (output)
            {
                cJSON *conditionReport = cJSON_GetObjectItem(param, "conditionReport");
                if (conditionReport)
                {
                    GET_JSON_VALUE_STRING(conditionReport, "type", table->property[m].condition.type);
                    GET_JSON_VALUE_DOUBLE(conditionReport, "thresholdVal", table->property[m].condition.thresholdVal);
                }
            }
        }
    }

    table->propertyCnt = cnt;
    *p_tab = table;
    return 0;
}

int object_services_info_parse(object_service_table_t **p_tab, cJSON *item)
{
    object_service_table_t *tab = NULL;
    int cnt = 0, i;
    cJSON *tag = NULL;

    if (!p_tab)
    {
        dy_syslog(LOG_ERR, "Invalid parameters");
        return -1;
    }
    if (item)
    {
        cnt = cJSON_GetArraySize(item);
    }
    tab = calloc(1, sizeof(object_service_table_t) + cnt * sizeof(object_service_cfg_t));
    if (tab == NULL)
    {
        dy_syslog(LOG_ERR, "malloc failed, cnt:%d", cnt);
        return -1;
    }

    for (i = 0; i < cnt; i++)
    {
        int required = 0;

        tag = cJSON_GetArrayItem(item, i);
        GET_JSON_VALUE_STRING(tag, "identifier", tab->service[i].identifier);
        GET_JSON_VALUE_DY_STRING(tag, "desc", tab->service[i].desc);
        GET_JSON_VALUE_STRING(tag, "method", tab->service[i].method);
        GET_JSON_VALUE_INT(tag, "required", required);

        dy_syslog(LOG_INFO, "object service_cfg[%d]->identifier:%s,desc:%s,method:%s,required:%d", i, tab->service[i].identifier, tab->service[i].desc, tab->service[i].method, required);

        if (required)
        {
            continue;
        }

        object_property_in_service_parse(&tab->service[i].pinput, cJSON_GetObjectItem(tag, "inputData"), 0);
        object_property_in_service_parse(&tab->service[i].poutput, cJSON_GetObjectItem(tag, "outputData"), 1);
    }

    tab->serviceCnt = cnt;
    *p_tab = tab;
    return 0;
}

int object_events_info_parse(object_event_table_t **p_tab, cJSON *item)
{
    object_event_table_t *tab = NULL;
    int cnt = 0, i;

    if (!p_tab)
    {
        dy_syslog(LOG_ERR, "Invalid parameters");
        return -1;
    }
    if (item)
    {
        cnt = cJSON_GetArraySize(item);
    }
    tab = calloc(1, sizeof(object_event_table_t) + cnt * sizeof(object_event_cfg_t));
    if (tab == NULL)
    {
        dy_syslog(LOG_ERR, "malloc failed, cnt:%d", cnt);
        return -1;
    }

    for (i = 0; i < cnt; i++)
    {
        int required = 0;
        cJSON *tag = cJSON_GetArrayItem(item, i);

        GET_JSON_VALUE_STRING(tag, "identifier", tab->event[i].identifier);
        GET_JSON_VALUE_DY_STRING(tag, "desc", tab->event[i].desc);
        GET_JSON_VALUE_STRING(tag, "method", tab->event[i].method);
        GET_JSON_VALUE_INT(tag, "required", required);

        object_property_in_service_parse(&tab->event[i].pinput, cJSON_GetObjectItem(tag, "inputData"), 0);
        object_property_in_service_parse(&tab->event[i].poutput, cJSON_GetObjectItem(tag, "outputData"), 1);
    }

    tab->eventCnt = cnt;
    *p_tab = tab;
    return 0;
}

/**
*******************************************************************************
*
* @brief 读取物模型配置
* @param p_object_table 用于存储物模型配置的结构
* @param cfg_file 配置文件
* @return 0 for success, -1 for error
*
*******************************************************************************
*/
int load_objects_cfg(object_table_t **p_object_table, const char *cfg_file)
{
    object_table_t *object_table = NULL;
    char *data = NULL;
    int i;
    cJSON *root = NULL;
    cJSON *objects = NULL;
    cJSON *object = NULL;
    int ret = 0;
    int cnt = 0;

    if (p_object_table == NULL || cfg_file == NULL)
    {
        dy_syslog(LOG_ERR, "p_object_table:%p cfg_file:%p", p_object_table, cfg_file);
        return -1;
    }

    data = read_file_data(cfg_file);
    if (data == NULL)
    {
        dy_syslog(LOG_ERR, "read cfg file %s error", cfg_file);
        ret = -1;
        goto out;
    }

    {
        char *new = download_url_data(data, OBJECTS_CFG_PATH);
        if (new != NULL)
        {
            free(data);
            data = new;
        }
    }

    root = cJSON_Parse(data);
    if (!root)
    {
        dy_syslog(LOG_ERR, "parse cfg file %s error", cfg_file);
        ret = -1;
        goto out;
    }

    objects = cJSON_GetObjectItem(root, "object_model_cfg");
    if (!objects)
    {
        dy_syslog(LOG_ERR, "%s get object error", cfg_file);
        ret = -1;
        goto out;
    }

    cnt = cJSON_GetArraySize(objects);
    object_table = calloc(1, sizeof(object_table_t) + cnt * sizeof(object_cfg_t));
    if (object_table == NULL)
    {
        dy_syslog(LOG_ERR, "malloc failed, cnt:%d", cnt);
        ret = -1;
        goto out;
    }

    for (i = 0; i < cnt; i++)
    {
        object = cJSON_GetArrayItem(objects, i);
        if (object)
        {
            cJSON *profile = cJSON_GetObjectItem(object, "profile");
            if (profile)
            {
                GET_JSON_VALUE_STRING(profile, "productKey", object_table->object[i].object_model_id);
                GET_JSON_VALUE_STRING(profile, "productSecret", object_table->object[i].product_secret);
            }

            dy_syslog(LOG_DEBUG, "p:%p s:%p e:%p", cJSON_GetObjectItem(object, "properties"), cJSON_GetObjectItem(object, "services"), cJSON_GetObjectItem(object, "events"));

            object_property_info_parse(&object_table->object[i].property_tab, cJSON_GetObjectItem(object, "properties"));
            object_services_info_parse(&object_table->object[i].service_tab, cJSON_GetObjectItem(object, "services"));
            object_events_info_parse(&object_table->object[i].event_tab, cJSON_GetObjectItem(object, "events"));
        }
    }

    object_table->object_cnt = cnt;

out:
    cJSON_Delete(root);
    if (data != NULL)
    {
        free(data);
    }

    if (object_table == NULL)
    {
        //分配node cnt等于0的node_cfg出来
        object_table = calloc(1, sizeof(object_table_t));
        object_table->object_cnt = 0;
    }
    *p_object_table = object_table;

    return ret;
}

object_cfg_t *find_object_by_id(object_table_t *object_table, char *object_model_id)
{
    int i;
    for (i = 0; i < object_table->object_cnt; i++)
    {
        if (strcmp(object_model_id, object_table->object[i].object_model_id) == 0)
        {
            return &(object_table->object[i]);
        }
    }

    return NULL;
}

/* check and allocate more memory: */
static int check_buffer_left(char * * ppbuf, size_t * maxlen, size_t curlen, size_t more)
{
    char * pbuf;
    size_t mlen, left;

    pbuf = *ppbuf;
    mlen = *maxlen;
    left = (mlen > curlen) ? (mlen - curlen) : 0;
    if (left < more) {
        char * news;
        news = (char *) realloc(pbuf, mlen + more);
        if (news == NULL) {
            dy_syslog(LOG_ERR, "Error, system out of memory: %u, %u, %u",
                (unsigned int) mlen, (unsigned int) curlen, (unsigned int) more);
            return -1;
        }

        if (news != pbuf)
            *ppbuf = news;
        *maxlen = mlen + more;
    }
    return 0;
}

int build_property_luaexp(const template_table_t * ptemp,
    object_service_cfg_t * scfg, const char * pkey)
{
    const char * ident;
    unsigned int pnum, idx;
    int calidx, tnum, jdx, paranum;
    const service_cfg_t * svr_cfg;
    object_property_table_t * pout;
    const object_property_cfg_t * pcfg;

    calidx = 0;
    ident = NULL;
    svr_cfg = NULL;
    pout = scfg->poutput;
    pnum = pout ? pout->propertyCnt : 0;
    /* check the number of properties */
    if (pnum == 0)
        return 0;

    /* calculate the number of `need_calc properties */
    for (idx = 0; idx < pnum; ++idx) {
        pcfg = &(pout->property[idx]);
        if (pcfg->desc && pcfg->desc[0] && pcfg->need_calc)
            calidx++;
    }
    if (calidx == 0) {
        /* No need to do Lua expression calculation */
        return 0;
    }

    tnum = ptemp->template_cnt;
    if (tnum <= 0) {
        /* no template found */
        return 0;
    }

    calidx = -1;
    ident = scfg->identifier;
    /* find corresponding template */
    for (jdx = 0; jdx < tnum; ++jdx) {
        unsigned int snum, kdx;
        const template_cfg_t * tcfg;
        const service_table_t * stab;

        tcfg = &(ptemp->template[jdx]);
        stab = tcfg->service_tab;
        snum = stab ? stab->serviceCnt : 0;
        if (snum == 0) {
            /* service count is zero, skipped */
            continue;
        }

        /* template ID must be equal */
        if (strcmp(tcfg->template_id, pkey))
            continue;

        for (kdx = 0; kdx < snum; ++kdx) {
            const service_cfg_t * svrcfg;
            svrcfg = &(stab->service[kdx]);
            if (svrcfg->direction == 0)
                /* skip downstream template service */
                continue;

            if (strcmp(ident, svrcfg->identifier) == 0) {
                svr_cfg = svrcfg;
                break;
            }
        }

        if (svr_cfg != NULL)
            break;
    }

    if (svr_cfg == NULL) {
        dy_syslog(LOG_ERR, "Error, no template found for %s", ident);
        return 0;
    }

    /* check the number of elements in parameter table */
    paranum = svr_cfg->param_tab ? svr_cfg->param_tab->paramCnt : 0;
    if (paranum <= 0) {
        dy_syslog(LOG_ERR, "Error, invalid number of paramtab: %d", paranum);
        return 0;
    }

    /* free any existing lua_script */
    if (pout->lua_script != NULL) {
        free(pout->lua_script);
        pout->lua_script = NULL;
    }

    /* time for the real work: build a Lua script for fast expression processing */
    do {
        int kdx;
        size_t slen; /* total length of `script */
        size_t clen; /* current length of `script */
        char * script = NULL;
        const bit_field_t * bitf;
        const service_param_t * param = NULL;
        const service_param_table_t * partab;

        slen = clen = 0;
        partab = svr_cfg->param_tab;

        /* allocate memory for `script */
        if (check_buffer_left(&script, &slen, clen, 1024) < 0)
            return -1;

        script[clen++] = '\n';
        /* then, push all the sign_mark into the script */
        for (jdx = 0; jdx < paranum; ++jdx) {
            int bdx, cdx;

            param = &(partab->param[jdx]);
            if (check_buffer_left(&script, &slen, clen, 512) < 0) {
                free(script);
                return -1;
            }

            /* push local variable */
            kdx = snprintf(script + clen, slen - clen, "local %s = 0\n", param->sign_mark);
            if (kdx > 0)
                clen += (size_t) kdx;

            bitf = param->bit_fields;
            cdx = bitf ? param->bit_cnt : 0;
            /* process bit fields, if any */
            for (bdx = 0; bdx < cdx; ++bdx) {
                if (bitf->sign_mark[0] == '\0') {
                    bitf++;
                    continue;
                }

                if (check_buffer_left(&script, &slen, clen, 512) < 0) {
                    free(script);
                    return -1;
                }

                kdx = snprintf(script + clen, slen - clen, "local %s = 0\n",
                    bitf->sign_mark);
                if (kdx > 0)
                    clen += (size_t) kdx;
                bitf++;
            }
        }
        script[clen++] = '\n';

        /* and then, push the calculation functions */
        for (idx = 0; idx < pnum; ++idx) {
            int hasret;
            pcfg = &(pout->property[idx]);
            if (pcfg->desc == NULL || pcfg->need_calc == 0)
                continue;

            if (check_buffer_left(&script, &slen, clen, 512) < 0) {
                free(script);
                return -1;
            }

            hasret = strstr(pcfg->desc, "return") != NULL;
            kdx = snprintf(script + clen, slen - clen,
                "function lxf_%s()\n\t%s%s\nend\n",
                pcfg->identifier, hasret ? "" : "return ", pcfg->desc);
            if (kdx > 0)
                clen += (size_t) kdx;
        }

        if (check_buffer_left(&script, &slen, clen, 512) < 0) {
            free(script);
            return -1;
        }

        /* last but not least, create a function which use all the upvalues */
        kdx = snprintf(script + clen, slen - clen,
            "function " LNX_UPVALUES_FUNC "()\n\tlocal _LV_ALL = 0\n");
        if (kdx > 0)
            clen += (size_t) kdx;

        for (jdx = 0; jdx < paranum; ++jdx) {
            int bdx, cdx;

            param = &(partab->param[jdx]);
            if (check_buffer_left(&script, &slen, clen, 512) < 0) {
                free(script);
                return -1;
            }

            /* push local variable */
            kdx = snprintf(script + clen, slen - clen, "\t_LV_ALL = _LV_ALL + %s\n",
                param->sign_mark);
            if (kdx > 0)
                clen += (size_t) kdx;

            bitf = param->bit_fields;
            cdx = bitf ? param->bit_cnt : 0;
            /* process bit fields, if any */
            for (bdx = 0; bdx < cdx; ++bdx) {
                if (bitf->sign_mark[0] == '\0') {
                    bitf++;
                    continue;
                }

                if (check_buffer_left(&script, &slen, clen, 512) < 0) {
                    free(script);
                    return -1;
                }

                kdx = snprintf(script + clen, slen - clen, "\t_LV_ALL = _LV_ALL + %s\n",
                    bitf->sign_mark);
                if (kdx > 0)
                    clen += (size_t) kdx;
                bitf++;
            }
        }

        kdx = snprintf(script + clen, slen - clen, "\treturn _LV_ALL\nend\n");
        if (kdx > 0)
            clen += (size_t) kdx;

        script[clen] = '\0';
        pout->lua_script = script;
    } while (0);

    return 1;
}
