#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include "ltrslot.h"
#include "ltrd_protocol_defs.h"


/*================================================================================================*/
#define SLOTCFG_ADDR(nslt, offs) (((DWORD)(nslt) << 16) | (offs))


/*================================================================================================*/
typedef unsigned long t_conf_addr;


/*================================================================================================*/
static INT get_array_config(t_ltrslot_conn h_conn, t_conf_addr addr, DWORD sz, void *data);
static INT put_array_config(t_ltrslot_conn h_conn, t_conf_addr addr, DWORD sz, const void *data);
static INT put_slotcfg(void *h_card, TLTR *ltr_conn, DWORD cfg_sz, WORD card_id, int check_cfgexist,
    DWORD slot_status, TLTR_CARD_START_MODE start_mode,
    void (*conv_hcardtocfg)(const void *, void *));


/*================================================================================================*/
static const t_conf_addr memoffset_config = 32;
static const t_conf_addr memoffset_info = 0;
static const t_conf_addr memoffset_storeconfig = 30000;


/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
static INT get_array_config(t_ltrslot_conn h_conn, t_conf_addr addr, DWORD sz, void *data) {
    return LTR_CrateGetArray(&h_conn->h_channel, SLOTCFG_ADDR(h_conn->n_slot, addr | SEL_SLOTS),
                             data, sz);
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_check_support(t_ltrslot_conn h_conn, int *has_support) {
    return ltrslot_check_support_ltr(&(h_conn->h_channel), has_support);
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_check_support_ltr(TLTR *h_ltr, int *has_support) {
    INT ret_code = LTR_OK;

    static const DWORD min_servver = 0x02000200;
    DWORD server_ver;
    TLTR_CRATE_DESCR *crate_descr = NULL;
    const INT descr_sz = sizeof(TLTR_CRATE_DESCR);

    assert((h_ltr != NULL) && (has_support != NULL) && (h_ltr->cc == LTR_CC_CHNUM_CONTROL));

    *has_support = 0;

    ret_code = LTR_GetServerVersion(h_ltr, &server_ver);
    if ((ret_code == LTR_OK) && (server_ver >= min_servver)) {
        *has_support = 1;

        if ((crate_descr = malloc(descr_sz)) == NULL)
            ret_code = LTR_ERROR_MEMORY_ALLOC;
    }

    if ((ret_code == LTR_OK) && *has_support) {
        ret_code = LTR_GetCrateDescr(h_ltr, LTR_CRATE_IFACE_UNKNOWN, h_ltr->csn,
                                     crate_descr, descr_sz);
    }

    if ((ret_code == LTR_OK) && *has_support) {
        *has_support = (((crate_descr->protocol_ver_major > 1) ||
            ((crate_descr->protocol_ver_major == 1) && (crate_descr->protocol_ver_minor >= 1))) &&
            ((crate_descr->slots_config_ver_major > 0) ||
            (crate_descr->slots_config_ver_minor > 0)));
    }

    free(crate_descr);

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_create_connection(DWORD netaddr, WORD netport, const CHAR serial[], WORD slot,
    t_ltrslot_conn *h_conn) {
    INT ret_code = LTR_OK;

    *h_conn = malloc(sizeof(struct LTRSlotConn));
    if (*h_conn == NULL)
        ret_code = LTR_ERROR_MEMORY_ALLOC;

    if (ret_code == LTR_OK)
        ret_code = LTR_Init(&((*h_conn)->h_channel));

    if (ret_code == LTR_OK) {
        (*h_conn)->n_slot = slot - 1;

        (*h_conn)->h_channel.cc = LTR_CC_CHNUM_CONTROL;
        (*h_conn)->h_channel.saddr = netaddr;
        (*h_conn)->h_channel.sport = netport;
        strncpy((*h_conn)->h_channel.csn, serial, LTR_CRATE_SERIAL_SIZE - 1);
        (*h_conn)->h_channel.csn[LTR_CRATE_SERIAL_SIZE - 1] = '\0';
        ret_code = LTR_Open(&((*h_conn)->h_channel));
    }

    if (ret_code != LTR_OK) {
        free(*h_conn);
        *h_conn = LTRSLOT_CONN_INVAL;
    }

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_destroy_connection(t_ltrslot_conn h_conn) {
    INT ret_code = LTR_OK;

    if (h_conn != LTRSLOT_CONN_INVAL) {
        ret_code = LTR_Close(&(h_conn->h_channel));
        free(h_conn);
    }

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_get_config(t_ltrslot_conn h_conn, DWORD sz, void *cfg) {
    INT ret_val = LTR_OK;

    ret_val = get_array_config(h_conn, memoffset_config, sz, cfg);

    return ret_val;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_get_info(t_ltrslot_conn h_conn, struct LTRSlotInfo *info) {
    INT ret_val = LTR_OK;

    ret_val = get_array_config(h_conn, memoffset_info, sizeof(struct LTRSlotInfo), info);

    return ret_val;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_put_config(t_ltrslot_conn h_conn, DWORD sz, const void *cfg) {
    INT ret_code = LTR_OK;

    ret_code = put_array_config(h_conn, memoffset_config, sz, cfg);

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_put_info(t_ltrslot_conn h_conn, const struct LTRSlotInfo *info) {
    INT ret_code = LTR_OK;

    ret_code = put_array_config(h_conn, memoffset_info, sizeof(struct LTRSlotInfo), info);

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_replicate_connection(const TLTR *h_base_conn, t_ltrslot_conn *h_conn) {
    INT ret_code = LTR_OK;

    *h_conn = malloc(sizeof(struct LTRSlotConn));
    if (*h_conn == NULL)
        ret_code = LTR_ERROR_MEMORY_ALLOC;

    if (ret_code == LTR_OK)
        ret_code = LTR_Init(&((*h_conn)->h_channel));

    if (ret_code == LTR_OK) {
        (*h_conn)->n_slot = h_base_conn->cc - 1;

        (*h_conn)->h_channel.cc = LTR_CC_CHNUM_CONTROL;
        (*h_conn)->h_channel.saddr = h_base_conn->saddr;
        (*h_conn)->h_channel.sport = h_base_conn->sport;
        memcpy((*h_conn)->h_channel.csn, h_base_conn->csn, sizeof (*h_conn)->h_channel.csn);
        ret_code = LTR_Open(&((*h_conn)->h_channel));
    }

    if (ret_code != LTR_OK) {
        free(*h_conn);
        *h_conn = LTRSLOT_CONN_INVAL;
    }

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_restore_config(void *h_card, DWORD net_addr, WORD net_port, const CHAR crate_serial[],
    WORD slot, DWORD cfg_sz, void (*convert_to_hcard)(const void *, void *), DWORD *out_flags) {
    int hassupport_slots;
    struct LTRSlotInfo *h_slot_info = NULL;
    t_ltrslot_conn h_slot_conn = LTRSLOT_CONN_INVAL;
    DWORD out_flg = 0;
    INT ret_val = LTR_OK;

    assert((h_card != NULL) && (cfg_sz > 0) && (convert_to_hcard != NULL) &&
        (LTR_CC_CHNUM_MODULE1 <= slot) && (slot <= LTR_MODULES_PER_CRATE_MAX));

    if (ret_val == LTR_OK)
        ret_val = ltrslot_create_connection(net_addr, net_port, crate_serial, slot, &h_slot_conn);

    if (ret_val == LTR_OK)
        ret_val = ltrslot_check_support(h_slot_conn, &hassupport_slots);

    if (ret_val == LTR_OK) {
        if (!hassupport_slots)
            ret_val = LTR_ERROR_CARDSCONFIG_UNSUPPORTED;
    }

    if (ret_val == LTR_OK) {
        if ((h_slot_info = malloc(sizeof(struct LTRSlotInfo))) == NULL)
            ret_val = LTR_ERROR_MEMORY_ALLOC;
    }

    if (ret_val == LTR_OK)
        ret_val = ltrslot_get_info(h_slot_conn, h_slot_info);

    if (ret_val == LTR_OK) {
        if (h_slot_info->status & LTRSLOT_STATUSFLG_RUN) {
            void *cfg = NULL;
            if ((cfg = malloc(cfg_sz)) == NULL)
                ret_val = LTR_ERROR_MEMORY_ALLOC;
            if (ret_val == LTR_OK)
                ret_val = ltrslot_get_config(h_slot_conn, cfg_sz, cfg);
            if (ret_val == LTR_OK) {
                convert_to_hcard(cfg, h_card);
                out_flg = LTR_OPENOUTFLG_REOPEN;
            }
            free(cfg);
        }
    }


    free(h_slot_info);
    ltrslot_destroy_connection(h_slot_conn);

    if (out_flags != NULL)
        *out_flags = out_flg;

    return ret_val;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_save_settings(t_ltrslot_conn h_conn) {
    INT ret_code = LTR_OK;

    ret_code = put_array_config(h_conn, memoffset_storeconfig, 0, NULL);

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_save_slot_settings(TLTR *h_ltr, WORD nslot) {
    INT ret_code = LTR_OK;

    ret_code = ltrslot_write(h_ltr, nslot, (WORD)memoffset_storeconfig, 0, NULL);

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_start_wconfig(void *h_card, TLTR *ltr_conn, DWORD cfg_sz, WORD card_id,
    void (*conv_hcardtocfg)(const void *, void *)) {

    assert((h_card != NULL) && (ltr_conn != NULL) && (cfg_sz > 0) && (conv_hcardtocfg));

    return put_slotcfg(h_card, ltr_conn, cfg_sz, card_id, 0,
        LTRSLOT_STATUSFLG_CONFIG|LTRSLOT_STATUSFLG_RUN, LTR_CARD_START_OFF, conv_hcardtocfg);
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_stop(TLTR *ltr_conn) {
    int issupported_slot;
    struct LTRSlotInfo *h_slot_info = NULL;
    t_ltrslot_conn h_slot_conn = LTRSLOT_CONN_INVAL;
    INT ret_val = LTR_OK;

    assert(ltr_conn != NULL);

    if (ret_val == LTR_OK)
        ret_val = ltrslot_replicate_connection(ltr_conn, &h_slot_conn);

    if (ret_val == LTR_OK)
        ret_val = ltrslot_check_support(h_slot_conn, &issupported_slot);

    if ((ret_val == LTR_OK) && issupported_slot) {
        if ((h_slot_info = malloc(sizeof(struct LTRSlotInfo))) == NULL)
            ret_val = LTR_ERROR_MEMORY_ALLOC;

        if (ret_val == LTR_OK)
            ret_val = ltrslot_get_info(h_slot_conn, h_slot_info);

        if (ret_val == LTR_OK) {
            h_slot_info->status &= ~LTRSLOT_STATUSFLG_RUN;
            ret_val = ltrslot_put_info(h_slot_conn, h_slot_info);
        }
    }

    ltrslot_destroy_connection(h_slot_conn);
    free(h_slot_info);

    return ret_val;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_store_config(void *h_card, TLTR *ltr_conn, DWORD cfg_sz, WORD card_id,
    TLTR_CARD_START_MODE start_mode, void (*convert_hcardtocfg)(const void *, void *)) {
    int slots_supported;
    t_ltrslot_conn h_slot_conn = LTRSLOT_CONN_INVAL;
    struct LTRSlotInfo *h_slot_info = NULL;
    INT ret_code = LTR_OK;

    assert((h_card != NULL) && (ltr_conn != NULL) && (cfg_sz > 0) && (convert_hcardtocfg != NULL));


    if (ret_code == LTR_OK)
        ret_code = ltrslot_replicate_connection(ltr_conn, &h_slot_conn);

    if (ret_code == LTR_OK)
        ret_code = ltrslot_check_support(h_slot_conn, &slots_supported);

    if (ret_code == LTR_OK) {
        if (!slots_supported)
            ret_code = LTR_ERROR_CARDSCONFIG_UNSUPPORTED;
    }

    if (ret_code == LTR_OK) {
        if ((h_slot_info = malloc(sizeof(struct LTRSlotInfo))) == NULL)
            ret_code = LTR_ERROR_MEMORY_ALLOC;
    }

    if (ret_code == LTR_OK)
        ret_code = ltrslot_get_info(h_slot_conn, h_slot_info);

    if (ret_code == LTR_OK) {
        if (!(h_slot_info->status & LTRSLOT_STATUSFLG_CONFIG)) {
            // Put config before saving
            void *cfg = malloc(cfg_sz);
            if (cfg == NULL)
                ret_code = LTR_ERROR_MEMORY_ALLOC;
            if (ret_code == LTR_OK) {
                convert_hcardtocfg(h_card, cfg);
                ret_code = ltrslot_put_config(h_slot_conn, cfg_sz, cfg);
            }
            free(cfg);
        }
    }

    if (ret_code == LTR_OK) {
        h_slot_info->status |= LTRSLOT_STATUSFLG_CONFIG;
        h_slot_info->card_id = card_id;
        h_slot_info->start_mode = start_mode;
        ret_code = ltrslot_put_info(h_slot_conn, h_slot_info);
    }

    if (ret_code == LTR_OK)
        ret_code = ltrslot_save_settings(h_slot_conn);


    ltrslot_destroy_connection(h_slot_conn);

    free(h_slot_info);

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_update_config(t_ltrslot_conn h_conn, WORD offs, DWORD sz, const void *payload) {
    INT ret_code = LTR_OK;

    ret_code = put_array_config(h_conn, memoffset_config+offs, sz, payload);

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
INT ltrslot_write(TLTR *h_ltr, WORD nslot, WORD offs, DWORD sz, const void *data) {
    return LTR_CratePutArray(h_ltr, SLOTCFG_ADDR(nslot, offs | SEL_SLOTS), data, sz);
}

/*------------------------------------------------------------------------------------------------*/
static INT put_array_config(t_ltrslot_conn h_conn, t_conf_addr addr, DWORD sz, const void *data) {
    return LTR_CratePutArray(&h_conn->h_channel, SLOTCFG_ADDR(h_conn->n_slot, addr | SEL_SLOTS),
                             data, sz);
}

/*------------------------------------------------------------------------------------------------*/
static INT put_slotcfg(void *h_card, TLTR *ltr_conn, DWORD cfg_sz, WORD card_id, int check_cfgexist,
    DWORD slot_status, TLTR_CARD_START_MODE start_mode,
    void (*conv_hcardtocfg)(const void *, void *)) {
    int slots_supported;
    t_ltrslot_conn h_slot_conn = LTRSLOT_CONN_INVAL;
    struct LTRSlotInfo *h_slot_info = NULL;
    INT ret_code = LTR_OK;

    assert((h_card != NULL) && (ltr_conn != NULL) && (cfg_sz > 0) && (conv_hcardtocfg != NULL));


    if (ret_code == LTR_OK)
        ret_code = ltrslot_replicate_connection(ltr_conn, &h_slot_conn);

    if (ret_code == LTR_OK)
        ret_code = ltrslot_check_support(h_slot_conn, &slots_supported);

    if (ret_code == LTR_OK) {
        if (!slots_supported)
            ret_code = LTR_ERROR_CARDSCONFIG_UNSUPPORTED;
    }

    if (ret_code == LTR_OK) {
        if ((h_slot_info = malloc(sizeof(struct LTRSlotInfo))) == NULL)
            ret_code = LTR_ERROR_MEMORY_ALLOC;
    }

    if (ret_code == LTR_OK)
        ret_code = ltrslot_get_info(h_slot_conn, h_slot_info);

    if (ret_code == LTR_OK) {
        const int cfg_iswritten = (!check_cfgexist ||
            !(h_slot_info->status & LTRSLOT_STATUSFLG_CONFIG));
        if (cfg_iswritten) {
            // Put config before saving
            void *cfg = malloc(cfg_sz);
            if (cfg == NULL)
                ret_code = LTR_ERROR_MEMORY_ALLOC;
            if (ret_code == LTR_OK) {
                conv_hcardtocfg(h_card, cfg);
                ret_code = ltrslot_put_config(h_slot_conn, cfg_sz, cfg);
            }
            free(cfg);
        }
    }

    if (ret_code == LTR_OK) {
        h_slot_info->status |= slot_status;
        h_slot_info->card_id = card_id;
        h_slot_info->start_mode = start_mode;
        ret_code = ltrslot_put_info(h_slot_conn, h_slot_info);
    }


    ltrslot_destroy_connection(h_slot_conn);

    free(h_slot_info);

    return ret_code;
}
