#include "lip_opcua_server_cfg_defs.h"
#if LIP_OPCUA_SERVER_ENABLE
#include "lip_opcua_sc_private.h"
#include "lip_opcua_server_time.h"
#include "lip_opcua_server_session.h"
#include "lip/app/opcua/lip_opcua_err_codes.h"

#include "string.h"


static t_lip_opcua_sc_ctx f_sc_list[LIP_OPCUA_SERVER_SC_CNT];
static uint32_t f_last_sc_id;
static uint32_t f_last_token_id;









static inline bool f_opcua_check_url_const(const t_lip_opcua_string_ptr *url_str, const char *check_val) {
    return strncmp((const char*)url_str->data, check_val, url_str->size) == 0;
}


t_lip_opcua_sc_ctx *f_get_new_sc(void) {
    t_lip_opcua_sc_ctx *ret = NULL;
    for (int sc_idx = 0; (sc_idx < LIP_OPCUA_SERVER_SC_CNT) && (ret == NULL); ++sc_idx) {
        t_lip_opcua_sc_ctx *sc = &f_sc_list[sc_idx];
        if (!sc->valid) {
            ret = sc;
        }
    }

    if (!ret) {

        /** @todo fnd oldest and close */
    }


    return ret;
}

static void f_init_new_token(t_lip_opcua_sc_token *token, uint32_t sc_id, uint32_t lifetime) {
    t_lip_opcua_tstmp tstmp;
    lip_opcua_get_cur_tstmp(&tstmp);
    token->SecureChannelId = sc_id;
    token->CreatedAt = tstmp.ns100;
    token->TokenId = ++f_last_token_id;
    if (f_last_token_id == 0)
        token->TokenId = ++f_last_token_id;
    token->RevisedLifetime = MIN(LIP_OPCUA_SERVER_SC_TOKEN_LIFETIME_MAX, lifetime);
    ltimer_set_ms(&token->ExpireTimer, token->RevisedLifetime);
}

t_lip_opcua_err lip_opcua_sc_check_rx_seqnum(t_lip_opcua_sc_ctx *sc, uint32_t sc_id, uint32_t seqnum) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    if (sc_id != sc->cur_token.SecureChannelId) {
        err = LIP_OPCUA_ERR_BAD_CHANNEL_ID_INVALID;
    } else if (seqnum != sc->rx_seq_num) {
        err = LIP_OPCUA_ERR_BAD_SEQUENCE_NUMBER_INVALID;
        lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_WARNING_MEDIUM, 0, "lip opcua: sc %d invalid rx msg sequence number (expected %d, receive %d)!\n", sc->cur_token.SecureChannelId, sc->rx_seq_num, seqnum);
    }
    return err;
}

void lip_opcua_next_rx_seqnum(t_lip_opcua_sc_ctx *sc) {
    ++sc->rx_seq_num; /** @todo для некоторых policy есть запас на 1024 в начале и конце */
}




void lip_opcua_scs_init(void) {
    memset(f_sc_list, 0, sizeof(f_sc_list));
}
void lip_opcua_scs_close(void) {

}


void lip_opcua_scs_pull(void) {
    for (int sc_idx = 0; sc_idx < LIP_OPCUA_SERVER_SC_CNT; ++sc_idx) {
        t_lip_opcua_sc_ctx *sc = &f_sc_list[sc_idx];
        if (sc->valid) {
            if ((sc->new_token.TokenId != 0) && ltimer_expired(&sc->new_token.ExpireTimer)) {
                sc->new_token.TokenId = 0;
            }
            if ((sc->cur_token.TokenId != 0) && ltimer_expired(&sc->cur_token.ExpireTimer)) {
                lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_WARNING_LOW, 0, "lip opcua: sc %d token expired %d!\n", sc->cur_token.SecureChannelId, sc->cur_token.TokenId);
                if (sc->new_token.TokenId != 0) {
                    sc->cur_token = sc->new_token;
                    sc->new_token.TokenId = 0;
                } else {
                    sc->cur_token.TokenId = 0;
                }
            }
        }
    }
}

t_lip_opcua_err lip_opcua_sc_create(const t_lip_opcua_sc_open_params *params, t_lip_opcua_datetime dt, uint32_t seqnum,
                                    t_lip_opcua_sc_ctx **new_sc, t_lip_opcua_sc_token **token, t_lip_opcua_string_ptr *server_nonce) {

    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    t_lip_opcua_sc_ctx *sc = f_get_new_sc();
    if (!sc) {
        err = LIP_OPCUA_ERR_BAD_TCP_NOT_ENOUGH_RESOURCES;
    } else {
        sc->valid = true;
        sc->cur_session = NULL;
        f_init_new_token(&sc->cur_token, ++f_last_sc_id, params->requestedLifetime);
        sc->cur_token.CreatedAt = dt;
        sc->new_token.TokenId = 0;



        sc->rx_seq_num = seqnum;
        sc->tx_seq_num = 1; /** @todo для некоторых policy может быть от 1024 */

        lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_DEBUG_MEDIUM, 0, "lip opcua: new sc open %d!\n", sc->cur_token.SecureChannelId);
    }


    if (err == LIP_OPCUA_ERR_GOOD) {
        if (new_sc) {
            *new_sc = sc;
        }
        if (token) {
            *token = &sc->cur_token;
        }

        server_nonce->data = NULL;
        server_nonce->size = -1;
    }
    return err;
}

t_lip_opcua_err lip_opcua_sc_renew_token(struct st_lip_opcua_sc_ctx *sc, const t_lip_opcua_sc_open_params *params, t_lip_opcua_sc_token **token, t_lip_opcua_string_ptr *server_nonce) {
    t_lip_opcua_err err = LIP_OPCUA_ERR_GOOD;
    if (sc && sc->valid) {
        f_init_new_token(&sc->new_token, sc->cur_token.SecureChannelId, params->requestedLifetime);
        if (token) {
            *token = &sc->new_token;
        }
        server_nonce->data = NULL;
        server_nonce->size = -1;
        lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_DEBUG_MEDIUM, 0, "lip opcua: sc %d token renew req %d!\n", sc->cur_token.SecureChannelId, sc->new_token.TokenId);
    } else {
        err = LIP_OPCUA_ERR_BAD_SECURE_CHANNEL_CLOSED;
    }
    return err;
}

void lip_opcua_sc_close(t_lip_opcua_sc_ctx *sc) {
    if (sc->cur_session) {
        lip_opcua_session_clear_sc(sc->cur_session);
    }
    lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_DEBUG_MEDIUM, 0, "lip opcua: sc closed %d!\n", sc->cur_token.SecureChannelId);
    sc->valid = false;
}

void lip_opcua_sc_set_session(t_lip_opcua_sc_ctx *sc, struct st_lip_opcua_session_ctx *session) {
    if (sc->cur_session) {
        lip_opcua_session_clear_sc(sc->cur_session);
    }

    sc->cur_session = session;
}


struct st_lip_opcua_session_ctx *lip_opcua_sc_session(struct st_lip_opcua_sc_ctx *sc) {
    return sc ? sc->cur_session : NULL;
}

t_lip_opcua_err lip_opcua_sc_check_msg_params(t_lip_opcua_sc_ctx *sc, const t_lip_opcua_sc_msg_info *msg) {
    t_lip_opcua_err err = lip_opcua_sc_check_rx_seqnum(sc, msg->SecureChannelId, msg->SequenceNumber);
    if ((err == LIP_OPCUA_ERR_GOOD) && (msg->TockenId != sc->cur_token.TokenId)) {
        if ((sc->new_token.TokenId != 0) && (sc->new_token.TokenId == msg->TockenId)) {
            sc->cur_token = sc->new_token;
            sc->new_token.TokenId = 0;
            lip_printf(LIP_LOG_MODULE_OPCUA, LIP_LOG_LVL_DEBUG_HIGH, 0, "lip opcua: sc %d start using new token %d!\n", sc->cur_token.SecureChannelId, sc->cur_token.TokenId);
        } else {
            err = LIP_OPCUA_ERR_BAD_SECURE_CHANNEL_TOKEN_UNKNOWN;
        }
    }

    if (err == LIP_OPCUA_ERR_GOOD) {
        lip_opcua_next_rx_seqnum(sc);
    }
    return err;
}

void lip_opcua_sc_next_msg(t_lip_opcua_sc_ctx *sc) {
    ++sc->tx_seq_num;
    /** @todo check ov + смена токена */
}


#endif
