#include <math.h>
#include <errno.h>
#include <syslog.h>
#include "sys/param.h"
#include "assert.h"
#include "pthread.h"
#include "../common.h"
#include "../mac_mul/raw_sock.h"
#include "../mac_mul/mac_protocol.h"
#include "lc_discovery.h"
#include "dy_utils/hash_intptr.h"
#include "collector-api.h"
#include "define.h"
#include "remote_ua_client.h"
#include "../opcua/ua_com.h"
#include "../cmdclient.h"
#include "pub_fun.h"
// char g_send_message[1024] = {0};//这个帧固定的可以不变一直用 免得一直拼 下一级做了校验这一级直接当数据用
// int g_message_len = 0;
int g_en_lo_lc = 0;
// Global list of discovered devices
static hash_intptr_t * g_disc_list;
// Global discovery information
static disc_cfginfo g_self;
// Global discovery information lock
static pthread_mutex_t g_disc_lock = PTHREAD_MUTEX_INITIALIZER;

// Broadcast raw socket and its lock:
static RAW_SOCK * g_raw;
static pthread_mutex_t g_data_lock = PTHREAD_MUTEX_INITIALIZER;

// static int create_lo_udp_socket(unsigned int emstype);
static int discover_devno_tosn(const char * devno, char * sn, size_t snlen);
static int discover_sn2info(const char * sn, disc_devinfo * pinfo);

static void create_discover_packet(char * output, const char * ip)
{
    disc_msg * pmsg;
    discmsg_basic * mip;
    const disc_cfginfo * pdm;
    const char * softver = MAJOR_PRO_VERSION;

    pdm = &g_self;
    pmsg = (disc_msg *) output;
    mip = (discmsg_basic *) pmsg->dmi_data;

    pmsg->gw_type = pdm->devtype;
    local_strlcpy(pmsg->dmi_sn, pdm->sn, sizeof(pmsg->dmi_sn));
    pmsg->dmi_type = DISCOVERY_MSGTYPE_BASIC;
    pmsg->dmi_len = (unsigned short) sizeof(*mip);

    if (ip != NULL)
        strncpy(mip->ipaddr, ip, sizeof(mip->ipaddr));
    strncpy(mip->softver, softver, sizeof(mip->softver));

    int fire = 0;
#if EN_EMS
    fire = dev_get_dev_tag_int(DEV_NO_EMS, ALL_FIRE);
#else
    fire = dev_get_dev_tag_int(DEV_NO_LC, FIRE);
#endif
    mip->fire = fire > 0 ? 1 : 0;
}

static int package_and_send_discover(RAW_SOCK *raw,
    const char * msgbuf, size_t msglen)
{
    int ret;

    ret = pthread_mutex_lock(&g_data_lock);
    if (ret != 0) {
        ems_syslog(LOG_ERR, "Error, failed to lock data: %d", ret);
        return -1;
    }

    ret = send_protocol_data(raw, (char *) msgbuf, msglen);
    pthread_mutex_unlock(&g_data_lock);
    return ret;
}

// Note that the function is called with `g_disc_lock locked
#if 0 //EN_EMS
static void ems_boardcast_fire(void)
{
    char msgbuf[sizeof(disc_msg) + sizeof(discmsg_basic)];
    char ipaddr[20] = {0};
    int  ret = get_localip(get_pcs_ctrl_var()->net_node, ipaddr);
    // if (ret == 0 && ipaddr[0] != '\0')
    // {
    //     int           locked;
    //     disc_cfginfo* pdm = &g_self;
    //     // lock global mutex
    //     locked = pthread_mutex_lock(&g_disc_lock);
    //     // update network device IP address
    //     strncpy(pdm->ipaddr, ipaddr, sizeof(pdm->ipaddr));
    //     if (locked == 0)
    //         pthread_mutex_unlock(&g_disc_lock);
    // }

    memset(msgbuf, 0, sizeof(msgbuf));

    disc_msg*           pmsg    = (disc_msg*)msgbuf;
    discmsg_basic*      mip     = (discmsg_basic*)pmsg->dmi_data;
    const disc_cfginfo* pdm     = &g_self;
    const char*         softver = MAJOR_PRO_VERSION;
    RAW_SOCK*           raw     = g_raw;

    pmsg->gw_type = pdm->devtype;
    strncpy(pmsg->dmi_sn, pdm->sn, sizeof(pmsg->dmi_sn));
    pmsg->dmi_type = DISCOVERY_MSGTYPE_BASIC;
    pmsg->dmi_len  = (unsigned short)sizeof(*mip);
    strncpy(mip->ipaddr, ipaddr, sizeof(mip->ipaddr));
    strncpy(mip->softver, softver, sizeof(mip->softver));
    
    mip->fire = 1;
    package_and_send_discover(raw, msgbuf, sizeof(msgbuf));
    ems_syslog_hex(LOG_ERR, msgbuf, sizeof(msgbuf), "ems_boardcast_fire:");
} 
#endif

static int detail_discover_data(char *data,
    int len, const char * emsip, hash_intptr_t * cfgdevp)
{
    int ret, islocal;
    unsigned int snlen;
    disc_devinfo * old = NULL;
    disc_msg * gdi = (disc_msg *) data;

    islocal = emsip ? 0 : 1;
    if (gdi->dmi_sn[0] == '\0')
        return -1;
    snlen = (unsigned int) strlen(gdi->dmi_sn);

    old = NULL;
    ret = hash_intptr_findptr(g_disc_list, gdi->dmi_sn, snlen, (void * *) &old);
    if (ret < 0) {
        old = (disc_devinfo *) calloc(0x1, sizeof(*old));
        memset(old, 0, sizeof(*old));
        if (old == NULL) {
            ems_syslog(LOG_ERR, "Error, system out of memory!");
            return -1;
        }

        time(&old->timestamp);
        strncpy(old->sn, gdi->dmi_sn, sizeof(old->sn));

        ret = hash_intptr_addptr(&g_disc_list, gdi->dmi_sn, snlen, old);
        if (ret == 0) {
            proto_syslog(LOG_ERR, "find new dev :%s!", gdi->dmi_sn);
        }
    } else {
        proto_syslog(LOG_NOTICE, "Update timestamp for device: %s\n", old->sn);
        time(&old->timestamp);
    }

    if (old->islocal != islocal)
        old->islocal = islocal;
    old->devtype = gdi->gw_type;
    switch (gdi->dmi_type) {
    case DISCOVERY_MSGTYPE_NONE:
        break;

    case DISCOVERY_MSGTYPE_BASIC:
        if (gdi->dmi_len >= (long int)&((discmsg_basic*)0)->fire) { // 从fire字段开始为扩展，v1.48.200之前的版本没有fire及其以后的字段
            const discmsg_basic * mbp;
            disc_cfginfo * cfgi = NULL;

            mbp = (const discmsg_basic *) gdi->dmi_data;
            strncpy(old->softver, mbp->softver, sizeof(old->softver));
            strncpy(old->ipaddr, mbp->ipaddr, sizeof(old->ipaddr));

            if (cfgdevp != NULL && emsip && emsip[0])
                hash_intptr_findptr(cfgdevp,
                    gdi->dmi_sn, snlen, (void * *) &cfgi);

            if (cfgi != NULL && cfgi->disabled == 0 && cfgi->ipaddr[0] &&
                strcmp(cfgi->ipaddr, mbp->ipaddr) != 0) {
                set_discovery_ipaddr(gdi->dmi_sn, cfgi->ipaddr, emsip);
            }
            if (gdi->dmi_len == sizeof(discmsg_basic))
            {
                // 接收到从机火灾广播，所有设备脱扣
                if (mbp->fire > 0 && old->fire == 0)
                {
                //     do_trip(); // 脱扣;
                // #if EN_EMS
                //     dev_set_dev_tag_int(DEV_NO_EMS, EXTERN_FIRE, 1); // 外部火灾
                //     ems_boardcast_fire();                            // 主机立即进行火灾广播
                // #endif
                    old->fire = mbp->fire;
                }
                old->fire = mbp->fire;
            }
            else
            {
                old->fire = 0;
            }
        }
        break;

    case DISCOVERY_MSGTYPE_SETIPADDR:
        if (gdi->dmi_len == sizeof(discmsg_setip)) {
            int app_restart = 0;
            disc_cfginfo * cfginfo;
            const discmsg_setip * pip;

            cfginfo = &g_self;
            pip = (const discmsg_setip *) gdi->dmi_data;
            if (strcmp(cfginfo->sn, pip->sn) == 0) {
                if (pip->ipaddr[0] && pip->gwip[0])
                    port_config_update_wan(pip->ipaddr, pip->gwip, NULL);
                else
                    port_config_update_wan(NULL, NULL, NULL); // DHCP
                
                app_restart = 1;
                char ipaddr[20] = {0};
                if ((0 == get_localip(get_pcs_ctrl_var()->net_node, ipaddr)) && (strcmp(ipaddr, pip->ipaddr) != 0))
                {
                    app_restart = 1;
                }

                if (app_restart != 0)
                {
                    ems_syslog(LOG_WARNING, "ip changed and restart......");
                    cmd_call("systemctl restart emsd", 10, NULL, 0);
                }
            }
        }
        break;

    case DISCOVERY_MSGTYPE_REBOOT:
        if (gdi->dmi_len == sizeof(discmsg_reboot)) {
            disc_cfginfo * cfginfo;
            const discmsg_reboot * boot;

            cfginfo = &g_self;
            boot = (const discmsg_reboot *) gdi->dmi_data;
            if (boot->reboot_magic == DISCMSG_REBOOT_MAGIC &&
                strcmp(cfginfo->sn, boot->sn) == 0) {
                ems_syslog(LOG_ERR, "discovery reboot: %u, %u\n",
                    (unsigned int) boot->sys_reboot, (unsigned int) boot->app_reboot);
                if (boot->sys_reboot != 0)
                    cmd_call("reboot", -1, NULL, 0);
                if (boot->app_reboot != 0)
                    cmd_call("systemctl restart emsd", -1, NULL, 0);
            }
        }
        break;

    default:
        break;
    }

    return 0;
}

int lc_get_discover_dev_num()
{
    int ret, locked;

    ret = 0;
    locked = pthread_mutex_lock(&g_disc_lock);
    if (locked != 0) {
        ems_syslog(LOG_ERR, "Failed to lock discovery lock: %d", locked);
    }

    if (g_disc_list != NULL)
        ret = hash_intptr_count(g_disc_list);

    if (locked == 0)
        pthread_mutex_unlock(&g_disc_lock);
    return ret;
}

static int copy_disc_dev(struct hash_intptr * hip, int index, void * _gws)
{
    disc_devinfo * old, * new;

    new = (disc_devinfo *) _gws;
    old = (disc_devinfo *) hip->hh_ptr;

    new = &(new[index]);
    memcpy(new, old, sizeof(*new));
    //
    if (new->islocal && (new->ipaddr[0] == '\0'))
    {
        snprintf(new->ipaddr, sizeof(new->ipaddr), "127.0.0.1");
    }
    return 0;
}

disc_devinfo * get_discover_devs(int * numdevs)
{
    int num, locked;
    disc_devinfo * gws;

    num = 0;
    gws = NULL;
    locked = pthread_mutex_lock(&g_disc_lock);
    if (locked != 0) {
        ems_syslog(LOG_ERR, "Failed to lock discovery lock: %d", locked);
    }

    if (g_disc_list != NULL)
        num = hash_intptr_count(g_disc_list);
    if (num <= 0)
        goto err0;

    gws = (disc_devinfo *) calloc((size_t) num, sizeof(disc_devinfo));
    if (gws == NULL) {
        ems_syslog(LOG_ERR, "Error, system out of memory: %d", num);
        goto err0;
    }
    hash_intptr_iter(&g_disc_list, copy_disc_dev, gws);

err0:
    if (locked == 0)
        pthread_mutex_unlock(&g_disc_lock);
    if (numdevs != NULL)
        *numdevs = num;
    return gws;
}

void free_discover_devs(disc_devinfo * gws, int num)
{
    (void) num; // ignored
    if (gws != NULL)
        free(gws);
}

struct iter_old {
    int sncount;
    time_t now;
    char sn[10][32];
};

static int iter_old_func(struct hash_intptr * hip, int index, void * _gws)
{
    struct iter_old * old;
    disc_devinfo * devinfo;

    old = (struct iter_old *) _gws;
    if (old->sncount >= 10)
        return -1;

    devinfo = (disc_devinfo *) hip->hh_ptr;
    if (devinfo == NULL)
        return 0;

    if (devinfo->timestamp > old->now ||
        (old->now - devinfo->timestamp) >= CCLC_AVLID_TIME) {
        local_strlcpy(old->sn[old->sncount], devinfo->sn, sizeof(old->sn[old->sncount]));
        old->sncount++;
        if (old->sncount >= 10)
            return -1;
    }
    return 0;
}

// called with `g_disc_lock unlocked
static void remove_old_broadcast_devs(void)
{
    int locked, i;
    struct iter_old old;

    locked = pthread_mutex_lock(&g_disc_lock);
    if (locked != 0) {
        ems_syslog(LOG_ERR, "Failed to lock discovery info: %d", locked);
    }

    memset(&old, 0, sizeof(old));
    old.now = time(NULL);
    hash_intptr_iter(&g_disc_list, iter_old_func, &old);

    for (i = 0; i < old.sncount; ++i) {
        void * dev = NULL;
        unsigned int len;

        len = (unsigned int) strlen(old.sn[i]);
        ems_syslog(LOG_ERR, "Remove old broadcast device: %s\n", old.sn[i]);
        if (hash_intptr_findptr(g_disc_list, old.sn[i], len, &dev) >= 0) {
            hash_intptr_remove(&g_disc_list, old.sn[i], len);
            free(dev);
        }
    }

    if (locked == 0)
        pthread_mutex_unlock(&g_disc_lock);
}

void *lc_discover_task(void *arg)
{
    int isems = -1;
    uint64_t loopstart;
    RAW_SOCK *raw = NULL;
    time_t now, lastsend, lastread;
    char netdev[32], ipaddr[20];
    char message[1024]= {0};
    hash_intptr_t * cfgdevs = NULL;

    do {
        const disc_cfginfo * cfgi;
        cfgi = &g_self;
        if (cfgi->devtype != DISCOVERY_TYPE_EMS)
            isems = 0;
    } while (0);

raw_again:
    netdev[0] = '\0';
	for (;;) {
        char * ndev;
        ndev = get_pcs_ctrl_var()->net_node;
        raw = protocol_Init(ndev, CCLC_DISCOVER_CHANNEL_NAME);
        if (raw != NULL) {
            local_strlcpy(netdev, ndev, sizeof(netdev));
            break;
        }
        sleep(10);
    }

    g_raw = raw;
    loopstart = sysuptime();
    lastsend = lastread = 0;
    int lo_flag = 0;
    memset(ipaddr, 0, sizeof(ipaddr));
    while (1) {
        int locked;
        int ret, len;
        disc_msg * dmi;
        uint64_t loopnow;

        len = 0;
        loopnow = sysuptime();
        if ((loopnow - loopstart) >= 180000)
            break;

        dmi = (disc_msg *) message;
        memset(message, 0, sizeof(message));
        //读取广播包，套接字一秒钟超时
        ret = read_protocol_data(raw, message, &len);
        if (ret == 0 && len >= sizeof(*dmi)) {
            // disc_cfginfo * pdm;
            if (dmi->gw_type != DISCOVERY_TYPE_EMS &&
                dmi->gw_type != DISCOVERY_TYPE_LC) {
                // ems_syslog(LOG_ERR, "Error, invalid discovery MAGIC: %#x\n", dmi->gw_type);
                goto next;
            }

            // pdm = &g_self;
#if 0 // 为了支持EMS500屏蔽
            if (dmi->gw_type == pdm->devtype) {
                // From another CC/LC, we are not interested, just drop it
                goto next;
            }
#endif
            // lock global mutex
            locked = pthread_mutex_lock(&g_disc_lock);
            if (locked != 0) {
                ems_syslog(LOG_ERR, "Failed to lock discovery info: %d", locked);
            }

            detail_discover_data(message, len, ipaddr, cfgdevs);
            if (locked == 0)
                pthread_mutex_unlock(&g_disc_lock);
        }

next:
        now = time(NULL);
        //定时广播自己的信息
        if (now != lastsend) {
            char msgbuf[sizeof(disc_msg) + sizeof(discmsg_basic)];

            lastsend = now;
            memset(ipaddr, 0, sizeof(ipaddr));
            ret = get_localip(netdev, ipaddr);
            if (ret == 0 && ipaddr[0] != '\0') {
                int locked;
                disc_cfginfo * pdm = &g_self;
                // lock global mutex
                locked = pthread_mutex_lock(&g_disc_lock);
                // update network device IP address
                strncpy(pdm->ipaddr, ipaddr, sizeof(pdm->ipaddr));
                if (locked == 0)
                    pthread_mutex_unlock(&g_disc_lock);
            }

            memset(msgbuf, 0, sizeof(msgbuf));
            create_discover_packet(msgbuf, ret == 0 ? ipaddr : NULL);

            package_and_send_discover(raw, msgbuf, sizeof(msgbuf));
            if (g_en_lo_lc != 0)
            {
                memcpy(message, msgbuf, sizeof(msgbuf));//给下面直接解析，作为主从一体的数据
                len = sizeof(msgbuf);
                lo_flag = 1;
            }
            // remove old broadcast devices
            remove_old_broadcast_devs();
        }

        if (isems && (lastread > now || (now - lastread) >= 8)) {
            if (cfgdevs != NULL)
                free_discovery_config(cfgdevs);
            cfgdevs = load_discovery_config(NULL);
            lastread = now;
        }
        //
        if (lo_flag != 0)
        {
            if (g_en_lo_lc != 0) // 直接解析，作为主从一体的数据
            {
                locked = pthread_mutex_lock(&g_disc_lock);
                if (locked != 0) {
                    ems_syslog(LOG_ERR, "Failed to lock discovery info: %d", locked);
                }
                detail_discover_data(message, len, NULL, NULL);
                if (locked == 0)
                    pthread_mutex_unlock(&g_disc_lock);
            }
            lo_flag = 0;
        }
    }

    g_raw = NULL;
    sleep(2);
    raw_config_deinit(raw);
    raw = NULL;
    goto raw_again;
}

// parse configuration file, and add devices to hash list
// caller function should have `g_disc_lock locked
static hash_intptr_t * discover_parse_config(cJSON * jcfg)
{
    cJSON * obj;
    int num, i, ret;
    hash_intptr_t * cfglist = NULL;

    obj = cJSON_GetObjectItem(jcfg, "devices");
    if (obj == NULL || cJSON_IsArray(obj) == 0) {
        goto empty;
    }

    num = cJSON_GetArraySize(obj);
    if (num <= 0) {
empty:
        ems_syslog(LOG_ERR, "Discovery configuration file is empty!");
        return NULL;
    }

    for (i = 0; i < num; ++i) {
        char type[32];
        cJSON * devobj;
        disc_cfginfo * pdev, * old;

        devobj = cJSON_GetArrayItem(obj, i);
        if (devobj == NULL)
            break;

        pdev = (disc_cfginfo *) calloc(0x1, sizeof(*pdev));
        if (pdev == NULL) {
            ems_syslog(LOG_ERR, "System out of memory!");
            break;
        }

        GET_JSON_VALUE_STRING(devobj, "sn", pdev->sn);
        GET_JSON_VALUE_STRING(devobj, "name", pdev->name);
        GET_JSON_VALUE_INT(devobj, "disabled", pdev->disabled);
        GET_JSON_VALUE_STRING(devobj, "ipaddr", pdev->ipaddr);
        GET_JSON_VALUE_STRING(devobj, "no", pdev->no);

        type[0] = '\0';
        GET_JSON_VALUE_STRING(devobj, "type", type);
        if (strcmp(type, "lc") == 0) {
            pdev->devtype = DISCOVERY_TYPE_LC;
        } else if (strcmp(type, "ems") == 0) {
            pdev->devtype = DISCOVERY_TYPE_EMS;
        } else if (strcmp(type, "vlc") == 0)
        {
            pdev->devtype = DISCOVERY_TYPE_VLC;
        } else {
            ems_syslog(LOG_ERR, "Error, invalid type for device: %s", type);
            free(pdev);
            continue;
        }

        old = NULL;
        if ((pdev->sn[0] == '\0') && (pdev->devtype != DISCOVERY_TYPE_VLC)) {
            ems_syslog(LOG_ERR, "Error, no valid sn found for device.");
            free(pdev);
            continue;
        } 
        char hash_key[128] = {};
        if (pdev->devtype == DISCOVERY_TYPE_VLC)
        {
            snprintf(pdev->sn, sizeof(pdev->sn), "%s", g_self.sn);
            snprintf(hash_key, sizeof(hash_key), "%s_%s", g_self.sn, pdev->no);
        }
        else
        {
            snprintf(hash_key, sizeof(hash_key), "%s", pdev->sn);
        }
        if (cfglist != NULL)
            hash_intptr_findptr(cfglist,
                hash_key, strlen(hash_key), (void * *) &old);
        if (old != NULL) {
            ems_syslog(LOG_ERR, "Warning, remove old device: %s", hash_key);
            free(old);
        }

        ret = hash_intptr_addptr(&cfglist, hash_key, strlen(hash_key), pdev);
        if (ret < 0) {
            ems_syslog(LOG_ERR, "Failed to add configure device: %s", hash_key);
            free(pdev);
        }
    }

    return cfglist;
}

struct hash_intptr * load_discovery_config(int * numdevs)
{
    char * pdata;
    cJSON * jcfg;
    const char * pconfig;
    hash_intptr_t * retval;

    jcfg = NULL;
    pdata = NULL;
    retval = NULL;
    // load configuration from `EMS_LC_LIST_FILE;
    pconfig = CONFIG_PATH "/" EMS_LC_LIST_FILE;

    if (numdevs != NULL)
        *numdevs = 0;

    pdata = read_file_data(pconfig);
    if (pdata == NULL)
        return NULL;

    jcfg = json_parse_string_with_comments(pdata);
    // free memory early
    free(pdata);
    pdata = NULL;
    if (jcfg == NULL) {
        ems_syslog(LOG_ERR, "Invalid configuration file: %s", pconfig);
        return NULL;
    }

    retval = discover_parse_config(jcfg);
    cJSON_Delete(jcfg);
    if (numdevs != NULL)
        *numdevs = retval ? hash_intptr_count(retval) : 0;
    return retval;
}

static int free_config_dev(struct hash_intptr * hip, int index, void * gws)
{
    if (hip->hh_ptr != NULL) {
        free(hip->hh_ptr);
        hip->hh_ptr = NULL;
    }
    return 0;
}

void free_discovery_config(struct hash_intptr * pdevs)
{
    if (pdevs == NULL)
        return;

    hash_intptr_iter(&pdevs, free_config_dev, NULL);
    hash_intptr_removeall(&pdevs);
}

int lc_discover_init(const char * sn, unsigned int emstype, int en_lo_lc)
{
    int ret;
    pthread_t tid;
    disc_cfginfo * pdm;

    if (sn == NULL || sn[0] == '\0') {
        ems_syslog(LOG_CRIT, "网关SN为空，请检查是否正确设置SN");
        return -1;
    }

    if (emstype != DISCOVERY_TYPE_EMS && emstype != DISCOVERY_TYPE_LC) {
        ems_syslog(LOG_ERR, "Invalid EMS/LC type value: %#x", emstype);
        return -1;
    }
    g_en_lo_lc = en_lo_lc;
    pdm = &g_self;
    pdm->devtype = emstype;
    strncpy(pdm->sn, sn, sizeof(pdm->sn));

    tid = 0;
    ret = pthread_create(&tid, NULL, lc_discover_task, NULL);
    if (ret != 0) {
        ems_syslog(LOG_CRIT, "创建LC自发现线程失败！");
        return -1;
    }

    pthread_setname_np(tid, "cclcDiscover");
    return 0;
}

// format discovered a list of gateway as JSON buffer
int get_discover_devbuf(disc_devinfo * gws, int num,
    char * output, int maxlen)
{
    int i, j, len = 0;

    if (gws == NULL || num <= 0)
        return 0;
    len = snprintf(output, maxlen, "{\"devices\":[");
    for (i = 0; i < num; ++i) {
        if (i != 0)
            output[len++] = ',';
        j = snprintf(&output[len], maxlen - len,
            "{\"sn\":\"%s\",\"softver\":\"%s\",\"ipaddr\":\"%s\",\"type\":\"%s\",\"timestamp\":%ld,\"islocal\":%d}",
            gws->sn, gws->softver, gws->ipaddr,
            (gws->devtype == DISCOVERY_TYPE_LC) ? "lc" : "ems",
            (long) gws->timestamp, gws->islocal);

        gws++;
        if (j > 0)
            len += j;
        if ((len + 200) >= maxlen)
            break;
    }

    output[len++] = ']';
    output[len++] = '}';
    output[len] = '\0';
    return len;
}

struct cfgbuf_iter {
    char * buf;
    int len, maxlen;
};

const char *devtype_to_string(uint32_t type)
{
    switch (type)
    {
        case DISCOVERY_TYPE_EMS:
        {
            return "ems";
        }
        case DISCOVERY_TYPE_LC:
        {
            return "lc";
        }
        case DISCOVERY_TYPE_VLC:
        {
            return "vlc";
        }
        default:
        {
            return "unknown";
        }
    }
}
static int format_disc_cfg(struct hash_intptr * hip, int index, void * _iter)
{
    int bufsize, ret;
    disc_cfginfo * cfginfo;
    struct cfgbuf_iter * iter;

    iter = (struct cfgbuf_iter *) _iter;
    bufsize = iter->maxlen - iter->len;
    if (bufsize <= 200)
        return -1;

    cfginfo = (disc_cfginfo *) hip->hh_ptr;
    if (cfginfo == NULL)
        return 0;

    if (index != 0) {
        bufsize--;
        iter->buf[iter->len++] = ',';
    }

    ret = snprintf(&iter->buf[iter->len], bufsize,
        "{\"sn\":\"%s\",\"name\":\"%s\",\"no\":\"%s\",\"type\":\"%s\",\"disabled\":%d,\"ipaddr\":\"%s\"}",
        cfginfo->sn, cfginfo->name, cfginfo->no,
        devtype_to_string(cfginfo->devtype),
        cfginfo->disabled, cfginfo->ipaddr);
    if (ret > 0)
        iter->len += ret;
    return 0;
}

int get_discover_cfgbuf(struct hash_intptr * pdevs, int num,
    char * output, int maxlen)
{
    int len = 0;
    struct cfgbuf_iter citer;

    if (pdevs == NULL || num <= 0)
        return 0;

    len = snprintf(output, maxlen, "{\"devices\":[");
    citer.buf = output;
    citer.len = len;
    citer.maxlen = maxlen;
    hash_intptr_iter(&pdevs, format_disc_cfg, (void *) &citer);

    len = citer.len;
    output[len++] = ']';
    output[len++] = '}';
    output[len] = '\0';
    return len;
}

int set_discovery_ipaddr(const char * sn,
    const char * ipaddr, const char * gwip)
{
    RAW_SOCK * raw;
    disc_msg * pmsg;
    discmsg_setip * pip;
    disc_cfginfo * cfginfo;
    char msgbuf[sizeof(disc_msg) + sizeof(discmsg_setip)];

    raw = g_raw;
    if (raw == NULL)
        return -1;
    if (sn == NULL)
        return -1;

    memset(msgbuf, 0, sizeof(msgbuf));
    cfginfo = &g_self;

    pmsg = (disc_msg *) msgbuf;
    pmsg->gw_type = DISCOVERY_TYPE_EMS;
    strncpy(pmsg->dmi_sn, cfginfo->sn, sizeof(pmsg->dmi_sn));
    pmsg->dmi_type = DISCOVERY_MSGTYPE_SETIPADDR;
    pmsg->dmi_len = sizeof(*pip);

    pip = (discmsg_setip *) pmsg->dmi_data;
    local_strlcpy(pip->sn, sn, sizeof(pip->sn));

    if (gwip != NULL)
        local_strlcpy(pip->gwip, gwip, sizeof(pip->gwip));
    if (ipaddr != NULL)
        local_strlcpy(pip->ipaddr, ipaddr, sizeof(pip->ipaddr));
    return package_and_send_discover(raw, msgbuf, sizeof(msgbuf));
}

int set_discovery_reboot(const char * sn, int sysreboot, int appreboot)
{
    RAW_SOCK * raw;
    disc_msg * pmsg;
    discmsg_reboot * boot;
    disc_cfginfo * cfginfo;
    char msgbuf[sizeof(disc_msg) + sizeof(discmsg_reboot)];

    boot = NULL;
    raw = g_raw;
    if (raw == NULL)
        return -1;
    if (sn == NULL)
        return -1;

    memset(msgbuf, 0, sizeof(msgbuf));
    cfginfo = &g_self;

    pmsg = (disc_msg *) msgbuf;
    pmsg->gw_type = DISCOVERY_TYPE_EMS;
    strncpy(pmsg->dmi_sn, cfginfo->sn, sizeof(pmsg->dmi_sn));
    pmsg->dmi_type = DISCOVERY_MSGTYPE_REBOOT;
    pmsg->dmi_len = sizeof(*boot);

    boot = (discmsg_reboot *) pmsg->dmi_data;
    boot->reboot_magic = DISCMSG_REBOOT_MAGIC;
    local_strlcpy(boot->sn, sn, sizeof(boot->sn));
    boot->sys_reboot = (unsigned char) (sysreboot != 0);
    boot->app_reboot = (unsigned char) (appreboot != 0);
    return package_and_send_discover(raw, msgbuf, sizeof(msgbuf));
}

struct iter_no2sn {
    char sn[32];
    const char * no;
};

static int no_2_sn(struct hash_intptr * hip, int index, void * _no2sn)
{
    disc_cfginfo * cfginfo;
    struct iter_no2sn * no2sn;

    no2sn = (struct iter_no2sn *) _no2sn;
    cfginfo = (disc_cfginfo *) hip->hh_ptr;

    if (cfginfo && !cfginfo->disabled &&
        no2sn->sn[0] == '\0' && strcmp(cfginfo->no, no2sn->no) == 0) {
        strncpy(no2sn->sn, cfginfo->sn, sizeof(no2sn->sn));
        return -1;
    }
    return 0;
}

int discover_devno_tosn(const char * devno, char * sn, size_t snlen)
{
    int num;
    hash_intptr_t * list;
    struct iter_no2sn no2sn;

    num = 0;
    list = load_discovery_config(&num);
    if (list == NULL) {
        ems_syslog(LOG_ERR, "No LC devices configured");
        return -1;
    }

    memset(&no2sn, 0, sizeof(no2sn));
    no2sn.no = devno;
    hash_intptr_iter(&list, no_2_sn, &no2sn);
    free_discovery_config(list);
    list = NULL;

    if (no2sn.sn[0] == '\0') {
        ems_syslog(LOG_ERR, "device with 编号 not found: %s", devno);
        return -1;
    }

    strncpy(sn, no2sn.sn, snlen);
    return 0;
}

int discover_sn2info(const char * sn, disc_devinfo * pinfo)
{
    int num, i;
    disc_devinfo * list;

    num = 0;
    list = get_discover_devs(&num);
    if (list == NULL) {
        ems_syslog(LOG_ERR, "Error, no device discovered");
        return -1;
    }

    for (i = 0; i < num; ++i) {
        disc_devinfo * info;
        info = &list[i];
        if (strcmp(info->sn, sn) == 0) {
            memcpy(pinfo, info, sizeof(*pinfo));
            free_discover_devs(list, num);
            return 0;
        }
    }

    free_discover_devs(list, num);
    return -1;
}

static int send_lc_opcua_command(UA_Client * cli, const char * sn,
    const char * command, char * * reply)
{
    int ret, locked;
    char ipaddr[20];
    UA_Variant input[3];
    size_t outputSize;
    UA_Variant *output;
    UA_StatusCode retval;
    UA_String tmp_str;
    const disc_cfginfo * pdm;

    ret = 0;
    output = NULL;
    outputSize = 0;
    UA_Variant_init(&input[0]);
    UA_Variant_init(&input[1]);
    UA_Variant_init(&input[2]);

    pdm = &g_self;
    memset(ipaddr, 0, sizeof(ipaddr));
    // copy ip address with `g_disc_lock locked
    locked = pthread_mutex_lock(&g_disc_lock);
    local_strlcpy(ipaddr, pdm->ipaddr, sizeof(ipaddr));
    if (locked == 0)
        pthread_mutex_unlock(&g_disc_lock);

    tmp_str = UA_STRING((char *) sn);
    UA_Variant_setScalarCopy(&input[0], &tmp_str, &UA_TYPES[UA_TYPES_STRING]);
    tmp_str = UA_STRING((char *) command);
    UA_Variant_setScalarCopy(&input[1], &tmp_str, &UA_TYPES[UA_TYPES_STRING]);
    tmp_str = UA_STRING(ipaddr);
    UA_Variant_setScalarCopy(&input[2], &tmp_str, &UA_TYPES[UA_TYPES_STRING]);

    retval = UA_Client_call(cli, ua_user_string_2_node_id("Advance"),
        UA_NODEID_STRING(1, "lcAgentSet"), 3, input, &outputSize, &output);
    if (retval != UA_STATUSCODE_GOOD) {
        ret = -1;
        ems_syslog(LOG_ERR, "Error, failed to call LC OPC-UA method: %s",
            UA_StatusCode_name(retval));
        goto err0;
    }

    if (output != NULL && outputSize > 0 &&
        UA_Variant_hasScalarType(output, &UA_TYPES[UA_TYPES_STRING])) {
        UA_String msg = *(UA_String *) output->data;
        if (msg.data != NULL && msg.length > 0) {
            char * p = (char *) malloc(msg.length + 1);
            if (p != NULL) {
                memcpy(p, msg.data, msg.length);
                p[msg.length] = '\0';
                *reply = p;
                ems_syslog(LOG_ERR, "lcAgentSet has replied: %s", p);
            }
        }
    }

err0:
    UA_Variant_clear(&input[0]);
    UA_Variant_clear(&input[1]);
    UA_Variant_clear(&input[2]);
    if (output != NULL && outputSize > 0)
        UA_Array_delete(output, outputSize, &UA_TYPES[UA_TYPES_VARIANT]);
    return ret;
}

int discovery_device_reboot(const char * devno, int sysreboot, int appreboot)
{
    int ret;
    char sn[32];
    disc_devinfo dinfo;
    const disc_cfginfo * cfgp;

    sn[0] = '\0';
    memset(&dinfo, 0, sizeof(dinfo));
    ret = discover_devno_tosn(devno, sn, sizeof(sn));
    if (ret >= 0)
        ret = discover_sn2info(sn, &dinfo);
    if (ret < 0) {
        ems_syslog(LOG_ERR, "Device with 编号 not found: %s", devno);
        return -1;
    }

    cfgp = &g_self;
    if (strcmp(cfgp->sn, sn) == 0) {
        ems_syslog(LOG_ERR, "Cannot send broadcast command to self: %s", sn);
        return -1;
    }
    return set_discovery_reboot(sn, sysreboot, appreboot);
}

int discovery_device_cmd(const char * devno,
    const char * command, char * * reply)
{
    int ret;
    UA_Client * cli;
    char sn[32], url[128];
    disc_devinfo dinfo;
    const disc_cfginfo * cfgp;
    struct cli_opcua_option uaopt;

    cli = NULL;
    sn[0] = '\0';
    memset(&dinfo, 0, sizeof(dinfo));
    ret = discover_devno_tosn(devno, sn, sizeof(sn));
    if (ret >= 0)
        ret = discover_sn2info(sn, &dinfo);
    if (ret < 0 || dinfo.ipaddr[0] == '\0') {
        ems_syslog(LOG_ERR, "Device with 编号 IP not found: %s", devno);
        return -1;
    }

    cfgp = &g_self;
    if (strcmp(cfgp->sn, sn) == 0) {
        ems_syslog(LOG_ERR, "Cannot send command to LC self-device: %s", sn);
        return -1;
    }

    lc_opcua_option_default(&uaopt);
    snprintf(url, sizeof(url), "opc.tcp://%s:4841", dinfo.ipaddr);
    lc_opcua_option_setname(&uaopt, url);

    // connect to OPC-UA Server, LC device
    cli = lc_connect_opcua(&uaopt);
    if (cli == NULL) {
        ems_syslog(LOG_ERR, "Error, failed to connect %s", url);
        return -1;
    }

    ret = send_lc_opcua_command(cli, sn, command, reply);
    lc_client_free(cli);
    return ret;
}
