#include "flash_iface_ltr.h"
#include "flash_sendrecv.h"
#include "ltrapi.h"
#include "ltrmodule.h"
#include <string.h>


#define LTR_CMD_ROMIO             (LTR010CMD | 0x60UL)
#define LTR_CMD_ROM_RESP          (LTR010CMD | 0x60UL)
#define LTR_CHECK_ROM_RESP(r)     (((r) & 0x0000C0FF) != LTR_CMD_ROM_RESP) ? \
                                         LTR_ERROR_INVALID_CMD_RESPONSE : LTR_OK

#define LTR_CMD_MAX_ONFLY       (512*1024)
#define LTR_CMD_MIN_DROP_RECV        1024

#define LTR_CMD_WT_MKS           2



typedef struct  {
    TLTR *hnd;
    size_t drop_size;
} t_intf_internal_pars;

static t_flash_errs f_send(struct st_flash_iface *iface, const unsigned char* vals, size_t *psize);
static t_flash_errs f_recv(struct st_flash_iface *iface, unsigned char* vals, size_t *psize);
static t_flash_errs f_select(t_flash_iface *iface, size_t total_size);
static t_flash_errs f_unselect(t_flash_iface *iface);

static const t_flash_sendrecv_iface f_sendrecv_iface = {
    f_send,
    f_recv,
    f_select,
    f_unselect
};


static INT f_spiflash_send(TLTR *hnd, const BYTE *data, size_t len) {
    DWORD out[32];
    int i = 0;
    INT res=LTR_OK;

    while (len-- && (res==LTR_OK)) {
        if (data != NULL) {
            out[i++] = LTR_CMD_ROMIO | ((DWORD)*data << 24) | 1;
            data++;
        } else {
            out[i++] = LTR_CMD_ROMIO | 1;
        }

        /* Передаем команды блоками по 32 */
        if ((i == sizeof(out)/sizeof(out[0])) || (len == 0)) {
            res = ltr_module_send_cmd(hnd, out, i);
            i = 0;
        }
    }
    return res;
}

INT ltr_spiflash_receive(TLTR *hnd, BYTE *data, size_t len) {
    DWORD in[32];
    DWORD i = 0, count;
    INT res=LTR_OK;

    while (len && (res==LTR_OK)) {
        count = sizeof(in)/sizeof(in[0]);
        if (count > len)
            count = (DWORD)len;

        /* Принимаем ответы. */
        res = ltr_module_recv_cmd_resp(hnd, in, count);

        /* Проверяем корректность. */
        for (i = 0; (i < count) && (res==LTR_OK); i++) {
            res = LTR_CHECK_ROM_RESP(in[i]);
            if ((data != NULL)  && (res==LTR_OK)) {
                *data++ = in[i] >> 24;
            }
        }
        len -= count;
    }
    return res;
}

static t_flash_errs f_drop_recv(struct st_flash_iface *iface, size_t rcv_size) {
    INT err = 0;
    t_intf_internal_pars *pars = (t_intf_internal_pars *)iface->port_data;
    if ((rcv_size == 0) || (rcv_size > pars->drop_size)) {
        rcv_size = pars->drop_size;
    }

    if (rcv_size != 0) {
        err = ltr_spiflash_receive(pars->hnd, NULL, rcv_size);
        if (err==LTR_OK) {
            pars->drop_size -= rcv_size;
        }
    }
    return err;
}

static t_flash_errs f_drop_add(struct st_flash_iface *iface, size_t drop_size) {
    t_flash_errs err = 0;
    t_intf_internal_pars *pars = (t_intf_internal_pars *)iface->port_data;
    pars->drop_size += drop_size;
    /* для того, чтобы не переполнить буфер ltrd ограничиваем количество отложенных
     * слов на прием. При привышении - пытаемся принять лишние слова */
    if (pars->drop_size > LTR_CMD_MAX_ONFLY) {
        size_t rcv_size = pars->drop_size - LTR_CMD_MAX_ONFLY;
        if (rcv_size < LTR_CMD_MIN_DROP_RECV)
            rcv_size = LTR_CMD_MIN_DROP_RECV;
        err = f_drop_recv(iface, rcv_size);
    }
    return err;
}

static t_flash_errs f_send(struct st_flash_iface *iface,
                           const unsigned char* vals, size_t *psize) {
    t_intf_internal_pars *pars = (t_intf_internal_pars *)iface->port_data;
    t_flash_errs err;
    err = f_spiflash_send(pars->hnd, vals, *psize);
    if (!err)
        *psize = 0;
    return err;
}

static t_flash_errs f_recv(struct st_flash_iface *iface,
                           unsigned char* vals, size_t *psize) {
    INT err=0;
    t_intf_internal_pars *pars = (t_intf_internal_pars*)iface->port_data;
    size_t rcv_size = *psize;

    /* выбрасываемые данные можем принять позже */
    if (vals==NULL) {
        err = f_drop_add(iface, rcv_size);
        *psize = 0;
    } else {
        err = f_drop_recv(iface, 0);
        if ((err==LTR_OK) && (rcv_size!=0)) {
            err = ltr_spiflash_receive(pars->hnd, vals, rcv_size);
            if (err==LTR_OK) {
                *psize = 0;
            }
        }
    }
    return err;
}


static t_flash_errs f_wait(struct st_flash_iface *iface, unsigned tout_mks) {
    INT err = LTR_OK;
    t_intf_internal_pars *pars = (t_intf_internal_pars *)iface->port_data;
    DWORD cmds[64];
    unsigned i;

    for (i=0; i < sizeof(cmds)/sizeof(cmds[0]); i++) {
        cmds[i] = LTR_CMD_ROMIO;
    }
    while (!err && tout_mks) {
        size_t snd_size = tout_mks/LTR_CMD_WT_MKS + 1;
        DWORD snd_time;
        if (snd_size > sizeof(cmds)/sizeof(cmds[0]))
            snd_size = sizeof(cmds)/sizeof(cmds[0]);

        snd_time = (DWORD)(snd_size * LTR_CMD_WT_MKS);

        err = ltr_module_send_cmd(pars->hnd, cmds, (DWORD)snd_size);
        if (err==LTR_OK) {
            err = f_drop_add(iface, snd_size);
        }
        tout_mks = tout_mks < snd_time ? 0 : tout_mks - snd_time;
    }
    return err;
}

static t_flash_errs f_select(t_flash_iface *iface, size_t total_size) {
    return 0;
}

static t_flash_errs f_unselect(t_flash_iface *iface) {
    INT err = LTR_OK;
    t_intf_internal_pars *pars = (t_intf_internal_pars *)iface->port_data;
    DWORD out;
    out = LTR_CMD_ROMIO;
    err = ltr_module_send_cmd(pars->hnd, &out, 1);

    if (err==LTR_OK) {
        err = f_drop_add(iface, 1);
    }
    return err;
}

static t_flash_errs f_flush(t_flash_iface *iface) {
    return f_drop_recv(iface, 0);
}


t_flash_errs flash_iface_ltr_init(t_flash_iface *iface, TLTR *channel) {
    INT err = (iface==NULL) || (channel==NULL) ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (!err) {   
        t_intf_internal_pars *pars = calloc(1, sizeof(t_intf_internal_pars));
        if (pars == NULL) {
            err = LTR_ERROR_MEMORY_ALLOC;
        } else {
            pars->hnd = channel;
            memset(iface, 0, sizeof(t_flash_iface));
            iface->port_exec_tout_ms = LTR_MODULE_CMD_RECV_TIMEOUT;
            iface->wait_treshold_us = 25 * 1000;
            iface->port_data = pars;
            flash_sendrecv_init(iface, &f_sendrecv_iface);
            iface->flush = f_flush;
            iface->wait = f_wait;
            iface->max_transf_size = LTR_CMD_MAX_ONFLY;
        }
    }
    return err;
}

t_flash_errs flash_iface_ltr_set_channel(t_flash_iface *iface, TLTR *channel) {
    INT err = (iface==NULL) || (channel==NULL) || (iface->port_data==NULL) ?
                LTR_ERROR_PARAMETERS : LTR_OK;
    if (!err) {
        t_intf_internal_pars *pars = (t_intf_internal_pars*)iface->port_data;
        pars->hnd = channel;
    }
    return err;
}



t_flash_errs flash_iface_ltr_close(t_flash_iface *iface) {
    INT err = LTR_OK;
    if (iface->flush)
        iface->flush(iface);
    free(iface->port_data );
    iface->port_data = NULL;
    return err;
}


INT flash_iface_ltr_conv_err(t_flash_errs err) {
    if (!err)
        return LTR_OK;
    if (err == FLASH_ERR_INVALID_PARAMS)
        return LTR_ERROR_PARAMETERS;
    if (err == FLASH_ERR_WAIT_RDY_TOUT)
        return LTR_ERROR_FLASH_WAIT_RDY_TOUT;
    if (err == FLASH_ERR_NO_DEVICE)
        return LTR_ERROR_FLASH_NOT_PRESENT;
    if (err == FLASH_ERR_INVALID_DEVICE)
        return LTR_ERROR_FLASH_UNSUPPORTED_ID;
    if (err == FLASH_ERR_ADDR_OUT_OF_MEM)
        return LTR_ERROR_FLASH_INVALID_ADDR;
    if (err <= FLASH_ERR_FIRST)
        return LTR_ERROR_FLASH_OP_FAILED;
    return err;
}
