#include "omw_cmd.h"

#if defined(OMW_CMD_CTRL) || defined(OMW_AT_CMD)

void omw_cmd_uart_enable(uint8_t it_mdoe){
    omw_uart_init(OMW_CMD_CTRL_UART, OMW_CMD_CTRL_UART_BAUDRATE, it_mdoe);
    omw_gpio_set_func(OMW_CMD_CTRL_UART_RX_PIN, OMW_GPIO_CFG_VAL(OMW_GPIO_DRIVE_STRENTH3, OMW_CMD_CTRL_UART_RX_FUNC));
    omw_gpio_set_func(OMW_CMD_CTRL_UART_TX_PIN, OMW_GPIO_CFG_VAL(OMW_GPIO_DRIVE_STRENTH3, OMW_CMD_CTRL_UART_TX_FUNC));

    if (it_mdoe)
    {
        omw_enable_uart_rx_it(OMW_CMD_CTRL_UART);
    }
}

#ifdef OMW_CMD_CTRL
uint16_t  omw_cmd_uart_fifo_sz;
uint16_t  omw_cmd_uart_fifo_rd;
uint8_t * omw_cmd_uart_fifo_buf;

int omw_cmd_uart_it_rx(uint8_t * rx_buf, uint16_t len)
{
    uint16_t wt = omw_cmd_uart_get_rx_fifo_wt();
    uint16_t dz = 0, cur_rd = omw_cmd_uart_fifo_rd;

    if (cur_rd < wt)
    {
        dz = wt - cur_rd;
        if (dz >= len)
        {
            memcpy(rx_buf, omw_cmd_uart_fifo_buf + cur_rd, len);
            cur_rd += len;
        }
    }
    else if (cur_rd > wt)
    {
        uint16_t rfz = omw_cmd_uart_fifo_sz - cur_rd;
        uint16_t cp_len = (rfz >= len)?len:rfz;

        dz = (rfz + wt);
        if (dz >= len)
        {
            uint16_t rm_cp_len = len - cp_len;
            memcpy(rx_buf, omw_cmd_uart_fifo_buf + cur_rd, cp_len);
            cur_rd += cp_len;
            if (rm_cp_len > 0 && wt >= rm_cp_len)
            {
                memcpy(rx_buf + cp_len, omw_cmd_uart_fifo_buf, rm_cp_len);
                cur_rd = rm_cp_len;
            }
        }
    }

    if (dz < len) return 0;

    if (cur_rd >= omw_cmd_uart_fifo_sz) cur_rd = 0;

    omw_cmd_uart_fifo_rd = cur_rd;
    omw_cmd_uart_set_rx_fifo_rd(cur_rd);

    return len;
}
#endif
#endif

#ifdef OMW_AT_CMD
#define AT_CMD_MAXN       (16)
#define AT_CMD_MAXL       (80)
#define AT_CMD_MINL       (4)
#define AT_CMD_ST_IDX     (3)
#define AT_CMD_PATTERN    ('A' | ('T' << 8) | ('+' << 16))
#define AT_CMD_PATTERN_1  ('A' | ('T' << 8) | ('\r' << 16) | ('\n' << 24))
#define AT_CMD_UART       OMW_CMD_CTRL_UART

static uint32_t at_cmd_buf[AT_CMD_MAXL / 4 + 1];
static uint32_t at_cmd_rsp_buf[AT_CMD_MAXL / 4 + 1];
static uint8_t * p_at_cmd_rx_buf = (uint8_t *)at_cmd_buf;
static uint8_t * p_at_cmd_rsp_buf;

static uint8_t  at_cmd_rsp_tp_len;
static uint8_t  at_cmd_rx_ofset;
static uint8_t  at_cmd_q_c_idx;
static uint8_t  at_cmd_e_c_idx;
static uint8_t  at_cmd_nr;

static const char ** at_cmd_list;
static const uint8_t * at_cmd_len_tp;
static const AT_CMD_HANDLE * at_cmd_hdl;

void at_cmd_rsp(int8_t cmd)
{
    void * p_rsp   = p_at_cmd_rsp_buf;

    if (at_cmd_nr == cmd)
    {
        char err_rsp[] = "+ERR=-1\r\n";
        omw_uart_send(AT_CMD_UART, err_rsp, sizeof(err_rsp) - 1);  // - '\0'
    }
    else if (NULL == p_rsp)
    {//is set cmd
        char ok_rsp[] = "+OK\r\n";
        omw_uart_send(AT_CMD_UART, ok_rsp, sizeof(ok_rsp) - 1);  // - '\0'
    }
    else
    {//is query cmd
        uint8_t tp_len = at_cmd_rsp_tp_len;
        char q_rsp[] = "+=\r\n";

        omw_uart_send(AT_CMD_UART, q_rsp, 1);
        omw_uart_send(AT_CMD_UART, (void *)at_cmd_list[cmd], at_cmd_len_tp[cmd] & 0xF);
        omw_uart_send(AT_CMD_UART, q_rsp + 1, 1);
        omw_uart_send(AT_CMD_UART, p_rsp, tp_len);
        omw_uart_send(AT_CMD_UART, q_rsp + 2, 2);
    }
}

static uint8_t at_cmd_get_one_char()
{
    uint8_t cpl = (at_cmd_rx_ofset < AT_CMD_MINL) ? (AT_CMD_MINL - at_cmd_rx_ofset) : 1;

    //copy data from uart buf to process buf
    #ifdef OMW_CMD_CTRL
    uint8_t rl = omw_cmd_uart_it_rx(p_at_cmd_rx_buf + at_cmd_rx_ofset, cpl);
    #else
    uint8_t rl = omw_uart_rcv(AT_CMD_UART, p_at_cmd_rx_buf + at_cmd_rx_ofset, cpl);
    #endif

    at_cmd_rx_ofset += rl;

    return rl;
}

static int at_cmd_process(uint8_t cl)
{
    for (int i = 0; i < at_cmd_nr; i++)
    {
        if ((at_cmd_len_tp[i] & 0xF) == cl && 0 == memcmp(at_cmd_list[i], p_at_cmd_rx_buf + AT_CMD_ST_IDX, cl))
        {//found cmd
            uint8_t is_query = (at_cmd_q_c_idx != 0);
            AT_CMD_CB_AFTER_RSP  cb_after_rsp = NULL;

            p_at_cmd_rsp_buf = NULL;
            at_cmd_rsp_tp_len = 0;

            if (at_cmd_q_c_idx && !(at_cmd_len_tp[i] & AT_CMD_TYPE_Q)) break;
            if (at_cmd_e_c_idx && !(at_cmd_len_tp[i] & AT_CMD_TYPE_S)) break;
            if (!at_cmd_q_c_idx && !at_cmd_e_c_idx && !(at_cmd_len_tp[i] & AT_CMD_TYPE_N)) break;

            if (!at_cmd_hdl[i](is_query ? NULL : ((char *)at_cmd_buf) + at_cmd_e_c_idx + 1, &cb_after_rsp))
            {
                at_cmd_rsp(i);
                if (cb_after_rsp) cb_after_rsp();
                return 0;
            }

            break;
        }
    }

    return -1;
}

//call in omw_app_main()
void at_cmd_rx_process()
{
    // uint8_t rlt = 0;
    uint8_t rsp_code = 0;  //default rsp "+OK"

    do
    {
        if (!at_cmd_get_one_char()) return; // rlt;  //no new char

        // rlt = 1;

        if (at_cmd_rx_ofset < AT_CMD_MINL) return; // rlt;  //at least must rcved len of "AT+x"
        if (at_cmd_rx_ofset == AT_CMD_MINL)
        {//rcved len of "AT+x" or "AT\r\n"
            if (at_cmd_buf[0] == AT_CMD_PATTERN_1)
            {//is "AT\r\n"
                goto __RSP_OK; //rsp "+OK"
            }
            else if ((at_cmd_buf[0] & 0x00FFFFFF) != AT_CMD_PATTERN)
            {//is not start with "AT+", error patten, skip one byte, continue to rx and check again
                at_cmd_buf[0] >>= 8;
                at_cmd_rx_ofset--;
            }

            continue; //if found "AT+x", need to continue to rx one byte at least to be "AT+xx"
        }

        //at_cmd_rx_ofset > AT_CMD_MINL
        //found "AT+"

        //NOT found "\r\n", and > cmd max len
        if (at_cmd_rx_ofset > AT_CMD_MAXL)
        {
            goto __ERR_RETRY; //error
        }

        uint8_t prv_ch = p_at_cmd_rx_buf[at_cmd_rx_ofset - 2];
        uint8_t cur_ch = p_at_cmd_rx_buf[at_cmd_rx_ofset - 1];

        //    if (prv_ch == ' ' || cur_ch == ' ')
        //    {//error, do more char check if need
        //        goto __RSP_ERR;
        //    }

        rsp_code = at_cmd_nr; //set to rsp "+ERR=-1"

        if (prv_ch == '?')
        {//is query
            if (!at_cmd_q_c_idx && !at_cmd_e_c_idx) at_cmd_q_c_idx = at_cmd_rx_ofset - 2;  //ok
            else goto __RSP_ERR;  //error
        }
        else if (prv_ch == '=')
        {//is set
            if (!at_cmd_q_c_idx && !at_cmd_e_c_idx) at_cmd_e_c_idx = at_cmd_rx_ofset - 2; //ok
            else goto __RSP_ERR; //error
        }
        else if (prv_ch == '\r' && cur_ch == '\n')
        {//complete to rx one AT CMD
            // index of '?' or '=' must >= AT_CMD_MINL
            if (at_cmd_q_c_idx && p_at_cmd_rx_buf[at_cmd_rx_ofset - 3] != '?')  goto __RSP_ERR;  //error
            if (at_cmd_e_c_idx && p_at_cmd_rx_buf[at_cmd_rx_ofset - 3] == '=')  goto __RSP_ERR;  //error

            uint8_t  cmd_len; // = at_cmd_q_c_idx ? at_cmd_q_c_idx - AT_CMD_MINL : at_cmd_e_c_idx - AT_CMD_MINL;

            if (at_cmd_q_c_idx) cmd_len = at_cmd_q_c_idx - AT_CMD_MINL;
            else if (at_cmd_e_c_idx) cmd_len = at_cmd_e_c_idx - AT_CMD_MINL;
            else cmd_len = at_cmd_rx_ofset - AT_CMD_MINL - 2; // - len of "\r\n"

            p_at_cmd_rx_buf[at_cmd_rx_ofset - 2] = '\0';

            if (!at_cmd_process(cmd_len + 1)) goto __ERR_RETRY;
            else goto __RSP_ERR;
        }

        continue;

__RSP_OK:
        p_at_cmd_rsp_buf = NULL;
__RSP_ERR:
        at_cmd_rsp(rsp_code);

__ERR_RETRY:
        at_cmd_rx_ofset = 0;
        at_cmd_q_c_idx  = 0;
        at_cmd_e_c_idx  = 0;

        rsp_code = 0;
    } while (1);

    //never go here
    // return 1;
}

#ifdef OMW_CMD_CTRL
#define AT_CMD_UART_FIFO_BUF_SIZE  64   //need power of 2
uint8_t  at_cmd_uart_buf[AT_CMD_UART_FIFO_BUF_SIZE];
#endif

//call in omw_app_init()
void at_cmd_init(at_cmd_reg_info_tag * at_info)
{
    #ifdef OMW_CMD_CTRL
    omw_cmd_uart_set_rx_fifo(at_cmd_uart_buf, AT_CMD_UART_FIFO_BUF_SIZE);
    omw_cmd_uart_enable(1); //IT MODE
    omw_set_irq_prio(UART1_IRQn, IRQ_CRITICAL_PRIORITY);
    omw_enable_irq(UART1_IRQn);
    #else
    omw_cmd_uart_enable(0);
    #endif

    at_cmd_hdl  = at_info->at_cmd_hdl_tbl;
    at_cmd_list = at_info->at_cmd_name_tbl;
    at_cmd_len_tp  = at_info->at_cmd_len_tp_tbl;
    at_cmd_nr   = at_info->at_cmd_nr;
}

void at_cmd_rsp_usr_data(char * p_data, uint8_t len)
{
    memcpy(at_cmd_rsp_buf, p_data, len);
    p_at_cmd_rsp_buf  = (uint8_t *)at_cmd_rsp_buf;
    at_cmd_rsp_tp_len = len;
}

static const uint8_t cvt_char[] = "0123456789ABCDEF";

uint8_t at_cmd_value2str_10(uint32_t val, char * p_str)
{
    uint8_t idx = 0;
    uint8_t pos = 0;
    uint8_t tmp_buf[16];

    do
    {
        tmp_buf[pos++] = val % 10;
        val /= 10;
    }while(val);

    for (int j = 0; j < pos; j++)
    {
        p_str[idx++] = cvt_char[tmp_buf[pos - 1 - j]];
    }

    return idx;
}

/*
caller must be sure that the input data is correct
rv_ord: 0 not reverse order, 1 reverse byte order
return: the coverted str length, no '\0'
*/
uint8_t at_cmd_value2str_16(uint8_t * p_val, char * p_str, uint8_t len, uint8_t rv_ord)
{

    uint8_t   idx = 0;

    for (int i = 0; i < len; i++)
    {
        uint8_t ch  = p_val[i];

        if (!rv_ord)
        {
            p_str[idx++] = cvt_char[ch >> 4];
            p_str[idx++] = cvt_char[ch & 0xF];
        }
        else
        {
            uint8_t pos = (len << 1) - 1;

            pos -= idx;
            p_str[pos - 1] = cvt_char[ch >> 4];
            p_str[pos] = cvt_char[ch & 0xF];

            idx += 2;
        }
    }

    return idx;
}

/*
p_str must end with '\0' or ','
strlen(p_str) % 2 must == 0
p_val: if 10, must uint32_t buf
tp: 10 digit, 16 hex
    if 10: val must <= 32 bits
rv_ord: only for tp 16, 0 not reverse order, 1 reverse byte order
cvt_len: only for tp 16, cvted str len

return:
0xFF: error
0: end with '\0'
n: meet ',' and the offset of ',' in p_str
*/
uint8_t at_cmd_str2value(char * p_str, uint8_t * p_val, uint8_t tp, uint8_t rv_ord, uint8_t * cvt_len)
{
    int i = 0;
    int pos = 0;
    uint8_t  ofst = 1;
    uint32_t val = 0;
    uint8_t  tmp_buf[32]; //when tp == 16
    char cur_ch;

    do
    {
        cur_ch = p_str[i++];
        if (cur_ch == ',' || cur_ch == '\0')
        {
            break;
        }

        if (tp == 10)
        {
            if (!(cur_ch >= '0' && cur_ch <= '9')) return 0xFF;

            val = (val * 10) + (cur_ch - '0');
        }
        else
        {
            uint32_t cur_val;

            if (cur_ch >= '0' && cur_ch <= '9') cur_val = cur_ch - '0';
            else if (cur_ch >= 'a' && cur_ch <= 'f') cur_val = cur_ch - 'a' + 10;
            else if (cur_ch >= 'A' && cur_ch <= 'F') cur_val = cur_ch - 'A' + 10;
            else return 0xFF;

            val = (val << 4) | cur_val;
            if (!(ofst & 0x1))
            {
                if (!rv_ord)
                {
                    p_val[pos++] = val;
                }
                else
                {
                    tmp_buf[pos++] = val;
                }

                val  = 0;
            }

            ofst = 1 - ofst;
        }
    } while (1);

    if (tp == 16)
    {
        if (!ofst) return 0xFF; //strlen % 2 != 0
        if (rv_ord)
        {
            for (int i = 0; i < pos; i++)
            {
                p_val[i] = tmp_buf[pos - 1 - i];
            }
        }
    }
    else
    {
        memcpy(p_val, &val, 4);
    }

    if (cvt_len) *cvt_len = pos;

    if (cur_ch == '\0') return 0;
    return i;
}
#endif