#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include "lrs_service.h"
#include "jsonct.h"
#include "../cc_lc_com/define.h"

typedef struct lrs_list_head
{
    struct lrs_list_head *next, *prev;
} lrs_list_head_t;

static inline void lrs_list_init(lrs_list_head_t *list)
{
    list->next = list;
    list->prev = list;
}

static inline void lrs_list_add_tail(lrs_list_head_t *node, lrs_list_head_t *head)
{
    head->prev->next = node;
    node->prev = head->prev;
    node->next = head;
    head->prev = node;
}

static inline void lrs_list_del(lrs_list_head_t *node)
{
    node->prev->next = node->next;
    node->next->prev = node->prev;
    node->next = node;
    node->prev = node;
}

#define lrs_list_entry(ptr, type, member) \
    ((type *)((char *)(ptr) - (size_t)&(((type *)0)->member)))

#define lrs_list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

#define lrs_list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next)

typedef struct lrs_dev_node {
    lrs_list_head_t list;
    char dev_no[LRS_DEV_NO_LEN];
} lrs_dev_node_t;

typedef struct lrs_dev_group {
    char dev_type[LRS_DEV_TYPE_LEN];
    lrs_list_head_t head;
} lrs_dev_group_t;

typedef struct lrs_tag_node {
    lrs_list_head_t list;
    char dev_type[LRS_DEV_TYPE_LEN];
    char ems_tag_name[LRS_TAG_NAME_LEN];
    char lc_tag_name[LRS_TAG_NAME_LEN];
    int calc_type;
    double cache;
    char pe_func_name[LRS_PE_FUNC_NAME_LEN];
    lrs_tag_cb cb;
    double init_val;
    lrs_calc_val_cb calc_cb;
    int skip_if_no_dev;  // 本轮未遍历到设备时跳过写点位
    int dev_cnt;         // 本轮遍历到的设备数
} lrs_tag_node_t;

static lrs_dev_group_t g_lrs_dev_groups[LRS_DEV_TABLE_SIZE];
static lrs_list_head_t g_lrs_tag_list;
static pthread_mutex_t g_lrs_mutex = PTHREAD_MUTEX_INITIALIZER;

static pthread_t g_lrs_thread;
static volatile int g_lrs_running = 0;
static int g_lrs_interval_ms = LRS_DEFAULT_INTERVAL_MS;
static int g_lrs_wakeup_fds[2] = { -1, -1 };

static lrs_dev_group_t *lrs_find_group(const char *dev_type)
{
    int i;
    for (i = 0; i < LRS_DEV_TABLE_SIZE; i++) {
        if (g_lrs_dev_groups[i].dev_type[0] != '\0' &&
            strcmp(g_lrs_dev_groups[i].dev_type, dev_type) == 0) {
            return &g_lrs_dev_groups[i];
        }
    }
    return NULL;
}

static void lrs_calc_val(lrs_tag_node_t *tnode, double new_val)
{
    switch (tnode->calc_type) {
        case LRS_CT_AND:
            tnode->cache = (double)((int)(tnode->cache) & (int)new_val);
            break;
        case LRS_CT_OR:
            tnode->cache = (double)((int)(tnode->cache) | (int)new_val);
            break;
        case LRS_CT_ADD:
            tnode->cache = tnode->cache + new_val;
            break;
        case LRS_CT_USE_CALC_CB:
            if (tnode->calc_cb) {
                tnode->calc_cb(tnode->cache, new_val, &(tnode->cache));
            }
            break;
        default:
            return;
    }
}

static void lrs_read_real_lc_tag(lrs_tag_node_t *tnode)
{
    int ctrl_mode = get_cabinet_info()->ctrl_mode;
    double tmp = 0.0;

    if (tnode->ems_tag_name[0] == '\0') {
        return;
    }

    if (ctrl_mode == EMS_MODE_ZC) {
        int lc_sum = get_pcs_ctrl_var()->lc_list_len;
        lc_it *lc_arr = get_pcs_ctrl_var()->lc_list;

        for (int i = 0; i < lc_sum; i++) {
            if(lc_arr[i].type != eLC_TYPE_VLC) {
                tmp = dev_get_dev_tag_float(lc_arr[i].no, tnode->ems_tag_name) ;              
                ems_syslog(LOG_DEBUG, "[LRS]read(%s) tag name : %s->%lf", lc_arr[i].no, tnode->ems_tag_name, tmp);
                lrs_calc_val(tnode, tmp);
            }
        }
    }
}

static void lrs_update_vlc_tag_val(lrs_tag_node_t *tnode, const char *dev_no, double new_val)
{
    int pos = 0;
    char no[LRS_DEV_NO_LEN + 1] = {0};
    char *p = NULL;

    if (dev_no == NULL || tnode == NULL || tnode->ems_tag_name[0] == '\0') {
        return;
    }

    p = strchr(dev_no, '.');
    if (p == NULL) {
        return;
    }
    pos = p - dev_no;
    memcpy(no, dev_no, (pos > LRS_DEV_NO_LEN ? LRS_DEV_NO_LEN : pos));

    ems_syslog(LOG_DEBUG, "[LRS]set vlc[%s] tag name[%s]->%lf", no, tnode->ems_tag_name, new_val);
    dev_set_dev_tag_float(no, tnode->ems_tag_name, new_val);
}

static void lrs_read_one(lrs_tag_node_t *tnode, const char *dev_no)
{
    double tmp = 0.0;
    int ret = -1;

    if (tnode->pe_func_name[0]) {
        ret = dev_get_function_double(dev_no, tnode->pe_func_name, &tmp);
        ems_syslog(LOG_DEBUG, "[LRS]call(%s) func : %s->%lf[ret=%d]", dev_no, tnode->pe_func_name, tmp, ret);
    } 

    if (ret < 0) {
        if (tnode->lc_tag_name[0]) {
            tmp = dev_get_dev_tag_float(dev_no, tnode->lc_tag_name);
            ems_syslog(LOG_DEBUG, "[LRS]read(%s) tag name : %s->%lf", dev_no, tnode->lc_tag_name, tmp);
            ret = 0;
        }
    }

    if (ret < 0) {
        ems_syslog(LOG_ERR, "[LRS]read(%s) func[%s] tag name[%s] failed", dev_no, (tnode->pe_func_name[0] ? tnode->pe_func_name : "NULL"), (tnode->lc_tag_name[0] ? tnode->lc_tag_name : "NULL"));
    } else {
        lrs_calc_val(tnode, tmp);
        lrs_update_vlc_tag_val(tnode, dev_no, tmp);
    }
}

static void common_update_ems_tag_status(const char *dev_type, const char *ems_tag_name, double val)
{
    (void)dev_type;
    if (ems_tag_name[0]) {
        dev_set_dev_tag_float(DEV_NO_EMS, ems_tag_name, val);
    }
}

static void lrs_round_read(void)
{
    lrs_list_head_t *pos, *n;

    pthread_mutex_lock(&g_lrs_mutex);
    lrs_list_for_each_safe(pos, n, &g_lrs_tag_list) {
        lrs_tag_node_t *node = lrs_list_entry(pos, lrs_tag_node_t, list);
        lrs_dev_group_t *grp = lrs_find_group(node->dev_type);
        node->dev_cnt = 0;
        if (grp) {
            lrs_list_head_t *dpos, *dn;
            lrs_list_for_each_safe(dpos, dn, &grp->head) {
                lrs_dev_node_t *dev = lrs_list_entry(dpos, lrs_dev_node_t, list);
                node->dev_cnt++;
                lrs_read_one(node, dev->dev_no);
            }
        }

        lrs_read_real_lc_tag(node);
        if (node->skip_if_no_dev && node->dev_cnt == 0) {
            node->cache = node->init_val;   // 未遍历到设备:仅重置缓存,不写点位,避免与同点位规则互相覆盖
            continue;
        }
        if (node->cb) {
            node->cb(node->dev_type, node->ems_tag_name, node->cache);
            node->cache = node->init_val;
        }
    }
    pthread_mutex_unlock(&g_lrs_mutex);
}

static void *lrs_thread_main(void *arg)
{
    (void)arg;
    fd_set rfds = {0};
    struct timeval tv = {0};
    int ret = 0;

    while (g_lrs_running) {
        FD_ZERO(&rfds);
        FD_SET(g_lrs_wakeup_fds[0], &rfds);
        pthread_mutex_lock(&g_lrs_mutex);
        tv.tv_sec = g_lrs_interval_ms / 1000;
        tv.tv_usec = (g_lrs_interval_ms % 1000) * 1000;
        pthread_mutex_unlock(&g_lrs_mutex);

        ret = select(g_lrs_wakeup_fds[0] + 1, &rfds, NULL, NULL, &tv);
        if (ret < 0) {
            if (errno == EINTR) {
                continue;
            }
            break;
        }
        if (ret > 0 && FD_ISSET(g_lrs_wakeup_fds[0], &rfds)) {
            char buf[16];
            /* 收到唤醒，清空管道 */
            while (read(g_lrs_wakeup_fds[0], buf, sizeof(buf)) > 0);
            if (!g_lrs_running) {
                break;
            }
            continue;
        }

        lrs_round_read();
    }
    return NULL;
}

int lrs_start(void)
{
    if (g_lrs_running) {
        return 0;
    }
    if (pipe(g_lrs_wakeup_fds) != 0) {
        return -1;
    }
    g_lrs_running = 1;
    if (pthread_create(&g_lrs_thread, NULL, lrs_thread_main, NULL) != 0) {
        g_lrs_running = 0;
        close(g_lrs_wakeup_fds[0]);
        close(g_lrs_wakeup_fds[1]);
        g_lrs_wakeup_fds[0] = g_lrs_wakeup_fds[1] = -1;
        return -1;
    }

    return 0;
}

static int lrs_stop(void)
{
    if (!g_lrs_running) {
        return 0;
    }
    g_lrs_running = 0;
    if (g_lrs_wakeup_fds[1] >= 0) {
        char c = 1;
        write(g_lrs_wakeup_fds[1], &c, 1);
    }
    pthread_join(g_lrs_thread, NULL);
    close(g_lrs_wakeup_fds[0]);
    close(g_lrs_wakeup_fds[1]);
    g_lrs_wakeup_fds[0] = g_lrs_wakeup_fds[1] = -1;

    return 0;
}

int lrs_init(void)
{
    int i;

    pthread_mutex_lock(&g_lrs_mutex);
    lrs_list_init(&g_lrs_tag_list);
    for (i = 0; i < LRS_DEV_TABLE_SIZE; i++) {
        g_lrs_dev_groups[i].dev_type[0] = '\0';
        lrs_list_init(&g_lrs_dev_groups[i].head);
    }
    g_lrs_interval_ms = LRS_DEFAULT_INTERVAL_MS;
    pthread_mutex_unlock(&g_lrs_mutex);

    return 0;
}

void lrs_deinit(void)
{
    lrs_list_head_t *pos, *n;
    int i;

    lrs_stop();

    pthread_mutex_lock(&g_lrs_mutex);
    lrs_list_for_each_safe(pos, n, &g_lrs_tag_list) {
        lrs_tag_node_t *tag = lrs_list_entry(pos, lrs_tag_node_t, list);
        lrs_list_del(&tag->list);
        free(tag);
    }
    for (i = 0; i < LRS_DEV_TABLE_SIZE; i++) {
        lrs_list_for_each_safe(pos, n, &g_lrs_dev_groups[i].head) {
            lrs_dev_node_t *dev = lrs_list_entry(pos, lrs_dev_node_t, list);
            lrs_list_del(&dev->list);
            free(dev);
        }
        g_lrs_dev_groups[i].dev_type[0] = '\0';
        lrs_list_init(&g_lrs_dev_groups[i].head);
    }
    g_lrs_interval_ms = LRS_DEFAULT_INTERVAL_MS;
    pthread_mutex_unlock(&g_lrs_mutex);
}

int lrs_update_dev_no(const char *dev_type, const char *dev_no)
{
    lrs_dev_group_t *grp = NULL;
    lrs_list_head_t *pos = NULL;
    int i = 0;

    if (!dev_type || !dev_no || dev_type[0] == '\0' || dev_no[0] == '\0') {
        return -1;
    }

    ems_syslog(LOG_INFO, "[LRS]update dev type:%s, dev no : %s", dev_type, dev_no);
    pthread_mutex_lock(&g_lrs_mutex);
    grp = lrs_find_group(dev_type);
    if (!grp) {
        for (i = 0; i < LRS_DEV_TABLE_SIZE; i++) {
            if (g_lrs_dev_groups[i].dev_type[0] == '\0') {
                grp = &g_lrs_dev_groups[i];
                snprintf(grp->dev_type, sizeof(grp->dev_type), "%s", dev_type);
                lrs_list_init(&grp->head);
                break;
            }
        }
        if (!grp) {
            pthread_mutex_unlock(&g_lrs_mutex);
            return -1;
        }
    }

    lrs_list_for_each(pos, &grp->head) {
        lrs_dev_node_t *dev = lrs_list_entry(pos, lrs_dev_node_t, list);
        if (strcmp(dev->dev_no, dev_no) == 0) {
            pthread_mutex_unlock(&g_lrs_mutex);
            return 0;
        }
    }

    lrs_dev_node_t *node = calloc(1, sizeof(*node));
    if (!node) {
        pthread_mutex_unlock(&g_lrs_mutex);
        return -1;
    }

    snprintf(node->dev_no, sizeof(node->dev_no), "%s", dev_no);
    lrs_list_add_tail(&node->list, &grp->head);
    pthread_mutex_unlock(&g_lrs_mutex);

    return 0;
}

int lrs_register_tag(lrs_tag_info_t *info)
{
    lrs_tag_node_t *node = NULL;

    if (!info || !info->dev_type) {
        return -1;
    }

    node = calloc(1, sizeof(*node));
    if (!node) {
        return -1;
    }
    snprintf(node->dev_type, sizeof(node->dev_type), "%s", info->dev_type);

    if (info->ems_tag_name) {
        snprintf(node->ems_tag_name, sizeof(node->ems_tag_name), "%s", info->ems_tag_name);
    }
    if (info->lc_tag_name) {
        snprintf(node->lc_tag_name, sizeof(node->lc_tag_name), "%s", info->lc_tag_name);
    }
    if (info->pe_func_name) {
        snprintf(node->pe_func_name, sizeof(node->pe_func_name), "%s", info->pe_func_name);
    }

    node->calc_type = info->calc_type;
    if (info->cb) {
        node->cb = info->cb;
    } else {
        node->cb = common_update_ems_tag_status;
    }
    node->init_val = info->init_val;
    node->cache = info->init_val;
    node->calc_cb = info->calc_cb;
    node->skip_if_no_dev = info->skip_if_no_dev;
    node->dev_cnt = 0;

    pthread_mutex_lock(&g_lrs_mutex);
    lrs_list_add_tail(&node->list, &g_lrs_tag_list);
    pthread_mutex_unlock(&g_lrs_mutex);

    return 0;
}

int lrs_set_interval(int ms)
{
    if (ms < LRS_MIN_INTERVAL_MS) {
        return -1;
    }
    pthread_mutex_lock(&g_lrs_mutex);
    g_lrs_interval_ms = ms;
    pthread_mutex_unlock(&g_lrs_mutex);
    return 0;
}

