#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "MetaTagSequence.pb-c.h"
#include "ValueSequence.pb-c.h"
#include "RtdEvent.pb-c.h"
#include "common.h"

#if 0
int main()
{
    size_t len;
    void *buf = NULL;
    int i;
    MetaTag *tags[1];
    MetaTagSequence meta_tag_sequence = META_TAG_SEQUENCE__INIT;
    MetaTagSequence *unpack = NULL;
    MetaTag meta_tag = META_TAG__INIT;

    tags[0] = &meta_tag;

    meta_tag.version = 1;
    meta_tag.name = strdup("hello word");
    meta_tag.type = VALUE_TYPE__Integer;

    meta_tag_sequence.n_tags = 1;

    meta_tag_sequence.tags = tags;

    len = meta_tag_sequence__get_packed_size(&meta_tag_sequence);

    buf = malloc(len);
    meta_tag_sequence__pack(&meta_tag_sequence, buf);
    for (i = 0; i < len; i++)
    {
        if (i % 8 == 0) printf("\t");
        if (i % 32 == 0) printf("\n");
        printf("%02X ", ((char *)buf)[i]);
    }
    unpack = meta_tag_sequence__unpack(NULL, len, buf);

    printf("%s", unpack->tags[0]->name);

    meta_tag_sequence__free_unpacked(unpack, NULL);
}

#endif

static int load_meta_tag_data(lanzhuo_var_t *var, char *cfg_file)
{
    char *data = NULL;
    int i;
    cJSON *root = NULL;
    cJSON *objects = NULL;
    cJSON *object = NULL;
    int ret = 0;
    int cnt = 0;
    int index = 0;
    int len;

    meta_tag_sequence__init(&var->meta_tag_seq);

    if (cfg_file == NULL)
    {
        dy_syslog(LOG_ERR, " cfg_file:%p", 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;
    }
    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);
    for (i = 0; i < cnt; i++)
    {
        object = cJSON_GetArrayItem(objects, i);
        if (object)
        {
            cJSON *properties = cJSON_GetObjectItem(object, "properties");
            if (properties)
            {
                int proper_cnt = cJSON_GetArraySize(properties);
                int j = 0;

                var->meta_tag_seq.n_tags += proper_cnt;
                var->meta_tag_seq.tags = realloc(var->meta_tag_seq.tags, var->meta_tag_seq.n_tags * sizeof(MetaTag *));

                for (j = 0; j < proper_cnt; j++)
                {
                    cJSON *peroperty = cJSON_GetArrayItem(properties, j);
                    if (peroperty)
                    {
                        MetaTag *tag = malloc(sizeof(MetaTag));
                        RtdValue *defval = malloc(sizeof(RtdValue));
                        char type[16] = {0};
                        char buff[8] = {0};
                        int min, max;
                        ValueType vtype = VALUE_TYPE__Unknown;

                        meta_tag__init(tag);
                        rtd_value__init(defval);

                        tag->version = 1;
                        GET_JSON_VALUE_DY_STRING(peroperty, "identifier", tag->name);
                        GET_JSON_VALUE_DY_STRING(peroperty, "name", tag->showname);
                        GET_JSON_VALUE_DY_STRING(peroperty, "remark", tag->description);
                        cJSON *data_type = cJSON_GetObjectItem(peroperty, "dataType");
                        cJSON *specs = cJSON_GetObjectItem(data_type, "specs");
                        GET_JSON_VALUE_DY_STRING(specs, "unit", tag->unit);
                        GET_JSON_VALUE_STRING(specs, "max", buff);
                        max = atoi(buff);
                        GET_JSON_VALUE_STRING(specs, "min", buff);
                        min = atoi(buff);
                        if (max || min)
                        {
                            char tmp[32] = {0};
                            sprintf(tmp, "%d-%d", min, max);
                            tag->range = strdup(tmp);
                        }

                        printf("showname:%s\n", tag->showname);
                        GET_JSON_VALUE_STRING(data_type, "type", type);
                        if (strcmp(type, "float") == 0 || strcmp(type, "double") == 0)
                        {
                            vtype = VALUE_TYPE__Double;
                            defval->value_case = RTD_VALUE__VALUE_DBL_VAL;
                            defval->dblval = 0;
                            tag->defaultvalue = defval;
                        }
                        else if (strcmp(type, "int") == 0 || strcmp(type, "long") == 0)
                        {
                            vtype = VALUE_TYPE__Integer;
                            defval->value_case = RTD_VALUE__VALUE_INT_VAL;
                            defval->intval = 0;
                            tag->defaultvalue = defval;
                        }
                        else if (strcmp(type, "text") == 0)
                        {
                            vtype = VALUE_TYPE__String;
                            defval->value_case = RTD_VALUE__VALUE_STR_VAL;
                            defval->strval = "";
                            tag->defaultvalue = defval;
                        }
                        else if (strcmp(type, "boolean") == 0)
                        {
                            vtype = VALUE_TYPE__Boolean;
                            defval->value_case = RTD_VALUE__VALUE_BOOL_VAL;
                            defval->intval = 0;
                            tag->defaultvalue = defval;
                        }
                        else if (strcmp(type, "bytes") == 0)
                        {
                            vtype = VALUE_TYPE__Bytes;
                            // values[index]->value->value_case = RTD_VALUE__VALUE_BYT_VAL;
                            // values[index]->value->bytval.len = 0;
                            // values[index]->value->bytval.data = NULL;
                        }
                        tag->type = vtype;

                        char *ext_data_str = NULL;
                        GET_JSON_VALUE_DY_STRING(peroperty, "ext_data", ext_data_str);
                        printf("ext_data_str:%s\n", ext_data_str);

                        cJSON *ext = cJSON_Parse(ext_data_str);
                        if (ext != NULL)
                        {
                            int storage = 0;

                            GET_JSON_VALUE_INT(ext, "storage", storage);
                            if (storage)
                            {
                                tag->has_storage = true;
                                tag->storage = true;
                            }

                            cJSON *compress = cJSON_GetObjectItem(ext, "compress");
                            if (compress)
                            {
                                tag->compress = malloc(sizeof(CompressSpec));
                                compress_spec__init(tag->compress);
                                int enable = 0;
                                GET_JSON_VALUE_INT(compress, "enable", enable);
                                if (enable)
                                {
                                    tag->compress->enable = true;
                                    GET_JSON_VALUE_DOUBLE(compress, "value", tag->compress->value);
                                    GET_JSON_VALUE_INT(compress, "maxElapse", tag->compress->maxelapse);
                                }
                                else
                                {
                                    tag->compress->enable = false;
                                }
                            }
                            cJSON_Delete(ext);
                        }
                        else
                        {
                            tag->has_storage = true;
                            tag->storage = true;
                            tag->compress = malloc(sizeof(CompressSpec));
                            compress_spec__init(tag->compress);
                            tag->compress->enable = false;
                            tag->compress->value = 0;
                            tag->compress->maxelapse = 0;
                        }
                        free(ext_data_str);

                        var->meta_tag_seq.tags[index] = tag;
                        index++;
                    }
                }
            }
        }
    }

    len = meta_tag_sequence__get_packed_size(&var->meta_tag_seq);
    var->meta_tag_buf = malloc(len);
    meta_tag_sequence__pack(&var->meta_tag_seq, var->meta_tag_buf);
    var->meta_tag_buf_len = len;

    #if 1 //test code
    {
        MetaTagSequence *unpack = NULL;

        for (i = 0; i < len; i++)
        {
            if (i % 8 == 0)
                printf("\t");
            if (i % 32 == 0)
                printf("\n");
            printf("%02X ", ((char *)var->meta_tag_buf)[i]);
        }
        printf("\n");

        unpack = meta_tag_sequence__unpack(NULL, len, var->meta_tag_buf);

        printf("list unpacked name\n");
        for (i = 0; i < unpack->n_tags; i++)
        {
            printf("%s type:%d compress:%d storage:%d show name:%s\n", unpack->tags[i]->name,  unpack->tags[i]->type,
                   unpack->tags[i]->compress->enable, unpack->tags[i]->storage, unpack->tags[i]->showname);
        }
        printf("\n");
        meta_tag_sequence__free_unpacked(unpack, NULL);
    }
    #endif

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

    return ret;
}

static int send_data_in_query_list(lanzhuo_var_t *var)
{
    tag_list_node_t *tmp = NULL;
    tag_list_node_t *tag_list = NULL;
    dy_db_session_t *session = var->db_session;
    int ret = 0;

    if (list_empty(&session->query_list))
    {
        return 0;
    }

    list_for_each_entry_safe(tag_list, tmp, &session->query_list, list)
    {
        tag_table_t *p = &tag_list->tag;
        char topic[TOPIC_MAX_LEN] = {0};

        snprintf(topic, TOPIC_MAX_LEN, "/%s/%s/%s/%s/%s", var->token, var->ep_id, var->ep_name, "cachevalue", "report");
        ret = mqtt_session_publish_with_flag(var->session_ext, topic, p->tag_node, p->tag_len, 1, false);
        if (ret == 0)
        {
            // 发布成功保留在链表里，用于更新数据库
            p->report = REPORTED;
        }
        else
        {
            list_del(&tag_list->list);
            free(tag_list->tag.tag_node);
            free(tag_list->tag.ext);
            free(tag_list);
        }
    }

    if (list_empty(&session->query_list))
    {
        // 全部上报失败，不需要更新数据库
        dy_syslog(LOG_WARNING, "all report failed");
        return 0;
    }
    else
    {
        list_move_tail_list(&session->query_list, &session->update_list);
        return 1;
    }
}

int data_report_poll(lanzhuo_var_t *var)
{
    TIMER_CONFIRM(var->report_timer);

    dy_db_session_select_not_repoted(var->db_session, 100);
    send_data_in_query_list(var);
    dy_db_session_update_reported(var->db_session);

    return 0;
}

static int lanzhuo_loop(lanzhuo_var_t *var)
{
    int ret = -1, maxfd;
    fd_set rset;
    struct timeval timeout;

    while (1)
    {
        SELECT_INIT();
        timeout.tv_usec = 0;
        timeout.tv_sec = 20;
        SELECT_ADD_FD(var->report_timer);
        ret = select(maxfd + 1, &rset, 0, 0, &timeout);
        if (ret < 0)
        {
            if (errno == EINTR)
            {
                continue;
            }
            else
            {
                break;
            }
        }
        else if (ret > 0)
        {
            if (var->report_timer > 0 && FD_ISSET(var->report_timer, &rset))
            {
                FD_CLR(var->report_timer, &rset);
                data_report_poll(var);
            }
        }
    }
}

static int load_cfg(lanzhuo_var_t *var, char *cfg_file)
{
    char *data = NULL;
    cJSON *root = NULL;
    int ret = -1;
    char    addr[MAX_HOST_LEN];     ///<MQTT地址
    unsigned short port;            ///<MQTT端口
    char    user[MAX_USER_LEN];     ///<MQTT
    char    pass[MAX_PASS_LEN];     ///<MQTT地址

    if (cfg_file == NULL)
    {
        dy_syslog(LOG_ERR, "cfg_file:%s not exist, use defaut MQTT server", cfg_file);
        goto out;
    }

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

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

    GET_JSON_VALUE_STRING(root, "host", addr);
    GET_JSON_VALUE_INT(root, "port", port);
    GET_JSON_VALUE_STRING(root, "user", user);
    GET_JSON_VALUE_STRING(root, "pass", pass);
    mqtt_session_set_address(var->session_ext, addr, port, user, pass);

    GET_JSON_VALUE_DY_STRING(root, "token", var->token);
    GET_JSON_VALUE_DY_STRING(root, "endpoint_id", var->ep_id);
    GET_JSON_VALUE_DY_STRING(root, "endpoint_name", var->ep_name);

    cJSON_Delete(root);
    ret = 0;

out:
    if (data)
    {
        free(data);
    }
    return ret;

}

static int lanzhuo_init(lanzhuo_var_t *var)
{
    char clientId[MAX_CLIENT_ID_LEN] = {0};
    const char *board_name = NULL;
    board_name = get_board_name();

    get_board_sn(var->sn_str);

    if (strcmp(board_name, BOARD_WOOLINK_MT7628) == 0)
    {
        dy_db_session_init(&var->db_session, LANZHUO_DB_TMPFS, (void*)var);
    }
    else
    {
        dy_db_session_init(&var->db_session, LANZHUO_DB, (void*)var);
    }
    load_meta_tag_data(var, OBJECTS_CFG_PATH);

    snprintf(clientId, MAX_CLIENT_ID_LEN, "LANZHUO_%s", var->sn_str);
    var->session_int = ipc_session_new(clientId, (void*)var, IPC_DEFAULT);
    var->session_ext = mqtt_session_new(clientId, (void*)var);
    if (var->session_ext == NULL || var->session_int == NULL)
    {
        dy_syslog(LOG_ERR, "create MQTT session failed");
        abort();
    }
    load_cfg(var, LANZHUO_CFG);

    internal_client_init(var);
    external_client_init(var);

    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, 10, 60 * 1000);
    }

    dy_syslog(LOG_INFO, "init done");

    return 0;
}

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

    openlog("LANZHUO", LOG_PID, LOG_DAEMON);

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

    lanzhuo_init(&var);
    lanzhuo_loop(&var);

    return 0;
}
