#include "opcua/ua-server.h"
#include "prot_transf.h"
#include "tag.h"
#include "common.h"
#include "bms_map.h"
#include "multilanguage.h"
#include "ems/jsonct.h"

#define EXT_DEV_TAG_FLAG  ((void *)99)

typedef struct bms_map_desc
{
    int type;
    const char *no; // no
    const char *description;
    const char *displayName;
    const char *desc;
} bms_node_map_desc;

typedef struct bms_map_ext_info
{
    tag_t **tag_arr;
    int tag_sum;
} bms_map_ext_info;

bms_node_map_desc bms_desc[] = {
    {.type = 1, .no = "SOC", .description = "SOC", .displayName = "RackSOC(%)"},
    {.type = 0, .no = "RackMaxVoltage", .description = "RackMaxVoltage", .displayName = "Rack单体电压最高值(mV)"},
    {.type = 0, .no = "RackMinVoltage", .description = "RackMinVoltage", .displayName = "Rack单体电压最低值(mV)"},
    {.type = 0, .no = "CellVolLowWarn2", .description = "CellVolLowWarn2", .displayName = "单体欠压二级报警"},
    {.type = 0, .no = "CellVolLowWarn3", .description = "CellVolLowWarn3", .displayName = "单体欠压三级报警"},
    {.type = 0, .no = "RackVolLowWarn2", .description = "RackVolLowWarn2", .displayName = "总电压欠压二级报警"},
    {.type = 0, .no = "RackVolLowWarn3", .description = "RackVolLowWarn3", .displayName = "总电压欠压三级报警"},
    {.type = 0, .no = "CellVolHighWarn2", .description = "CellVolHighWarn2", .displayName = "单体过压二级报警"},
    {.type = 0, .no = "CellVolHighWarn3", .description = "CellVolHighWarn3", .displayName = "单体过压三级报警"},
    {.type = 0, .no = "RackVolHighWarn2", .description = "RackVolHighWarn2", .displayName = "总电压过压二级报警"},
    {.type = 0, .no = "RackVolHighWarn3", .description = "RackVolHighWarn3", .displayName = "总电压过压三级报警"},
    {.type = 0, .no = "RackMaxChargeCur", .description = "RackMaxChargeCur", .displayName = "BMS最大充电电流"},
    {.type = 0, .no = "RackMaxDisChaCur", .description = "RackMaxDisChaCur", .displayName = "BMS最大放电电流"},
    {.type = 0, .no = "RackVoltage", .description = "RackVoltage", .displayName = "Rack电池总电压"},
    {.type = 0, .no = "Online", .description = "Online", .displayName = "在线", .desc = "0:离线;1:在线;"},
};

static int bms_ex_fun(void *par)
{
    return 0;
}

static int add_tag_to_ua(proto_forward_t *var, UA_NodeId parent_id, device_t *dev, tag_t *tag, int read_only)
{
    UA_SERVER_PARAM *ua_ser_para = var->ua_server_param;
    UA_Server *server = ua_ser_para->ua_server;
    // 注册UA的回调
    UA_NodeId_init(&tag->node_id);
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.description = UA_LOCALIZEDTEXT("zh_CN", tag->name);
    attr.displayName = UA_LOCALIZEDTEXT("zh_CN", tag->disp_name);
    if (read_only == 0)
    {
        attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    }
    else
    {
        attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    }

    UA_Double actposValue_d = 0.0f; // 默认值
    UA_Int32 actposValue_i = 0;     // 默认值
    void *value1 = NULL;
    if (tag->data_type == TYPE_TAG_FLOAT)
    {
        tag->read_cache.to_float = actposValue_d; // 默认值
        attr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
        value1 = &actposValue_d;
        UA_Variant_setScalar(&attr.value, value1, &UA_TYPES[UA_TYPES_DOUBLE]);
    }
    else
    {
        //
        if (tag->desc != NULL)
        {
            char enum_name[DEV_NO_LEN + TAG_NAME_LEN + 10] = {0};
            snprintf(enum_name, sizeof(enum_name), "%s_enum", tag->dev_tag_name);
            tag->ua_enum_type = ua_creat_custom_enum(server, enum_name, tag->desc);
        }
        //
        tag->read_cache.to_int = actposValue_i; // 默认值
        attr.dataType = (tag->ua_enum_type == NULL) ? UA_TYPES[UA_TYPES_INT32].typeId : ((ua_custom_enum_type *)tag->ua_enum_type)->typeId;
        value1 = &actposValue_i;
        UA_Variant_setScalar(&attr.value, value1, &UA_TYPES[UA_TYPES_INT32]);
    }

    UA_StatusCode retval = UA_Server_addVariableNode(server, UA_NODEID_STRING(1, tag->dev_tag_name),
                                                     parent_id,
                                                     UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), UA_QUALIFIEDNAME(1, tag->name),
                                                     UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, /*timeDataSource,*/ tag, &tag->node_id);
    if (retval != UA_STATUSCODE_GOOD)
    {
        proto_syslog(LOG_WARNING,
                     "tag:%s  UA_Server_addVariableNode %s", tag->dev_tag_name, UA_StatusCode_name(retval));
    }

    addValueCallbackToVariable(server, tag->node_id);
    return 0;
}

int is_dev_tag(tag_t *tag)
{
    if (tag->some_pr[0] == EXT_DEV_TAG_FLAG)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

static tag_t *new_tag(channel_t *ch, device_t *dev, const char *bms_no, bms_node_map_desc *tag_par)
{
    proto_forward_t *var = (proto_forward_t *)ch->var;
    tag_t *tag = calloc(1, sizeof(tag_t));

    snprintf(tag->name, sizeof(tag->name), "%s", tag_par->description);
    snprintf(tag->disp_name, sizeof(tag->disp_name), "%s", tag_par->displayName);
    snprintf(tag->dev_tag_name, sizeof(tag->dev_tag_name), "%s.%s.%s", dev->no, bms_no, tag_par->no);
    tag->desc = (tag_par->desc != NULL) ? strdup(tag_par->desc) : NULL;
    tag->chan_ptr = ch;
    tag->dev_ptr = dev;
    tag->data_type = tag_par->type;
    tag->history_type = HISTORY_TYPE_OFF;
    tag->no_fetch = 1;
    tag->proto_ptr = NULL;
    tag->funptr = NULL;

    tag->ua_enum_type = NULL;
    tag->some_pr[0] = EXT_DEV_TAG_FLAG;
    int ret = hash_intptr_addptr(&var->tag_map_hash, tag->dev_tag_name, strlen(tag->dev_tag_name), tag);
    if (ret < 0)
    {
        proto_syslog(LOG_ERR, "hash_intptr_addptr failed, key: %s", tag->dev_tag_name);
        dev->adp->tag = NULL;
        free(tag);
        return NULL;
    }
    return tag;
}

UA_NodeId *ua_server_add_dir_for_dev(void *par, char *dev_no, const char *node, char *description, char *displayName);

int register_ext_tag(void *chan_ptr, void *dev, void *json)
{
    channel_t *ch = (channel_t *)chan_ptr;
    cJSON *cfg_dev = (cJSON *)json;
    proto_forward_t *var = (proto_forward_t *)ch->var;
    device_t *cur_dev = (device_t *)dev;
    cJSON *dev_no = cJSON_GetObjectItemCaseSensitive(cfg_dev, "dev_no");
    cJSON *dev_name = cJSON_GetObjectItemCaseSensitive(cfg_dev, "dev_name");
    if ((dev_no == NULL) || (dev_name == NULL))
    {
        proto_syslog(LOG_CRIT, "设备扩展tag[%s]缺失必要参数", cur_dev->no);
        return -1;
    }
    UA_NodeId *nide_id = ua_server_add_dir_for_dev((UA_SERVER_PARAM *)var->ua_server_param, cur_dev->no, dev_no->valuestring, dev_name->valuestring, dev_name->valuestring);
    //
    struct tag_info
    {
        cJSON *tags;
        int sum;
        int read_only;
        UA_NodeId *nide_id;
    } tag_info[2] = {0};
    tag_info[0].tags = cJSON_GetObjectItemCaseSensitive(cfg_dev, "ro_tags");
    tag_info[0].sum = (tag_info[0].tags != NULL) ? cJSON_GetArraySize(tag_info[0].tags) : 0;
    tag_info[0].read_only = 1;
    tag_info[0].nide_id = nide_id;
    //
    tag_info[1].tags = cJSON_GetObjectItemCaseSensitive(cfg_dev, "rw_tags");
    tag_info[1].sum = (tag_info[1].tags != NULL) ? cJSON_GetArraySize(tag_info[1].tags) : 0;
    tag_info[1].read_only = 0;
    tag_info[1].nide_id = nide_id + 1;
    //
    int sum = tag_info[0].sum + tag_info[1].sum;
    bms_map_ext_info *ext_info = (bms_map_ext_info *)cur_dev->ext_par;
    int old_sum = 0;
    if (ext_info)
    {
        old_sum = ext_info->tag_sum;
        tag_t **new_arr = realloc(ext_info->tag_arr, (old_sum + sum) * sizeof(tag_t *));
        if (!new_arr)
        {
            ems_syslog(LOG_CRIT, "申请内存失败！！");
            return -1;
        }
        ext_info->tag_arr = new_arr;
    }
    else
    {
        ext_info = (bms_map_ext_info *)calloc(1, sizeof(bms_map_ext_info));
        ext_info->tag_arr = (tag_t **)calloc(sum, sizeof(tag_t *));
        old_sum = 0;
    }
    //
    bms_node_map_desc node = {0};
    tag_t *tag = NULL;
    for (int j = 0; j < sizeof(tag_info) / sizeof(tag_info[0]); j++)
    {
        sum = tag_info[j].sum;
        for (int i = 0; i < sum; i++)
        {
            cJSON *it = cJSON_GetArrayItem(tag_info[j].tags, i);
            cJSON *no = cJSON_GetObjectItemCaseSensitive(it, "no");
            cJSON *dis_name = cJSON_GetObjectItemCaseSensitive(it, "dis_name");
            cJSON *type = cJSON_GetObjectItemCaseSensitive(it, "type");
            cJSON *desc = cJSON_GetObjectItemCaseSensitive(it, "desc");
            if (no == NULL || dis_name == NULL || type == NULL)
            {
                proto_syslog(LOG_CRIT, "设备扩展tag[%s]缺失必要参数", dev_no->valuestring);
                continue;
            }

            node.type = type->valueint;
            node.no = no->valuestring;
            node.description = no->valuestring;
            node.displayName = dis_name->valuestring;
            node.desc = (desc != NULL) ? desc->valuestring : NULL;
            tag = new_tag(ch, cur_dev, dev_no->valuestring, &node);
            add_tag_to_ua(var, *tag_info[j].nide_id, cur_dev, tag, tag_info[j].read_only);
            ext_info->tag_arr[old_sum] = tag;
            old_sum++;
        }
    }
    //
    ext_info->tag_sum = old_sum;
    cur_dev->ext_par = ext_info;
    cur_dev->ext_fun = bms_ex_fun;

    return 0;
}

int register_ext_info(void *chan_ptr, void *dev, const char *bms_no, char *description, char *displayName)
{
    channel_t *ch = (channel_t *)chan_ptr;
    proto_forward_t *var = (proto_forward_t *)ch->var;
    device_t *cur_dev = (device_t *)dev;
    UA_NodeId *nide_id = ua_server_add_dir_for_dev((UA_SERVER_PARAM *)var->ua_server_param, cur_dev->no, bms_no, description, displayName);
    tag_t *tag = NULL;
    int len = sizeof(bms_desc) / sizeof(bms_desc[0]);

    bms_map_ext_info *ext_info = (bms_map_ext_info *)cur_dev->ext_par;
    int old_sum = 0;
    if (ext_info) {
        old_sum = ext_info->tag_sum;
        tag_t **new_arr = realloc(ext_info->tag_arr, (old_sum + len) * sizeof(tag_t *));
        if (!new_arr) {
            ems_syslog(LOG_ERR, "realloc failed in register_ext_info");
            return -1;
        }
        ext_info->tag_arr = new_arr;
    } else {
        ext_info = (bms_map_ext_info *)calloc(1, sizeof(bms_map_ext_info));
        ext_info->tag_arr = (tag_t **)calloc(len, sizeof(tag_t *));
        old_sum = 0;
    }

    for (int i = 0; i < len; i++)
    {
        tag = new_tag(ch, cur_dev, bms_no, &bms_desc[i]);
        add_tag_to_ua(var, nide_id[0], cur_dev, tag, 1);
        ext_info->tag_arr[old_sum + i] = tag;
    }
    ext_info->tag_sum = old_sum + len;
    cur_dev->ext_par = ext_info;
    cur_dev->ext_fun = bms_ex_fun;
    return 0;
}

tag_t **get_ext_tag(void *ext_par, int *out_sum)
{
    bms_map_ext_info *ext_info = (bms_map_ext_info *)ext_par;
    *out_sum = ext_info->tag_sum;
    return ext_info->tag_arr;
}