/**
 * @file        commserial.c
 */

/* Includes *******************************************************************/
#include <stdint.h>
#include <stdbool.h>

#include "commserial.h"

extern uint16_t uiCOMMSERIALTimer;

/* Constants ******************************************************************/
#define COMMSERIAL_TIMEOUT          500
#define COMMSERIAL_STEP_HEADER      0
#define COMMSERIAL_STEP_DATA        1

/* Private variables **********************************************************/
static uint8_t commserial_rx[COMMSERIAL_LEN_MAX];
static uint8_t commserial_tx[COMMSERIAL_LEN_MAX];
static uint8_t commserial_step;
static uint8_t commserial_flags = 0;

/* Private functions **********************************************************/
/**
 * @brief - COMMSeriali_CheckSum()
 *          Calculates Checksum for the input byte buffer.
 *
 * @param   buffer  [in]    data to calculate the checksum
 * @param   length  [in]    length of the buffer array
 * @return  Value of the checksum
 */
 uint8_t COMMSeriali_CheckSum(uint8_t* buffer, const uint16_t length);

/**
 * @brief   COMMSeriali_Decode()
 *          Decodes the message header from the read commserial_buff.
 */
 void COMMSeriali_Decode(uint8_t* buffer, commserial_msg_t* msg);

/**
 * @brief   COMMSeriali_Encode()
 *          Encodes message header and the data into the byte stream and add
 *          the CRC to the end.
 *
 * @return  Total length of the encoded frame in bytes.
 */
 uint16_t COMMSeriali_Encode(commserial_msg_t* msg, uint8_t* buffer);

/**
 * @brief   COMMSeriali_FlushRX()
 *          Clears the internal buffer.
 */
 void COMMSeriali_FlushRX(void);

/**
 * @brief   COMMSeriali_FlushTX()
 *          Clears the internal buffer.
 */
 void COMMSeriali_FlushTX(void);

/**
 * @brief   COMMSeriali_Send_DMA()
 *          Transmits prepared buffer to the UART line in non-blocking DMA mode.
 */
 void COMMSeriali_Send_DMA(void);

 uint8_t COMMSeriali_CheckSum(uint8_t* buffer, const uint16_t length)
{
    /* Initial value is 0 */
    uint8_t cs = 0x00;

    /* Resulting CRC is calculated of buffer XORed with current CRC */
    for(uint8_t i = 0; i < length; i++) {
        cs += buffer[i];
    }

    return ~cs;
}

 void COMMSeriali_Decode(uint8_t* buffer, commserial_msg_t* msg)
{
    uint8_t csReceived;
    uint8_t csComputed;
    uint16_t frameLength;

    /* Read frame length */
    frameLength = buffer[1] + COMMSERIAL_LEN_FRAME_OFFSET;

    /* Check CheckSum */
    csReceived = buffer[frameLength - 1];
    csComputed = COMMSeriali_CheckSum(buffer, frameLength - 1);

    /* Read header */
    msg->Valid       = (csReceived == csComputed) ? 1 : 0;
    msg->DataLength  = frameLength - COMMSERIAL_LEN_NONDATA;
    msg->Command     = buffer[2];

    /* Read data */
    for(uint8_t i = 0; i < msg->DataLength; i++) {
        msg->Data[i] = buffer[i + COMMSERIAL_LEN_HEADER];
    }
}

 uint16_t COMMSeriali_Encode(commserial_msg_t* msg, uint8_t* buffer)
{
    uint16_t frameLength;

    frameLength = msg->DataLength + COMMSERIAL_LEN_NONDATA;
    buffer[0] = COMMSERIAL_STX;
    buffer[1] = frameLength - COMMSERIAL_LEN_FRAME_OFFSET;
    buffer[2] = msg->Command;

    for(uint8_t i = 0; i < msg->DataLength; i++) {
        buffer[i + COMMSERIAL_LEN_HEADER] = msg->Data[i];
    }

    buffer[frameLength - 1] = COMMSeriali_CheckSum(buffer, frameLength - 1);
    return frameLength;
}

 void COMMSeriali_FlushRX(void)
{
    for(int16_t i = 0u; i < COMMSERIAL_LEN_MAX; i++) {
        commserial_rx[i] = 0u;
    }
}

 void COMMSeriali_FlushTX(void)
{
    for(int16_t i = 0u; i < COMMSERIAL_LEN_MAX; i++) {
        commserial_tx[i] = 0u;
    }
}

/* Public functions ***********************************************************/
 void COMMSeriald_Init(void)
{
    commserial_step = COMMSERIAL_STEP_HEADER;
    COMMSeriali_FlushRX();
    COMMSeriali_FlushTX();
    HAL_UART_Receive_DMA(COMMSERIAL_UART, commserial_rx, COMMSERIAL_LEN_HEADER);
}

 void COMMSeriald_Read(void)
{
    uint16_t fbp = 0;       /*!< First byte position */
    uint16_t i = 0;
    uint16_t j = 0;
    uint16_t frameLength = 0;

    /* Read the header in the first DMA transfer */
    if(COMMSERIAL_STEP_HEADER == commserial_step) {
        /* Find the position of the first byte */
        while(fbp < COMMSERIAL_LEN_HEADER && commserial_rx[fbp] != COMMSERIAL_STX){
            fbp++;
        }

        /*
         * When the first byte is at first position, everything is fine and
         * and receive the data part.
         */
        if(0 == fbp) {
            commserial_step = COMMSERIAL_STEP_DATA;
            frameLength = commserial_rx[1] + COMMSERIAL_LEN_FRAME_OFFSET;

            /* Read data */
            HAL_UART_Receive_DMA(COMMSERIAL_UART,
                                 commserial_rx + COMMSERIAL_LEN_HEADER,
                                 frameLength - COMMSERIAL_LEN_HEADER);
        }
        else {
            /*
             * When the first byte is shifted, shift the buffer.
             */
            if(fbp < COMMSERIAL_LEN_HEADER) {
                j = 0;
                i = fbp;
                while(i < COMMSERIAL_LEN_HEADER) {
                    commserial_rx[j] = commserial_rx[i];
                    i++;
                    j++;
                }
            }
            /*
             * Read remaining bytes. When first byte position is zero, the whole
             * header is read again
             */
            HAL_UART_Receive_DMA(COMMSERIAL_UART, commserial_rx + COMMSERIAL_LEN_HEADER - fbp, fbp);
        }

        /* Reset timer */
        uiCOMMSERIALTimer = COMMSERIAL_TIMEOUT;
    }
    /* Read the data in the second DMA receive */
    else {
        commserial_step = COMMSERIAL_STEP_HEADER;
        commserial_flags |= COMMSERIAL_FLAG_RX;
    }
}

 void COMMSeriald_Error(void)
{
    HAL_UART_AbortReceive(COMMSERIAL_UART);
    COMMSeriali_FlushRX();
    COMMSeriali_FlushTX();
    commserial_step = COMMSERIAL_STEP_HEADER;
    HAL_UART_Receive_DMA(COMMSERIAL_UART, commserial_rx, COMMSERIAL_LEN_HEADER);
    uiCOMMSERIALTimer = COMMSERIAL_TIMEOUT * 100;
}

 uint8_t COMMSeriald_LoadMsg(commserial_msg_t* msg)
{
    uint8_t newMsg = 0;

    if(COMMSERIAL_FLAG_RX == (commserial_flags & COMMSERIAL_FLAG_RX)) {
        commserial_flags &= ~COMMSERIAL_FLAG_RX;
        COMMSeriali_Decode(commserial_rx, msg);
        newMsg = 1;
    }

    if(COMMSERIAL_FLAG_ERR == (commserial_flags & COMMSERIAL_FLAG_ERR)) {
        commserial_flags &= ~COMMSERIAL_FLAG_ERR;
        COMMSeriald_Error();
    }

    if(uiCOMMSERIALTimer == 0) {
        COMMSeriald_Error();
    }

    return newMsg;
}

 void COMMSeriald_SendMsg(commserial_msg_t* msg)
{
    uint16_t frameLength;
    COMMSeriali_FlushRX();
    COMMSeriali_FlushTX();
    frameLength = COMMSeriali_Encode(msg, commserial_tx);
    HAL_UART_Transmit_DMA(COMMSERIAL_UART, commserial_tx, frameLength);
    HAL_UART_Receive_DMA(COMMSERIAL_UART, commserial_rx, COMMSERIAL_LEN_HEADER);
    uiCOMMSERIALTimer = COMMSERIAL_TIMEOUT * 100;
}

void COMMSeriald_RaiseFlag(uint16_t flag)
{
    commserial_flags |= flag;
}
