#include "cli.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_usart.h"
#include "stm32f4xx_dma.h"

#define CLI_PRINTABLE_CHAR_SPACE (0x20)
#define CLI_PRINTABLE_CHAR_MIN   (0x21)
#define CLI_PRINTABLE_CHAR_MAX   (0x7E)

struct cli_data_s cli_data;
struct cli_data_comb_s cli_data_comb;
char cli_buff_tx[CLI_BUFF_TX_LEN] __attribute__((aligned(16)));
char cli_buff_rx[CLI_BUFF_RX_LEN] __attribute__((aligned(16)));

char *cli_cmd_list[] = {
    "help",
    "force",
    "print",
    "invalid",
};

uint16_t (*cli_cmd_process_arr[])(char *, uint16_t) = {
    cli_cmd_process_help,
    cli_cmd_process_force,
    cli_cmd_process_print,
    cli_cmd_process_invalid,
};

void cli_init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    DMA_InitTypeDef  DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    /* init struct data */
    cli_data.rx_len = 0;
    cli_data.tx_len = 0;
    cli_data.buff_rx = cli_buff_rx;
    cli_data.buff_tx = cli_buff_tx;
    cli_data.frame_cnt = 0;

    cli_data_comb.force_available = 0;

    /* config cli uart pin */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    GPIO_PinAFConfig(CLI_PORT, GPIO_PinSource10, GPIO_AF_USART3);
    GPIO_PinAFConfig(CLI_PORT, GPIO_PinSource11, GPIO_AF_USART3);
    GPIO_InitStructure.GPIO_Pin = CLI_PIN_TX | CLI_PIN_RX;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(CLI_PORT, &GPIO_InitStructure);
    /* config cli uart function */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
    USART_DeInit(USART3);
    USART_OverSampling8Cmd(USART3, ENABLE);
    USART_InitStructure.USART_BaudRate = CLI_BAUDRATE;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART3, &USART_InitStructure);
    USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);
    USART_DMACmd(USART3, USART_DMAReq_Rx, ENABLE);
    USART_Cmd(USART3, ENABLE);
    /* Configure DMA controller to manage USART TX request */ 
    DMA_InitStructure.DMA_BufferSize = CLI_BUFF_TX_LEN;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t)(&(USART3->DR));
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)cli_buff_tx;
    DMA_Init(CLI_TX_STREAM, &DMA_InitStructure);
    /* Configure DMA controller to manage USART RX request */ 
    DMA_InitStructure.DMA_BufferSize = CLI_BUFF_RX_LEN;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)cli_buff_rx;
    DMA_Init(CLI_RX_STREAM, &DMA_InitStructure);
    /* Configure interrupt for USART3 RX */
    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
    USART_ITConfig(USART3, USART_IT_TC, ENABLE);
    /* clear IT flag */
    USART_GetITStatus(USART3, USART_IT_IDLE);
    USART_ReceiveData(USART3);
    USART_ClearITPendingBit(USART3, USART_IT_IDLE);
    USART_ClearITPendingBit(USART3, USART_IT_TC);
}

void cli_send_data(uint16_t len)
{
    USART_GetITStatus(USART3, USART_IT_IDLE);
    USART_ReceiveData(USART3);
    USART_ClearITPendingBit(USART3, USART_IT_IDLE);
    if (len == 0) {
        DMA_SetCurrDataCounter(CLI_RX_STREAM, CLI_BUFF_RX_LEN);
        DMA_Cmd(CLI_RX_STREAM, ENABLE);
        USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
    } else {
        DMA_SetCurrDataCounter(CLI_TX_STREAM, len);
        DMA_Cmd(CLI_TX_STREAM, ENABLE);
    }
}

void USART3_IRQHandler(void)
{
    uint16_t len;

    if (USART_GetITStatus(USART3, USART_IT_TC) != RESET)
    {
        USART_ClearITPendingBit(USART3, USART_IT_TC);
        DMA_ClearFlag(CLI_TX_STREAM, DMA_FLAG_TCIF3 | DMA_FLAG_HTIF3 | DMA_FLAG_TEIF3 | DMA_FLAG_DMEIF3 | DMA_FLAG_FEIF3);
        DMA_Cmd(CLI_TX_STREAM, DISABLE);
        /* after cli tx complete, config cli rx */
        USART_ReceiveData(USART3);
        USART_ClearITPendingBit(USART3, USART_IT_IDLE);
        DMA_SetCurrDataCounter(CLI_RX_STREAM, CLI_BUFF_RX_LEN);
        DMA_Cmd(CLI_RX_STREAM, ENABLE);
        USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
    }
    if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) {
        USART_ITConfig(USART3, USART_IT_IDLE, DISABLE);
        USART_ReceiveData(USART3);
        USART_ClearITPendingBit(USART3, USART_IT_IDLE);
        DMA_Cmd(CLI_RX_STREAM, DISABLE);
        DMA_ClearFlag(CLI_RX_STREAM, DMA_FLAG_TCIF1 | DMA_FLAG_HTIF1 | DMA_FLAG_TEIF1 | DMA_FLAG_DMEIF1 | DMA_FLAG_FEIF1);
        len = DMA_GetCurrDataCounter(CLI_RX_STREAM);
        cli_data.rx_len = CLI_BUFF_RX_LEN - len;
        cli_data.frame_cnt++;
    }
}

uint16_t cli_print_num10(char *buff, uint32_t num)
{
    uint16_t i = 0;
    uint32_t div, result;
    uint8_t non_zero = 0;
    div = 1000000000;
    while (1) {
        result = num / div;
        num = num % div;
        if (result) {
            non_zero = 1;
            buff[i] = (char)result + '0';
            i++;
        } else {
            if (non_zero) {
                buff[i] = '0';
                i++;
            } else {

            }
        }
        div /= 10;
        if (div == 0) {
            return i;
        }
    }
}

uint16_t cli_scan_num10(char *buff, uint32_t *num)
{
    uint32_t sum = 0;
    uint16_t i = 0;
    char c;

    while (1) {
        c = buff[i];
        if ((c >= '0') && (c <= '9')) {
            sum *= 10;
            sum += (uint32_t)(c - '0');
            i++;
        } else {
            *num = sum;
            return i;
        }
    }
}

uint16_t cli_string_copy(char *dst, char *src)
{
    uint16_t len = 0;
    char c;

    while(1) {
        c = src[len];
        if ((c >= CLI_PRINTABLE_CHAR_SPACE) && (c <= CLI_PRINTABLE_CHAR_MAX)) {
            dst[len] = c;
            len++;
        } else {
            return len;
        }
    }
}

uint16_t cli_string_copy_newline(char *dst, char *src)
{
    uint16_t len = cli_string_copy(dst, src);
    dst[len++] = '\r';
    dst[len++] = '\n';
    return len;
}

uint8_t cli_string_is_same(char *str1, char *str2, uint16_t *len)
{
    uint16_t i = 0;

    while (1) {
        if (str1[i] == str2[i]) {
            if ((str1[i] >= CLI_PRINTABLE_CHAR_MIN) && (str1[i] <= CLI_PRINTABLE_CHAR_MAX)) {
                i++;
            } else {
                *len = i;
                return 1;
            }
        } else {
            if (((str1[i] >= CLI_PRINTABLE_CHAR_MIN) && (str1[i] <= CLI_PRINTABLE_CHAR_MAX)) || \
                ((str2[i] >= CLI_PRINTABLE_CHAR_MIN) && (str2[i] <= CLI_PRINTABLE_CHAR_MAX))) {
                return 0;
            } else {
                *len = i;
                return 1;
            }
        }
    }
}

/* buff: is ready for check */
/* if is an cmd, then cmd  */
uint8_t cli_is_cmd(char *buff, uint8_t *cmd_idx, uint16_t *len)
{
    uint16_t length;
    uint16_t cmd_cnt;

    cmd_cnt = sizeof(cli_cmd_list) / sizeof(cli_cmd_list[0]);
    for (uint8_t i = 0; i < cmd_cnt; i++) {
        if (cli_string_is_same(buff, cli_cmd_list[i], &length)) {
            *cmd_idx = i;
            *len = length;
            return 1;
        }
    }
    return 0;
}

uint16_t cli_cmd_process_help(char *buff, uint16_t len)
{
    uint16_t len_tx = cli_string_copy_newline(cli_data.buff_tx, "cmd data!");
    return len_tx;
}

/* cmd example
force sig1=force
force sig2=free
force sig4=6540926
*/
uint16_t cli_cmd_process_force_error(char *buff)
{
    uint16_t len;

    len = cli_string_copy_newline(buff, "example:");
    len += cli_string_copy_newline(buff + len, "force sig1=force");
    len += cli_string_copy_newline(buff + len, "force sig2=free");
    len += cli_string_copy_newline(buff + len, "force sig4=6540926");
    return len;
}

uint16_t cli_cmd_process_force(char *buff, uint16_t len)
{
    uint8_t ch = 0;

    if ((buff[0] != ' ') || \
        (buff[1] != 's') || \
        (buff[2] != 'i') || \
        (buff[3] != 'g') || \
        ((buff[4] < '1') || (buff[4] > '4')) || \
        (buff[5] != '=')) {
        return cli_cmd_process_force_error(cli_data.buff_tx);
    }
    ch = buff[4] - '1';
    buff += 6;
    if ((buff[0] == 'f') && (buff[1] == 'o') && (buff[2] == 'r') && (buff[3] == 'c') && (buff[4] == 'e')) {
        cli_data_comb.force_available |= (1 << (ch + CLI_FORCE_FILTER_INPUT1));
        return cli_string_copy_newline(cli_data.buff_tx, "force succeed!");
    } else if ((buff[0] == 'f') && (buff[1] == 'r') && (buff[2] == 'e') && (buff[3] == 'e')) {
        cli_data_comb.force_available &= ~(1 << (ch + CLI_FORCE_FILTER_INPUT1));
        return cli_string_copy_newline(cli_data.buff_tx, "free succeed!");
    } else {
        uint32_t num;
        if (0 == cli_scan_num10(buff, &num)) {
            return cli_cmd_process_force_error(cli_data.buff_tx);
        } else {
            cli_data_comb.force_data[ch + CLI_FORCE_FILTER_INPUT1] = num;
            return cli_string_copy_newline(cli_data.buff_tx, "force number succeed!");
        }
    }
}

uint16_t cli_cmd_process_print(char *buff, uint16_t len)
{
    return 0;
}

uint16_t cli_cmd_process_invalid(char *buff, uint16_t len)
{
    return 0;
}

uint16_t cli_cmd_process(char *buff)
{
    uint8_t is_cmd;
    uint8_t cmd_idx;
    uint16_t cmd_len;

    is_cmd = cli_is_cmd(buff, &cmd_idx, &cmd_len);
    if (is_cmd == 0) {
        uint16_t len = cli_string_copy_newline(cli_data.buff_tx, "not support this cmd!");
        return len;
    } else {
        return cli_cmd_process_arr[cmd_idx](buff + cmd_len, cli_data.rx_len - cmd_len);
    }
}

void cli_loop(void)
{
    static uint32_t frame_cnt = 0;

    if (cli_data.frame_cnt == frame_cnt) {
        return;
    }
    frame_cnt = cli_data.frame_cnt;
    cli_data.buff_rx[cli_data.rx_len] = 0;
    cli_data.tx_len = cli_cmd_process(cli_data.buff_rx);
    cli_send_data(cli_data.tx_len);
}
