#include "lip/app/opcua/server/lip_opcua_server_cfg_defs.h"
#if LIP_OPCUA_SERVER_SUBS_ENABLE
#include "lip_opcua_server_subscriptions.h"
#include "lip/misc/lip_misc_defs.h"
#include "lip/app/opcua/lip_opcua_err_codes.h"
#include "lip/app/opcua/server/monitems/lip_opcua_server_monitem.h"
#include "lip/app/opcua/server/lip_opcua_server_session.h"
#include "lip/app/opcua/server/lip_opcua_server_time.h"
#include "lip/app/opcua/lip_opcua_std_nodeids.h"
#include "lip/app/opcua/binary/lip_opcua_binary_proto_defs.h"
#include "lip/app/opcua/server/lip_opcua_server_con.h"

static t_lip_opcua_intid f_last_id; /** @todo - random an power on */


static inline void f_next_seqnum(t_lip_opcua_srvsub_ctx *sub) {
    if (++sub->next_seqnum == 0) {
        ++sub->next_seqnum;
    }
}


static t_lip_opcua_err f_sent_keepalive(t_lip_opcua_srvsub_ctx *sub, struct st_lip_opcua_session_ctx *session) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    t_lip_proc_buf *buf = lip_opcua_server_session_publish_resp_prepare(session, sub->id, NULL);    
    if (!buf) {
        err = LIP_OPCUA_ERR_BAD_TCP_SERVER_TOO_BUSY;
    } else {
        err = lip_opcua_procbuf_put_counter(buf, sub->next_seqnum);
        if (err == LIP_OPCUA_ERR_GOOD) {
            t_lip_opcua_tstmp time;
            lip_opcua_get_cur_tstmp(&time);
            err = lip_opcua_procbuf_put_datetime(buf, time.ns100);
        }
        if (err == LIP_OPCUA_ERR_GOOD) {
            err = lip_opcua_procbuf_put_arrsize_null(buf);
        }

        if (err == LIP_OPCUA_ERR_GOOD) {
            err = lip_opcua_server_session_publish_resp_finish(session, buf);
        }

        if (err == LIP_OPCUA_ERR_GOOD) {
            sub->flags |= LIP_OPCUA_SRVSUB_FLAG_FIRST_MSG_SENT;
            sub->curKeepAliveCnt = 0;
            /* !!для keep alive не нужно увеличивать sequenceNumber */
            lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_DEBUG_MEDIUM, 0, "lip opcua: sub %d: send keepalive!\n", sub->id);
        }
    }
    return err;
}

static void f_set_items_pubrdy(t_lip_opcua_srvsub_ctx *sub) {
    int total_pubrdy_cnt = 0;
    for (int monitem_idx = 0; (monitem_idx < sub->monitems_cnt); ++monitem_idx) {
        t_lip_opcua_monitem_ctx *monitem = sub->monitems[monitem_idx];
        lip_opcua_server_monitem_notification_pubrdy(monitem);
        uint16_t item_pubrdy_cnt = lip_opcua_server_monitem_pubrdy_items_cnt(monitem);
        total_pubrdy_cnt += item_pubrdy_cnt;
    }
    sub->pubRdyNotification = total_pubrdy_cnt;
}


static t_lip_opcua_err f_sent_notifications(t_lip_opcua_srvsub_ctx *sub, struct st_lip_opcua_session_ctx *session) {
    static const t_lip_opcua_nodeid f_notify_data_change_id = LIP_OPCUA_NODEID_STDNS_INT(LIP_OPCUA_NODEID_STD_DATA_CHANGE_NOTIFICATION_BIN);
    //static const t_lip_opcua_nodeid f_notify_events_id = LIP_OPCUA_NODEID_STDNS_INT(LIP_OPCUA_NODEID_STD_EVENT_NOTIFICATION_LIST_BIN);

    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    /* сохраняем предыдущую позицию, в которую был записан MoreNotify, чтобы если они есть,
         * можно было подменить в данных */
    t_lip_proc_buf more_notify_buf;
    t_lip_proc_buf *buf = lip_opcua_server_session_publish_resp_prepare(session, sub->id, &more_notify_buf);
    if (!buf) {
        err = LIP_OPCUA_ERR_BAD_TCP_SERVER_TOO_BUSY;
    } else {
        err = lip_opcua_procbuf_put_counter(buf, sub->next_seqnum);
        if (err == LIP_OPCUA_ERR_GOOD) {
            t_lip_opcua_tstmp time;
            lip_opcua_get_cur_tstmp(&time);
            err = lip_opcua_procbuf_put_datetime(buf, time.ns100);
        }
        if (err == LIP_OPCUA_ERR_GOOD) {
            /* так как заранее не знаем количество нотификаций, то записываем нулевой
             * размер и сохраняем позицию для дальнейшей подстановки
             * (может быть до 2 элементов - DataChange на все данные,
             * и EventList на все события (сейчас не поддкрживается) */
            t_lip_proc_buf put_size_buf = *buf;
            int notify_cnt = 0;
            err = lip_opcua_procbuf_put_arrsize_null(buf);

            /* информация для сохранения DataChange */
            unsigned data_items_cnt = 0;
            t_lip_proc_buf data_change_obj_size_buf;
            t_lip_proc_buf data_items_size_buf;

            bool out = !(sub->pubRdyNotification > 0);
            unsigned notrdy_items_cnt = 0;
            while (!out && (err == LIP_OPCUA_ERR_GOOD)) {
                /* для равномерного опроса элементов используем сохраняемый между вызовами номер
                 * следующего опрашиваемого элемента monitem_pubreq_pos, и кладем за один проход только
                 * по одной нотификации из каждого элемента, но делаем проходы пока не кончаться нотификации,
                 * или место в пакете */
                t_lip_opcua_monitem_ctx *monitem = sub->monitems[sub->monitem_pubreq_pos];
                bool item_has_put_err = false;
                int rdy_cnt = lip_opcua_server_monitem_pubrdy_items_cnt(monitem);
                if (rdy_cnt > 0) {
                    t_lip_opcua_notification_info *notify = lip_opcua_server_monitem_next_notification(monitem);
                    if (notify) {
                        if (notify->type == LIP_OPCUA_NOTIFICATION_TYPE_DATA_CHANGE) {
                            /* по первому обнаруженному DataChange notification сохраняем
                             * начало общего сообщения DataChange на все изменения данных всех объектов */
                            if (data_items_cnt == 0) {
                                /* Сохраняем заголовок extensionObject, однако в данный момент мы не знаем
                                 * размера теала, из-за чего сохраняем нулевой размер и позицию в буфере
                                 * для его дальнейшей замены */
                                err = lip_opcua_procbuf_put_extobj_info(buf, &f_notify_data_change_id, &data_change_obj_size_buf);

                                /* так как количество элементов в массиве monitoredItems сейчас не знаем,
                                 * то также записываем пустой размер и сохраняем позцию */
                                if (err == LIP_OPCUA_ERR_GOOD) {
                                    data_items_size_buf = *buf;
                                    err = lip_opcua_procbuf_put_arrsize_null(buf);
                                }

                            }

                            if (err == LIP_OPCUA_ERR_GOOD) {
                                t_lip_proc_buf item_buf = *buf;
                                t_lip_opcua_err item_err = lip_opcua_procbuf_put_intid(&item_buf, monitem->client_handle);
                                if (item_err == LIP_OPCUA_ERR_GOOD) {
                                    item_err = lip_opcua_procbuf_put_datavalue(&item_buf, &notify->data, monitem->rd_params.tstmps, session);
                                }
                                if (item_err == LIP_OPCUA_ERR_GOOD) {
                                    ++data_items_cnt;
                                    *buf = item_buf;
                                    lip_opcua_server_monitem_drop_notification(monitem);
                                    if (--sub->pubRdyNotification == 0) {
                                        out = true;
                                    }

                                    /* если maxNotificationPerPublish не ноль, то при достижении данного количества нотификаций
                                     * завершаем опрос */
                                    if ((sub->maxNotificationPerPublish != 0) && (data_items_cnt >= sub->maxNotificationPerPublish)) {
                                        out = true;
                                    }
                                } else {
                                    item_has_put_err = true;
                                }
                            } else {
                                item_has_put_err = true;
                            }
                        }
                    } else {
                        item_has_put_err = true;
                    }
                }

                /* Если не хавтило места сохранить нотификацию, то завершаем текущий опрос и следующий
                 * начнем с этого же элемента, иначе - переходим к следующему элементу для проверки
                 * оповещений */
                if (item_has_put_err) {
                    out = true;
                } else {
                    if (++sub->monitem_pubreq_pos == sub->monitems_cnt) {
                        sub->monitem_pubreq_pos = 0;
                    }
                    if (rdy_cnt == 0) {
                        if (++notrdy_items_cnt > sub->monitems_cnt) {
                            out = true;
                        }
                    }  else {
                        notrdy_items_cnt = 0;
                    }
                }
            }

            if ((err == LIP_OPCUA_ERR_GOOD) && (data_items_cnt > 0)) {
                /* diagnostic info */
                err = lip_opcua_procbuf_put_arrsize_null(buf);
                if (err == LIP_OPCUA_ERR_GOOD) {
                    lip_opcua_procbuf_put_arrsize(&data_items_size_buf, data_items_cnt);
                    lip_opcua_procbuf_put_objlen_deltabuf(buf, &data_change_obj_size_buf);
                }
                ++notify_cnt;
            }


            if ((err == LIP_OPCUA_ERR_GOOD) && (notify_cnt > 0)) {
                /* подменяем размер в массиве на реальный размер сохраненных сообщений */
                lip_opcua_procbuf_put_arrsize(&put_size_buf, notify_cnt);

                /* если еще остались оповещения, которые не удалось добавить в пакет,
                 * то устанавливаем соответствующий флаг */
                if (sub->pubRdyNotification > 0) {
                    lip_opcua_procbuf_put_bool(&more_notify_buf, true);
                }

                err = lip_opcua_server_session_publish_resp_finish(session, buf);

                if (err == LIP_OPCUA_ERR_GOOD) {
                    sub->flags |= LIP_OPCUA_SRVSUB_FLAG_FIRST_MSG_SENT;
                    lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_DEBUG_MEDIUM, 0, "lip opcua: sub %d seq %d: send data %d, len %d of %d!\n",
                               sub->id,sub->next_seqnum, data_items_cnt, buf->cur - buf->start, buf->end - buf->start);
                    f_next_seqnum(sub);
                }
            }
        }
    }

    return err;
}


static t_lip_opcua_err f_sent_sub_status_changed(t_lip_opcua_srvsub_ctx *sub, struct st_lip_opcua_session_ctx *session, t_lip_opcua_err status)  {
    static const t_lip_opcua_nodeid f_notify_status_change_id = LIP_OPCUA_NODEID_STDNS_INT(LIP_OPCUA_NODEID_STD_STATUS_CHANGE_NOTIFICATION_BIN);
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    t_lip_proc_buf *buf = lip_opcua_server_session_publish_resp_prepare(session, sub->id, NULL);
    if (!buf) {
        err = LIP_OPCUA_ERR_BAD_TCP_SERVER_TOO_BUSY;
    } else {
        err = lip_opcua_procbuf_put_counter(buf, sub->next_seqnum);
        if (err == LIP_OPCUA_ERR_GOOD) {
            t_lip_opcua_tstmp time;
            lip_opcua_get_cur_tstmp(&time);
            err = lip_opcua_procbuf_put_datetime(buf, time.ns100);
        }
        if (err == LIP_OPCUA_ERR_GOOD) {
            err = lip_opcua_procbuf_put_arrsize(buf, 1);
        }

        if (err == LIP_OPCUA_ERR_GOOD) {
            t_lip_proc_buf data_status_obj_size_buf;
            err = lip_opcua_procbuf_put_extobj_info(buf, &f_notify_status_change_id, &data_status_obj_size_buf);
            if (err == LIP_OPCUA_ERR_GOOD) {
                err = lip_opcua_procbuf_put_status(buf, status);
            }
            if (err == LIP_OPCUA_ERR_GOOD) {
                err = lip_opcua_procbuf_put_diagnostic_info_null(buf);
            }

            if (err == LIP_OPCUA_ERR_GOOD) {
                lip_opcua_procbuf_put_objlen_deltabuf(buf, &data_status_obj_size_buf);
            }
        }



        if (err == LIP_OPCUA_ERR_GOOD) {
            err = lip_opcua_server_session_publish_resp_finish(session, buf);
        }

        if (err == LIP_OPCUA_ERR_GOOD) {
            sub->flags |= LIP_OPCUA_SRVSUB_FLAG_FIRST_MSG_SENT;
            f_next_seqnum(sub);
            lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_DEBUG_MEDIUM, 0, "lip opcua: sub %d: send status change 0x%08X!\n", sub->id, status);
        }
    }
    return err;
}

static void f_sub_abort(t_lip_opcua_srvsub_ctx *sub, struct st_lip_opcua_session_ctx *session, t_lip_opcua_err err) {
    lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_WARNING_HIGH, 0, "lip opcua: sub %d is aborted (err 0x%08X)!\n", sub->id, err);
    f_sent_sub_status_changed(sub, session, err);
    lip_opcua_session_sub_delete(session, sub->id);
}


static t_lip_opcua_err f_get_monitem_idx(t_lip_opcua_srvsub_ctx *sub, t_lip_opcua_intid monitem_id,  int *res_monitem_idx) {
    int fnd_idx = -1;
    for (int monitem_idx = 0; (monitem_idx < sub->monitems_cnt) && (fnd_idx < 0); ++monitem_idx) {
        if (sub->monitems[monitem_idx]->id == monitem_id) {
            fnd_idx = monitem_idx;
        }
    }
    if (res_monitem_idx) {
        *res_monitem_idx = fnd_idx;
    }
    return fnd_idx >= 0 ? LIP_OPCUA_ERR_GOOD : LIP_OPCUA_ERR_BAD_MONITORED_ITEM_ID_INVALID;
}

static void f_update_sub_params(t_lip_opcua_subscription_params *params) {
#ifdef LIP_OPCUA_SERVER_SUB_MAX_LIFE_CNT
    if (params->lifetimeCnt > LIP_OPCUA_SERVER_SUB_MAX_LIFE_CNT) {
        params->lifetimeCnt = LIP_OPCUA_SERVER_SUB_MAX_LIFE_CNT;
    }
#endif
    if (params->maxKeepAliveCnt > LIP_OPCUA_SERVER_SUB_KEEPALIVE_MAX_CNT) {
        params->maxKeepAliveCnt = LIP_OPCUA_SERVER_SUB_KEEPALIVE_MAX_CNT;
    }
    if (params->pubInterval < LIP_OPCUA_SERVER_SUB_MIN_PUB_INTERVAL_MS) {
        params->pubInterval = LIP_OPCUA_SERVER_SUB_MIN_PUB_INTERVAL_MS;
    }
}


t_lip_opcua_err lip_opcua_server_sub_create(t_lip_opcua_srvsub_ctx *sub, t_lip_opcua_subscription_params *params, bool publish_en) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;

    if (err == LIP_OPCUA_ERR_GOOD) {
        /** @todo check not match existed, check cnt */
        sub->id = ++f_last_id;
        sub->state = LIP_OPCUA_SRVSUB_STATE_NORMAL;
        sub->flags = 0;
        sub->next_seqnum = 1;
        if (publish_en) {
            sub->flags |= LIP_OPCUA_SRVSUB_FLAG_PUB_ENABLED;
        }
        sub->pubRdyNotification = 0;
        sub->monitem_pubreq_pos = 0;
        sub->monitems_cnt = 0;

        f_update_sub_params(params);

        sub->maxLifetimeCnt = params->lifetimeCnt;
        sub->maxKeepAliveCnt = params->maxKeepAliveCnt;
        sub->maxNotificationPerPublish = params->maxNotificationPerPublish;
        unsigned interval_ms = (unsigned)(params->pubInterval + 0.5);
        ltimer_set_ms(&sub->pub_tmr, interval_ms);

    }
    return err;
}

void lip_opcua_server_sub_delete(t_lip_opcua_srvsub_ctx *sub) {
    for (int monitem_idx = 0; monitem_idx < sub->monitems_cnt; ++monitem_idx) {
        lip_opcua_server_monitem_delete(sub->monitems[monitem_idx]);
    }
    sub->monitems_cnt = 0;
    sub->state = LIP_OPCUA_SRVSUB_STATE_CLOSED;
}

t_lip_opcua_err lip_opcua_server_sub_set_params(t_lip_opcua_srvsub_ctx *sub, t_lip_opcua_subscription_params *params) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;

    /* _18_ - сброс LifetimeCounter */
    sub->curLifetimeCnt = 0;

    f_update_sub_params(params);

    sub->maxLifetimeCnt = params->lifetimeCnt;
    sub->maxKeepAliveCnt = params->maxKeepAliveCnt;
    sub->maxNotificationPerPublish = params->maxNotificationPerPublish;
    /* для выполнения условия посылки keepalive на следующем цикле */
    if (sub->curKeepAliveCnt > sub->maxKeepAliveCnt)
        sub->curKeepAliveCnt = sub->maxKeepAliveCnt;

    unsigned interval_ms = (unsigned)(params->pubInterval + 0.5);
    if (interval_ms != lip_opcua_server_sub_publish_interval(sub)) {
        ltimer_set_ms(&sub->pub_tmr, interval_ms);
    }

    return err;
}

t_lip_opcua_err lip_opcua_server_sub_set_publish_en(t_lip_opcua_srvsub_ctx *sub, bool publish_en) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    /* _19_ - сброс LifetimeCounter */
    sub->curLifetimeCnt = 0;
    if (publish_en) {
        sub->flags |= LIP_OPCUA_SRVSUB_FLAG_PUB_ENABLED;
    } else {
        sub->flags &= ~LIP_OPCUA_SRVSUB_FLAG_PUB_ENABLED;
    }
    return err;
}

void lip_opcua_server_sub_pull(t_lip_opcua_srvsub_ctx *sub, struct st_lip_opcua_session_ctx *session) {
    t_lclock_ticks cur_time = lclock_get_ticks();
    for (int monitem_idx = 0; monitem_idx < sub->monitems_cnt; ++monitem_idx) {
        lip_opcua_server_monitem_pull(sub->monitems[monitem_idx], session, sub, cur_time);
    }

    bool hasPubReq = lip_opcua_session_has_publish_req(session);
    bool pubEnabled = sub->flags & LIP_OPCUA_SRVSUB_FLAG_PUB_ENABLED;
    bool hasNotification = sub->pubRdyNotification > 0;

    if (ltimer_expired_at(&sub->pub_tmr, cur_time)) {
        ltimer_reset(&sub->pub_tmr);
        ++sub->curLifetimeCnt;
        /* помечаем все накопленные notification как готовые к публикации */
        f_set_items_pubrdy(sub);

        bool msgSent = sub->flags & LIP_OPCUA_SRVSUB_FLAG_FIRST_MSG_SENT;

        if (sub->state == LIP_OPCUA_SRVSUB_STATE_NORMAL) {
            if (hasPubReq) {
                if (pubEnabled && hasNotification) {
                    /* _6_ - если есть оповещения и можем послать, то шлем*/
                    sub->curLifetimeCnt = 0;
                    f_sent_notifications(sub, session);
                } else if (!msgSent) {
                    /* _7_ - если нет оповещений и это первый pubInterval, то шлем сразу keepalive */
                    sub->curLifetimeCnt = 0;
                    f_sent_keepalive(sub, session);
                } else {
                    /* _9_ - если нет оповещений и это не первый pubInterval, то переходим в
                     * keepalive, где накапливаем счетчик */
                    sub->state = LIP_OPCUA_SRVSUB_STATE_KEEPALIVE;
                    sub->curKeepAliveCnt = 1;
                }
            } else {
                if ((pubEnabled && hasNotification) || !msgSent) {
                    sub->state = LIP_OPCUA_SRVSUB_STATE_LATE;
                    /* _8_ - Если нет запроса, но нужно посылать оповещения, то переходим в состояние late */
                } else {
                    /* _9_ - вариант без pubRequest обрабатывается аналогично */
                    sub->state = LIP_OPCUA_SRVSUB_STATE_KEEPALIVE;
                    sub->curKeepAliveCnt = 1;
                }
            }
        } else if (sub->state == LIP_OPCUA_SRVSUB_STATE_KEEPALIVE) {
            if (sub->curKeepAliveCnt < sub->maxKeepAliveCnt)
                ++sub->curKeepAliveCnt;
            if (hasPubReq) {
                if (hasNotification && pubEnabled) {
                    /* _14_ -> to Normal */
                    sub->state = LIP_OPCUA_SRVSUB_STATE_NORMAL;
                    sub->curLifetimeCnt = 0;
                    f_sent_notifications(sub, session);
                } else if (sub->curKeepAliveCnt == sub->maxKeepAliveCnt) {
                    /* _15_ - посылка keep-alive */
                    sub->curLifetimeCnt = 0; /* входит в DequeuePublishReq()? */
                    f_sent_keepalive(sub, session);
                }
            } else if ((sub->curKeepAliveCnt == sub->maxKeepAliveCnt) || (pubEnabled && hasNotification)) {
                /* _17_ - если нет pub req, но должны послать либо сообщение, либо keep-alive,
                 * то переходим в LATE */
                sub->state = LIP_OPCUA_SRVSUB_STATE_LATE;
            } /* _16_ - уменьшение счетчика keep-alive уже выполняется для всех шагов */
        }
        /* _12_ - в late никаких действий не предпринимаем */

        if (sub->curLifetimeCnt > sub->maxLifetimeCnt) {
            f_sub_abort(sub, session, LIP_OPCUA_ERR_BAD_TIMEOUT);
        }
    } else if (pubEnabled && hasNotification && hasPubReq) {
        /* если в прошлый раз были переданы не все оповещения и еще остались запросы в очереди,
         * то пробуем допередать */
        sub->curLifetimeCnt = 0;
        f_sent_notifications(sub, session);
    }
}


void lip_opcua_server_sub_proc_publish(t_lip_opcua_srvsub_ctx *sub, struct st_lip_opcua_session_ctx *session) {
    /* _4_ - для NORMAL без разрешения публикаций или без их налчия не одна ветка не требуется (запрос в очередь ставится в общем коде) */
    /* _13_ - для KEEPALIVE sub->pubRdyNotification == 0, поэтому ни одна ветка не будет выполнена */
    if ((sub->flags & LIP_OPCUA_SRVSUB_FLAG_PUB_ENABLED) && (sub->pubRdyNotification > 0)) {
        if (sub->state == LIP_OPCUA_SRVSUB_STATE_LATE) /* _10_ - выход из late */
            sub->state = LIP_OPCUA_SRVSUB_STATE_NORMAL;
        sub->curLifetimeCnt = 0;
        /* _5_, _10_ - посылка сообщений из Late или если не все удалось передать за одно отправление */
        f_sent_notifications(sub, session);
    } else if (sub->state == LIP_OPCUA_SRVSUB_STATE_LATE) {
        /* 11 - посылка из LATE keepalive */
        sub->curLifetimeCnt = 0;
        sub->state = LIP_OPCUA_SRVSUB_STATE_KEEPALIVE;
        f_sent_keepalive(sub, session);
    }
}




t_lip_opcua_err lip_opcua_server_sub_monitem_create(t_lip_opcua_srvsub_ctx *sub, struct st_lip_opcua_session_ctx *session,
                                                    t_lip_opcua_monitem_create_params *params, t_lip_opcua_intid *monitemid) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    if (sub->monitems_cnt == LIP_OPCUA_SERVER_SUB_MONITEM_MAX_CNT) {
        err = LIP_OPCUA_ERR_BAD_TOO_MANY_MONITORED_ITEMS;
    } else {
        err = lip_opcua_server_monitem_create(session, sub, params, &sub->monitems[sub->monitems_cnt]);
        if (err == LIP_OPCUA_ERR_GOOD) {
            *monitemid = sub->monitems[sub->monitems_cnt]->id;
            ++sub->monitems_cnt;
        }
    }

    return err;
}

t_lip_opcua_err lip_opcua_server_sub_monitem_delete(t_lip_opcua_srvsub_ctx *sub, t_lip_opcua_intid monitem_id) {
    int monitem_idx;
    t_lip_opcua_err err = f_get_monitem_idx(sub, monitem_id, &monitem_idx);
    if (err == LIP_OPCUA_ERR_GOOD) {
        /* Удаляем значение готовых публикаций */
        sub->pubRdyNotification -= sub->monitems[monitem_idx]->notifications.pub_rdy;
        lip_opcua_server_monitem_delete(sub->monitems[monitem_idx]);
        if (monitem_idx < (sub->monitems_cnt - 1)) {
            memmove(&sub->monitems[monitem_idx], &sub->monitems[monitem_idx+1], (sub->monitems_cnt - monitem_idx - 1) * sizeof(sub->monitems[0]));
        }

        --sub->monitems_cnt;
        if (sub->monitem_pubreq_pos > monitem_idx) {
            --sub->monitem_pubreq_pos;
        }
        if (sub->monitem_pubreq_pos >= sub->monitems_cnt) {
            sub->monitem_pubreq_pos = 0;
        }
    }
    return err;
}

t_lip_opcua_err lip_opcua_server_sub_monitem_get(t_lip_opcua_srvsub_ctx *sub, t_lip_opcua_intid monitem_id,  t_lip_opcua_monitem_ctx **pmonitem) {
    int monitem_idx;
    t_lip_opcua_err err = f_get_monitem_idx(sub, monitem_id, &monitem_idx);
    if ((err == LIP_OPCUA_ERR_GOOD) && pmonitem) {
        *pmonitem = sub->monitems[monitem_idx];
    }
    return err;
}

#endif
