#include "lip/app/opcua/server/lip_opcua_server_cfg_defs.h"
#if LIP_OPCUA_SERVER_ENABLE
#include "lip_opcua_server_svcset_view.h"
#include "lip/app/opcua/server/lip_opcua_server_con.h"
#include "lip/app/opcua/binary/lip_opcua_binary_types_procbuf.h"
#include "lip/app/opcua/lip_opcua_std_nodeids.h"
#include "lip/app/opcua/server/tree/lip_opcua_server_tree.h"
#include "lip/app/opcua/server/tree/items/types/lip_opcua_server_tree_reftypes.h"
#include "lip/app/opcua/server/tree/lip_opcua_tree_item_ref.h"

typedef enum {
    LIP_OPCUA_BROWSE_RESULT_MASK_REFERENCE_TYPE     = 1 << 0,
    LIP_OPCUA_BROWSE_RESULT_MASK_IS_FORWARD         = 1 << 1,
    LIP_OPCUA_BROWSE_RESULT_MASK_NODE_CLASS         = 1 << 2,
    LIP_OPCUA_BROWSE_RESULT_MASK_BROWSE_NAME        = 1 << 3,
    LIP_OPCUA_BROWSE_RESULT_MASK_DISPLAY_NAME       = 1 << 4,
    LIP_OPCUA_BROWSE_RESULT_MASK_TYPE_DEFINITION    = 1 << 5,
} t_lip_opcua_browse_result_mask_flags;

static t_lip_opcua_tree_item_ref f_result_refs[LIP_OPCUA_BROWSE_REFS_MAX];

t_lip_opcua_err lip_opcua_server_procreq_view_browse(struct st_lip_opcua_con *con, const t_lip_opcua_req_info *req, t_lip_proc_buf *data_buf) {
    t_lip_opcua_view_descr viewdescr;
    t_lip_opcua_counter max_refs_req;
    t_lip_opcua_arrsize nodes_cnt;

    t_lip_opcua_err err = lip_opcua_procbuf_get_viewdescr(data_buf, &viewdescr);
    if (err == LIP_OPCUA_ERR_GOOD) {
        if (viewdescr.view_id.type != LIP_OPCUA_NODEID_TYPE_INVALID) {
            /** @todo get view */
            err = LIP_OPCUA_ERR_BAD_VIEW_ID_UNKNOWN;
        }
    }

    if (err == LIP_OPCUA_ERR_GOOD) {
        err = lip_opcua_procbuf_get_counter(data_buf, &max_refs_req);
    }

    if (err == LIP_OPCUA_ERR_GOOD) {
        err = lip_opcua_procbuf_get_arrsize(data_buf, &nodes_cnt);
    }

    if (err == LIP_OPCUA_ERR_GOOD) {
        static const t_lip_opcua_nodeid f_resp_node_id = LIP_OPCUA_NODEID_STDNS_INT(LIP_OPCUA_NODEID_STD_BROWSE_RESPONSE_BIN);
        t_lip_proc_buf *resp_buf = lip_opcua_con_resp_prepare(con, &req->resp, &f_resp_node_id, LIP_OPCUA_ERR_GOOD);
        if (!resp_buf) {
            err = LIP_OPCUA_ERR_BAD_TCP_SERVER_TOO_BUSY;
        } else {
            struct st_lip_opcua_session_ctx *session = lip_opcua_con_session(con);

            err = lip_opcua_procbuf_put_arrsize(resp_buf, nodes_cnt);
            for (int32_t node_idx = 0; (node_idx < nodes_cnt) && (err == LIP_OPCUA_ERR_GOOD); ++node_idx) {
                t_lip_opcua_nodeid  nodeid;
                t_lip_opcua_nodeid  ref_type_id;
                uint32_t result_mask = 0;
                t_lip_opcua_tree_item_browse_params params;
                params.qname.name.data = NULL;
                params.qname.name.size = -1;

                err = lip_opcua_procbuf_get_nodeid(data_buf, &nodeid);
                if (err == LIP_OPCUA_ERR_GOOD) {
                    t_lip_opcua_enumval browse_dir;
                    err = lip_opcua_procbuf_get_enum(data_buf, &browse_dir);
                    if (err == LIP_OPCUA_ERR_GOOD) {
                        params.browse_dir = browse_dir;
                    }
                }
                if (err == LIP_OPCUA_ERR_GOOD) {
                    err = lip_opcua_procbuf_get_nodeid(data_buf, &ref_type_id);
                }
                if (err == LIP_OPCUA_ERR_GOOD) {
                    bool inc_subs;
                    err = lip_opcua_procbuf_get_bool(data_buf, &inc_subs);
                    if (err == LIP_OPCUA_ERR_GOOD) {
                        params.include_subtypes = inc_subs;
                    }
                }
                if (err == LIP_OPCUA_ERR_GOOD) {
                    uint32_t class_mask;
                    err = lip_opcua_procbuf_get_uint32(data_buf, &class_mask);
                    if (err == LIP_OPCUA_ERR_GOOD) {
                        params.node_class_mask = class_mask;
                    }
                }
                if (err == LIP_OPCUA_ERR_GOOD) {
                    err = lip_opcua_procbuf_get_uint32(data_buf, &result_mask);
                }

                if (err == LIP_OPCUA_ERR_GOOD) {

                    const t_lip_opcua_tree_item *item;
                    uint32_t refs_cnt = 0;
                    t_lip_opcua_err op_status = lip_opcua_server_tree_get_item(session, &nodeid, &item);

                    if (op_status == LIP_OPCUA_ERR_GOOD) {
                        t_lip_opcua_err ref_status = lip_opcua_server_tree_get_reftype(session, &ref_type_id, &params.ref_type);

                        if ((ref_status != LIP_OPCUA_ERR_GOOD) || !params.ref_type || (params.ref_type->class_descr_hdr->node_class != LIP_OPCUA_NODECLASS_REFERENCE_TYPE)) {
                            op_status = LIP_OPCUA_ERR_BAD_REFTYPE_ID_INVALID;
                        }
                    }

                    if (op_status == LIP_OPCUA_ERR_GOOD) {
                        uint32_t req_refs_cnt = ((max_refs_req > LIP_OPCUA_BROWSE_REFS_MAX) || (max_refs_req == 0)) ? LIP_OPCUA_BROWSE_REFS_MAX : max_refs_req;
                        op_status = lip_opcua_server_tree_item_browse(item, &params, &f_result_refs[0], &req_refs_cnt);
                        if (op_status == LIP_OPCUA_ERR_GOOD) {
                            refs_cnt = req_refs_cnt;
                        }
                    }

                    /* Сохранение результатов в виде BrowseResult */
                    err = lip_opcua_procbuf_put_status(resp_buf, op_status);
                    if (err == LIP_OPCUA_ERR_GOOD) {
                        /** @todo ContinuationPoint */
                        err = lip_opcua_procbuf_put_string(resp_buf, NULL);
                    }
                    if (err == LIP_OPCUA_ERR_GOOD) {
                        err = lip_opcua_procbuf_put_arrsize(resp_buf, refs_cnt);
                    }
                    for (uint32_t ref_idx = 0; (ref_idx < refs_cnt) && (err == LIP_OPCUA_ERR_GOOD); ++ref_idx) {
                        /* ReferenceDescription */
                        const t_lip_opcua_tree_item_ref *ref = &f_result_refs[ref_idx];
                        if ((result_mask & LIP_OPCUA_BROWSE_RESULT_MASK_REFERENCE_TYPE) && (err == LIP_OPCUA_ERR_GOOD)) {
                            err = lip_opcua_procbuf_put_nodeid(resp_buf, &ref->ref_type->nodeid);
                        }
                        if ((result_mask & LIP_OPCUA_BROWSE_RESULT_MASK_IS_FORWARD) && (err == LIP_OPCUA_ERR_GOOD)) {
                            err = lip_opcua_procbuf_put_bool(resp_buf, !ref->inverse);
                        }
                        if (err == LIP_OPCUA_ERR_GOOD) {
                            err = lip_opcua_procbuf_put_nodeid(resp_buf, ref->target_node ? &ref->target_node->nodeid : NULL);
                        }
                        if ((result_mask & LIP_OPCUA_BROWSE_RESULT_MASK_BROWSE_NAME) && (err == LIP_OPCUA_ERR_GOOD)) {
                            err = lip_opcua_procbuf_put_qalified_name(resp_buf, ref->target_node ? &ref->target_node->browse_name : NULL);
                        }
                        if ((result_mask & LIP_OPCUA_BROWSE_RESULT_MASK_DISPLAY_NAME) && (err == LIP_OPCUA_ERR_GOOD)) {
                            t_lip_opcua_loc_string_ptr loctxt;
                            lip_opcua_server_tree_item_get_display_name(ref->target_node, session, &loctxt);
                            err = lip_opcua_procbuf_put_loc_string(resp_buf, &loctxt);
                        }
                        if ((result_mask & LIP_OPCUA_BROWSE_RESULT_MASK_NODE_CLASS) && (err == LIP_OPCUA_ERR_GOOD)) {
                            err = lip_opcua_procbuf_put_enum(resp_buf, ref->target_node ? ref->target_node->class_descr_hdr->node_class : LIP_OPCUA_NODECLASS_UNSPECIFIED);
                        }
                        if ((result_mask & LIP_OPCUA_BROWSE_RESULT_MASK_TYPE_DEFINITION) && (err == LIP_OPCUA_ERR_GOOD)) {
                            const t_lip_opcua_nodeid *typedef_node_id = lip_opcua_server_tree_item_get_typedef_id(ref->target_node);
                            if (typedef_node_id) {
                                err = lip_opcua_procbuf_put_nodeid(resp_buf, typedef_node_id);
                            } else {
                                err = lip_opcua_procbuf_put_nodeid(resp_buf, NULL);
                            }
                        }

                    }

                }
            }

            if (err == LIP_OPCUA_ERR_GOOD) {
                /* DiagnosticInfo */
                err = lip_opcua_procbuf_put_arrsize_null(resp_buf);
            }

            if (err == LIP_OPCUA_ERR_GOOD) {
                lip_opcua_con_resp_finish(con, resp_buf);
            }

        }
    }

    return err;
}


t_lip_opcua_err lip_opcua_server_procreq_translate_path(struct st_lip_opcua_con *con, const t_lip_opcua_req_info *req, t_lip_proc_buf *data_buf) {
    t_lip_opcua_err err;
    t_lip_opcua_arrsize nodes_cnt;

    err = lip_opcua_procbuf_get_arrsize(data_buf, &nodes_cnt);

    if (err == LIP_OPCUA_ERR_GOOD) {
        static const t_lip_opcua_nodeid f_resp_node_id = LIP_OPCUA_NODEID_STDNS_INT(LIP_OPCUA_NODEID_STD_TRANSLATE_BROWSE_PATHS_TO_NODEIDS_RESPONSE_BIN);
        t_lip_proc_buf *resp_buf = lip_opcua_con_resp_prepare(con, &req->resp, &f_resp_node_id, LIP_OPCUA_ERR_GOOD);
        if (!resp_buf) {
            err = LIP_OPCUA_ERR_BAD_TCP_SERVER_TOO_BUSY;
        } else {
            err = lip_opcua_procbuf_put_arrsize(resp_buf, nodes_cnt);

            struct st_lip_opcua_session_ctx *session = lip_opcua_con_session(con);

            for (int32_t node_idx = 0; (node_idx < nodes_cnt) && (err == LIP_OPCUA_ERR_GOOD); ++node_idx) {
                t_lip_opcua_nodeid nodeid;
                t_lip_opcua_arrsize rel_path_elem_cnt;
                err = lip_opcua_procbuf_get_nodeid(data_buf, &nodeid);
                if (err == LIP_OPCUA_ERR_GOOD) {
                    err = lip_opcua_procbuf_get_arrsize(data_buf, &rel_path_elem_cnt);
                }
                if (err == LIP_OPCUA_ERR_GOOD) {
                    const t_lip_opcua_tree_item *item;
                    t_lip_opcua_arrsize fnd_targets_cnt = 0;

                    t_lip_opcua_err op_status = lip_opcua_server_tree_get_item(session, &nodeid, &item);
                    if ((op_status == LIP_OPCUA_ERR_GOOD) && (rel_path_elem_cnt == 0)) {
                        /* This code indicates that the relativePath contained an empty list. */
                        op_status = LIP_OPCUA_ERR_BAD_NOTHING_TO_DO;
                    }

                    /* проход по всем шагам пути и поиск на кадом ссылки, ведущей к следующему шагу */
                    for (int32_t elem_idx = 0; (elem_idx < rel_path_elem_cnt) && (err == LIP_OPCUA_ERR_GOOD); ++elem_idx) {
                        t_lip_opcua_nodeid ref_type_id;
                        t_lip_opcua_tree_item_browse_params params;
                        params.node_class_mask = 0;
                        err = lip_opcua_procbuf_get_nodeid(data_buf, &ref_type_id);
                        if (err == LIP_OPCUA_ERR_GOOD) {
                            bool is_inverse;
                            err = lip_opcua_procbuf_get_bool(data_buf, &is_inverse);
                            if (err == LIP_OPCUA_ERR_GOOD) {
                                params.browse_dir = is_inverse ? LIP_OPCUA_BROWSE_DIR_INVERSE : LIP_OPCUA_BROWSE_DIR_FORWARD;
                            }

                        }
                        if (err == LIP_OPCUA_ERR_GOOD) {
                            bool inc_subs;
                            err = lip_opcua_procbuf_get_bool(data_buf, &inc_subs);
                            if (err == LIP_OPCUA_ERR_GOOD) {
                                params.include_subtypes = inc_subs;
                            }
                        }
                        if (err == LIP_OPCUA_ERR_GOOD) {
                            err = lip_opcua_procbuf_get_qalified_name(data_buf, &params.qname);
                        }

                        if (err == LIP_OPCUA_ERR_GOOD) {
                            bool last = elem_idx == rel_path_elem_cnt-1;
                            if ((op_status == LIP_OPCUA_ERR_GOOD) && (params.qname.name.size <= 0) && !last) {
                                /* This code indicates that a TargetName was missing in a RelativePath (может быть только для последнего элемента) */
                                op_status = LIP_OPCUA_ERR_BAD_BROWSE_NAME_INVALID;
                            }
                            if (op_status == LIP_OPCUA_ERR_GOOD) {
                                uint32_t refs_cnt = LIP_OPCUA_BROWSE_REFS_MAX;

                                op_status = lip_opcua_server_tree_item_browse(item, &params, &f_result_refs[0], &refs_cnt);
                                if (op_status == LIP_OPCUA_ERR_GOOD) {
                                    if (refs_cnt == 0) {
                                        /* The requested relativePath cannot be resolved to a target to return */
                                        op_status = LIP_OPCUA_ERR_BAD_NO_MATCH;
                                    } else {
                                        if (!last) {
                                            /* Переходим к следующему шагу.
                                             * Подразумевается наличие только одного пути на каждом шаге кроме последнего,
                                             * если больше, то используем первый */
                                            item = f_result_refs[0].target_node;
                                        } else {
                                            fnd_targets_cnt = refs_cnt;
                                        }
                                    }
                                }
                            }
                        }
                    } /* for (int32_t elem_idx = 0....) */

                    if (err == LIP_OPCUA_ERR_GOOD) {
                        err = lip_opcua_procbuf_put_status(resp_buf, op_status);
                    }
                    if (err == LIP_OPCUA_ERR_GOOD) {
                        err = lip_opcua_procbuf_put_arrsize(resp_buf, fnd_targets_cnt);
                    }
                    for (t_lip_opcua_arrsize target_idx = 0; (target_idx < fnd_targets_cnt) && (err == LIP_OPCUA_ERR_GOOD); ++target_idx) {
                        const struct st_lip_opcua_tree_item *target_item = f_result_refs[target_idx].target_node;
                        err = lip_opcua_procbuf_put_nodeid(resp_buf, target_item ? &target_item->nodeid : NULL);
                        if (err == LIP_OPCUA_ERR_GOOD) {
                            /* Не поддерживаем ссылки на другие сервера, поэтому всегда возвращаем
                             * что обработали весь путь - index - max value */
                            err = lip_opcua_procbuf_put_index(resp_buf, 0xFFFFFFFF);
                        }
                    }
                }
            }


            if (err == LIP_OPCUA_ERR_GOOD) {
                /* DiagnosticInfo */
                err = lip_opcua_procbuf_put_arrsize_null(resp_buf);
            }

            if (err == LIP_OPCUA_ERR_GOOD) {
                lip_opcua_con_resp_finish(con, resp_buf);
            }
        }
    }

    return err;
}

t_lip_opcua_err lip_opcua_server_procreq_nodes_register(struct st_lip_opcua_con *con, const t_lip_opcua_req_info *req, t_lip_proc_buf *data_buf) {
    t_lip_opcua_err err;
    t_lip_opcua_arrsize nodes_cnt;

    err = lip_opcua_procbuf_get_arrsize(data_buf, &nodes_cnt);

    if (err == LIP_OPCUA_ERR_GOOD) {
        static const t_lip_opcua_nodeid f_resp_node_id = LIP_OPCUA_NODEID_STDNS_INT(LIP_OPCUA_NODEID_STD_REGISTER_NODES_RESPONSE_BIN);
        t_lip_proc_buf *resp_buf = lip_opcua_con_resp_prepare(con, &req->resp, &f_resp_node_id, LIP_OPCUA_ERR_GOOD);
        if (!resp_buf) {
            err = LIP_OPCUA_ERR_BAD_TCP_SERVER_TOO_BUSY;
        } else {
            err = lip_opcua_procbuf_put_arrsize(resp_buf, nodes_cnt);

            for (int32_t node_idx = 0; (node_idx < nodes_cnt) && (err == LIP_OPCUA_ERR_GOOD); ++node_idx) {
                /* Сейчас не поддерживаем каких либо действий по оптимизации, просто возвращаем те же nodeid,
                 * что и запрашивались. Если понадобится, то в будущем можно сделать callback, но тогда их
                 * нужно хранить в сессии, чтобы при закрытии сессии сделать автоматом unregister */
                t_lip_opcua_nodeid nodeid;
                err = lip_opcua_procbuf_get_nodeid(data_buf, &nodeid);
                if (err == LIP_OPCUA_ERR_GOOD) {
                    err = lip_opcua_procbuf_put_nodeid(resp_buf, &nodeid);
                }
            }

            if (err == LIP_OPCUA_ERR_GOOD) {
                lip_opcua_con_resp_finish(con, resp_buf);
            }
        }
    }

    return err;
}

t_lip_opcua_err lip_opcua_server_procreq_nodes_unregister(struct st_lip_opcua_con *con, const t_lip_opcua_req_info *req, t_lip_proc_buf *data_buf) {
    t_lip_opcua_err err;
    t_lip_opcua_arrsize nodes_cnt;

    err = lip_opcua_procbuf_get_arrsize(data_buf, &nodes_cnt);

    if (err == LIP_OPCUA_ERR_GOOD) {
        for (int32_t node_idx = 0; (node_idx < nodes_cnt) && (err == LIP_OPCUA_ERR_GOOD); ++node_idx) {
            /* Отмена действий по register. сейчас ничего не делаем */
            t_lip_opcua_nodeid nodeid;
            err = lip_opcua_procbuf_get_nodeid(data_buf, &nodeid);
        }
    }

    if (err == LIP_OPCUA_ERR_GOOD) {
        static const t_lip_opcua_nodeid f_resp_node_id = LIP_OPCUA_NODEID_STDNS_INT(LIP_OPCUA_NODEID_STD_UNREGISTER_NODES_RESPONSE_BIN);
        t_lip_proc_buf *resp_buf = lip_opcua_con_resp_prepare(con, &req->resp, &f_resp_node_id, LIP_OPCUA_ERR_GOOD);
        if (!resp_buf) {
            err = LIP_OPCUA_ERR_BAD_TCP_SERVER_TOO_BUSY;
        } else {
            lip_opcua_con_resp_finish(con, resp_buf);
        }
    }

    return err;
}

#endif
