#include "lip/app/opcua/lip_opcua_cfg_defs.h"
#if LIP_OPCUA_SERVER_SUBS_ENABLE
#include "lip_opcua_server_monitem.h"
#include "lip_opcua_server_subscriptions.h"
#include "lip_opcua_server_monitem_queue.h"
#include "lip/app/opcua/lip_opcua_err_codes.h"
#include "lip/app/opcua/server/tree/lip_opcua_server_tree.h"
#include "lip/app/opcua/server/lip_opcua_server_time.h"

#define LIP_OPCUA_MONITEM_INVALID_ID 0

LIP_OPCUA_SERVER_MONITEMS_MEM(static t_lip_opcua_monitem_ctx f_monitmes[LIP_OPCUA_SERVER_MONITEM_MAX_CNT]);
static t_lip_opcua_intid f_last_monitem_id;


static t_lip_opcua_err f_check_mon_params(struct st_lip_opcua_srvsub_ctx *sub, t_lip_opcua_monitem_ctx *monitem, t_lip_opcua_monitoring_params *mon_params) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    /* SamplingInterval < 0 означает, что значение интервала берется из настроек подписки
     * Этот интервал не должен изменяться в случае ручного изменения интервала публикаций для подписки,
     * поэтому корректно его сразу рассчитать тут */
    if (mon_params->sampling_interval < 0) {
        mon_params->sampling_interval = lip_opcua_server_sub_publish_interval(sub);
    }

    if ((err == LIP_OPCUA_ERR_GOOD) && monitem->mon_cbs->check_params) {
        err = monitem->mon_cbs->check_params(monitem, mon_params);
    }

    return err;
}

static bool f_item_sampling_support(const t_lip_opcua_monitem_ctx *monitem) {
    return monitem->item && monitem->mon_cbs->sample && (ltimer_interval(&monitem->sampling_tmr) > 0);
}

void lip_opcua_server_monitem_init(void) {
    f_last_monitem_id = 1; /** @todo random */
    for (size_t item_idx = 0; (item_idx < LIP_OPCUA_SERVER_MONITEM_MAX_CNT); ++item_idx) {
        f_monitmes[item_idx].item = NULL;
        f_monitmes[item_idx].id = LIP_OPCUA_MONITEM_INVALID_ID;
    }
    lip_opcua_server_monitem_queues_init();
}


t_lip_opcua_monitem_ctx *f_get_free_monitem(void) {
    t_lip_opcua_monitem_ctx *ret = NULL;
    for (size_t item_idx = 0; (item_idx < LIP_OPCUA_SERVER_MONITEM_MAX_CNT) && (ret == NULL); ++item_idx) {
        t_lip_opcua_monitem_ctx *check_item = &f_monitmes[item_idx];
        if (check_item->id == LIP_OPCUA_MONITEM_INVALID_ID) {
            ret = check_item;
        }
    }
    return ret;
}

static t_lip_opcua_err f_start_sampling(t_lip_opcua_monitem_ctx *monitem, t_lclock_ticks time) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    if (monitem->item) {
        const t_lip_opcua_tree_item_mon_cbs *mon = monitem->mon_cbs;
        if (mon->start) {
            err = mon->start(monitem, time);
        }
    }
    /* запускаем со времени на интервал раньше текущего, т.к. первое значение должны прочитать
     * как можно раньше, таким образом у нас сразу таймер в состоянии expired */
    ltimer_restart_at(&monitem->sampling_tmr, time - monitem->sampling_tmr.interval);
    return err;
}

static t_lip_opcua_err f_stop_sampling(t_lip_opcua_monitem_ctx *monitem, t_lclock_ticks time) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    if (monitem->item) {
        const t_lip_opcua_tree_item_mon_cbs *mon = monitem->mon_cbs;
        if (mon->stop) {
            err = mon->stop(monitem);
        }
    }
    return err;
}

void lip_opcua_server_monitem_process_item_delete(const t_lip_opcua_tree_item *item) {
    for (unsigned item_idx = 0; (item_idx < LIP_OPCUA_SERVER_MONITEM_MAX_CNT); ++item_idx) {
        t_lip_opcua_monitem_ctx *monctx = &f_monitmes[item_idx];
        if ((monctx->id != LIP_OPCUA_MONITEM_INVALID_ID) &&
            (monctx->item == item)) {

            if (monctx->mon_mode != LIP_OPCUA_MONMODE_DISABLED) {
                f_stop_sampling(monctx, lclock_get_ticks());
            }

            monctx->item = NULL;
            t_lip_opcua_notification_info *notify = lip_opcua_server_monitem_put_start(&monctx->notifications);
            if (notify) {
                /* записываем новые данные в очередь оповещения со статусом, что больше такого nodeid нет */
                notify->type = LIP_OPCUA_NOTIFICATION_TYPE_DATA_CHANGE;
                lip_opcua_variant_set_null(&notify->data.value);
                notify->data.status = LIP_OPCUA_ERR_BAD_NODEID_UNKNOWN;
                lip_opcua_get_cur_tstmp(&notify->data.srv_time);
                lip_opcua_server_monitem_put_finish(&monctx->notifications);
            }
        }
    }
}

void lip_opcua_server_monitem_process_item_add(const t_lip_opcua_tree_item *item) {
    for (unsigned item_idx = 0; (item_idx < LIP_OPCUA_SERVER_MONITEM_MAX_CNT); ++item_idx) {
        t_lip_opcua_monitem_ctx *monctx = &f_monitmes[item_idx];
        if ((monctx->id != LIP_OPCUA_MONITEM_INVALID_ID) && !monctx->item &&
            lip_opcua_nodeid_is_eq(&item->nodeid, &monctx->node_id)) {
            monctx->item = item;
            if (monctx->mon_mode != LIP_OPCUA_MONMODE_DISABLED) {
                f_start_sampling(monctx, lclock_get_ticks());
            }

        }
    }
}

t_lip_opcua_err lip_opcua_server_monitem_create(struct st_lip_opcua_session_ctx *session,
                                                struct st_lip_opcua_srvsub_ctx *sub,
                                                t_lip_opcua_monitem_create_params *params,
                                                t_lip_opcua_monitem_ctx **pmonitem) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    t_lip_opcua_monitem_ctx *monitem = f_get_free_monitem();
    if (!monitem) {
        err = LIP_OPCUA_ERR_BAD_TOO_MANY_MONITORED_ITEMS;
    } else {
        err = lip_opcua_server_tree_get_item(session, &params->rd_value_id.nodeid, &monitem->item);
        if (err == LIP_OPCUA_ERR_GOOD) {
            const t_lip_opcua_tree_item_descr_variable *var_descr = lip_opcua_tree_item_variable_descr(monitem->item);
            const t_lip_opcua_tree_item_mon_cbs *mon_cbs = NULL;
            monitem->client_handle = params->mon_params.client_handle;
            monitem->mon_mode = LIP_OPCUA_MONMODE_DISABLED;
            monitem->node_id = params->rd_value_id.nodeid;
            monitem->attr = params->rd_value_id.attr;
            monitem->rd_params.tstmps = params->tstmps;

            if (!var_descr) {
                /** @todo реализация подписки на события для объектов */
                err = LIP_OPCUA_ERR_BAD_NODEID_INVALID;
            } else if (monitem->attr == LIP_OPCUA_ATTR_ID_VALUE) {
                if (var_descr->cbs.mon) {
                    mon_cbs = var_descr->cbs.mon;
                } else {
                    mon_cbs = &g_lip_opcua_tree_item_mon_cbs_def_value_read;
                }
            } else {
                /** @todo */
                err = LIP_OPCUA_ERR_BAD_ATTRIBUTEID_INVALID;
            }

            if ((err == LIP_OPCUA_ERR_GOOD) && !mon_cbs) {
                err = LIP_OPCUA_ERR_BAD_SERVICE_UNSUPPORTED;
            } else {
                monitem->mon_cbs = mon_cbs;
            }

            if ((err == LIP_OPCUA_ERR_GOOD) && mon_cbs->create) {
                err = mon_cbs->create(monitem);
            }
            if (err == LIP_OPCUA_ERR_GOOD) {
                err = f_check_mon_params(sub, monitem, &params->mon_params);

                if (err == LIP_OPCUA_ERR_GOOD) {

                    /* ищем незанятый для текущей сессии id, отличный от LIP_OPCUA_MONITEM_INVALID_ID */
                    do {
                        ++f_last_monitem_id;
                    } while ((f_last_monitem_id == LIP_OPCUA_MONITEM_INVALID_ID)
                             || (lip_opcua_server_sub_monitem_get(sub, f_last_monitem_id, NULL) == LIP_OPCUA_ERR_GOOD));


                    monitem->id = f_last_monitem_id;
                    lip_opcua_server_monitem_queue_init(&monitem->notifications, params->mon_params.queue_size, params->mon_params.discart_oldest);
                    /* возвращаем скорректированный размер очереди */
                    params->mon_params.queue_size = monitem->notifications.size;

                    lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_DEBUG_MEDIUM, 0, "lip opcua: monitem create. id %d, item %d:%d, attr %d\n",
                               monitem->id, monitem->item->nodeid.ns_idx, monitem->item->nodeid.intval, monitem->attr);

                    uint32_t tout_ms = params->mon_params.sampling_interval;
                    ltimer_set_ms(&monitem->sampling_tmr, tout_ms);

                    lip_opcua_server_monitem_set_monmode(monitem, params->mon_mode, params->req_time);

                    if (pmonitem) {
                        *pmonitem = monitem;
                    }
                }

                if (err != LIP_OPCUA_ERR_GOOD) {
                    if (mon_cbs->del) {
                        mon_cbs->del(monitem);
                    }
                }
            }
        }
    }

    return err;
}

void lip_opcua_server_monitem_delete(t_lip_opcua_monitem_ctx *monitem) {
    lip_opcua_server_monitem_set_monmode(monitem, LIP_OPCUA_MONMODE_DISABLED, lclock_get_ticks());
    lip_opcua_server_monitem_queue_delete(&monitem->notifications);
    if (monitem->item) {
        const t_lip_opcua_tree_item_mon_cbs *mon = monitem->mon_cbs;
        if (mon->del) {
            mon->del(monitem);
        }
        lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_DEBUG_MEDIUM, 0, "lip opcua: monitem delete. id %d\n", monitem->id);
        monitem->item = NULL;
    }
    monitem->id = LIP_OPCUA_MONITEM_INVALID_ID;
}

t_lip_opcua_err lip_opcua_server_monitem_sample(t_lip_opcua_monitem_ctx *monitem, struct st_lip_opcua_session_ctx *session, t_lclock_ticks time) {
    t_lip_opcua_err rd_state;
    if (!monitem->item || !monitem->mon_cbs->sample) {
        rd_state = LIP_OPCUA_ERR_BAD_NOT_SUPPORTED;
    } else {
        t_lip_opcua_data_value val;
        memset(&val, 0, sizeof(val));

        rd_state = monitem->mon_cbs->sample(monitem, session, &monitem->rd_params, time, &val);
        if (rd_state != LIP_OPCUA_ERR_GOOD_NO_DATA) {
            t_lip_opcua_notification_info *notify = lip_opcua_server_monitem_put_start(&monitem->notifications);
            if (notify) {
                notify->type = LIP_OPCUA_NOTIFICATION_TYPE_DATA_CHANGE;
                notify->data = val;
                if (rd_state != LIP_OPCUA_ERR_GOOD) {
                    notify->data.status = rd_state;
                }
                lip_opcua_server_monitem_put_finish(&monitem->notifications);
            }
        }
    }
    return rd_state;
}

void lip_opcua_server_monitem_pull(t_lip_opcua_monitem_ctx *monitem, struct st_lip_opcua_session_ctx *session, struct st_lip_opcua_srvsub_ctx *sub, t_lclock_ticks time) {
    if ((monitem->mon_mode != LIP_OPCUA_MONMODE_DISABLED) && monitem->item) {
        if (f_item_sampling_support(monitem) && ltimer_expired_at(&monitem->sampling_tmr, time)) {
            lip_opcua_server_monitem_sample(monitem, session, ltimer_expiration_time(&monitem->sampling_tmr));
            ltimer_reset(&monitem->sampling_tmr);
        }
    }
}

t_lip_opcua_err lip_opcua_server_monitem_set_monmode(t_lip_opcua_monitem_ctx *monitem, t_lip_opcua_monmode mode, t_lclock_ticks req_time) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    if (monitem->mon_mode != mode) {
        monitem->mon_mode = mode;
        if (mode == LIP_OPCUA_MONMODE_DISABLED) {
            f_stop_sampling(monitem, req_time);
            lip_opcua_server_monitem_queue_clear(&monitem->notifications);
        } else {
            f_start_sampling(monitem, req_time);
        }
    }
    return err;
}

t_lip_opcua_err lip_opcua_server_monitem_set_monitoring_params(struct st_lip_opcua_srvsub_ctx *sub, t_lip_opcua_monitem_ctx *monitem, t_lip_opcua_monitoring_params *mon_params, t_lip_opcua_ret_tstmp_types tstmps, t_lclock_ticks req_time) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    err = f_check_mon_params(sub, monitem, mon_params);
    if (err == LIP_OPCUA_ERR_GOOD) {
        /** @todo  check tstmps */
        monitem->rd_params.tstmps = tstmps;
        /* обновляем размер очереди, если изменилась */
        lip_opcua_server_monitem_queue_update_size(&monitem->notifications, mon_params->queue_size, mon_params->discart_oldest);
        /* возвращаем скорректированный размер очереди */
        mon_params->queue_size = monitem->notifications.size;

        /* если изменился интервал опроса, то перезапускаем таймер с времени запроса (чтобы одновременно для всех элементов запроса) */
        t_lclock_ticks tmr_interval = LTIMER_MS_TO_CLOCK_TICKS(mon_params->sampling_interval);
        if (tmr_interval != monitem->sampling_tmr.interval) {
            ltimer_set_at(&monitem->sampling_tmr, tmr_interval, req_time);
        }
    }

    return err;
}

void lip_opcua_server_monitem_notification_pubrdy(t_lip_opcua_monitem_ctx *monitem) {
    if (monitem->mon_mode == LIP_OPCUA_MONMODE_REPORTING) {
        monitem->notifications.pub_rdy = monitem->notifications.rdy_cnt;
    }
}


t_lip_opcua_notification_info *lip_opcua_server_monitem_next_notification(t_lip_opcua_monitem_ctx *monitem) {
    t_lip_opcua_notification_info *ret = NULL;
    if (monitem->notifications.pub_rdy > 0) {
        ret = lip_opcua_server_monitem_queue_get_start(&monitem->notifications);
    }
    return ret;
}
void lip_opcua_server_monitem_drop_notification(t_lip_opcua_monitem_ctx *monitem) {
    if (monitem->notifications.pub_rdy > 0) {
        lip_opcua_server_monitem_queue_get_finish(&monitem->notifications);
    }
}



#endif
