//
// Created by dipak on 18.05.18.
//

#include "Serial.h"
#include <stdlib.h>

Serial Debug(0);
Serial *Serial::Serial0 = nullptr;

//Serial *Serial::Serial1 = nullptr;

Serial *Serial::Serial2 = nullptr;

Serial *Serial::Serial3 = nullptr;

Serial::Serial(const uint8_t port)
{
    this->portNumber = port;

    switch (portNumber) {

        case 0:
            if (Serial0 == nullptr) {
                this->UDRn = &UDR0;
                this->UCSRnA = &UCSR0A;
                this->UCSRnB = &UCSR0B;
                this->UCSRnC = &UCSR0C;
                this->UBRRnH = &UBRR0H;
                this->UBRRnL = &UBRR0L;
                this->TXENn = TXEN0;
                this->RXENn = RXEN0;
                this->UDREn = UDRE0;
                this->U2Xn = U2X0;
                this->RXCIEn = RXCIE0;
                this->USBSn = USBS0;
                this->UPMn0 = UPM00;
                this->UPMn1 = UPM01;
                this->UCSZn0 = UCSZ00;
                this->UCSZn1 = UCSZ01;
                this->UCSZn2 = UCSZ02;
                this->UMSELn0 = UMSEL00;
                this->UMSELn1 = UMSEL01;
                this->UDRIEn = UDRIE0;
                Serial0 = this;
            }
            break;

       /*
         case 1:
            if (Serial1 == nullptr) {
                this->UDRn = &UDR1;
                this->UCSRnA = &UCSR1A;
                this->UCSRnB = &UCSR1B;
                this->UCSRnC = &UCSR1C;
                this->UBRRnH = &UBRR1H;
                this->UBRRnL = &UBRR1L;
                this->TXENn = TXEN1;
                this->RXENn = RXEN1;
                this->UDREn = UDRE1;
                this->U2Xn = U2X1;
                this->RXCIEn = RXCIE1;
                this->USBSn = USBS1;
                this->UPMn0 = UPM10;
                this->UPMn1 = UPM11;
                this->UCSZn0 = UCSZ10;
                this->UCSZn1 = UCSZ11;
                this->UCSZn2 = UCSZ12;
                this->UMSELn0 = UMSEL10;
                this->UMSELn1 = UMSEL11;
                this->UDRIEn = UDRIE1;
                Serial1 = this;
            }
            break;
        */

        case 2:
            if (Serial2 == nullptr) {
                this->UDRn = &UDR2;
                this->UCSRnA = &UCSR2A;
                this->UCSRnB = &UCSR2B;
                this->UCSRnC = &UCSR2C;
                this->UBRRnH = &UBRR2H;
                this->UBRRnL = &UBRR2L;
                this->TXENn = TXEN2;
                this->RXENn = RXEN2;
                this->UDREn = UDRE2;
                this->U2Xn = U2X2;
                this->RXCIEn = RXCIE2;
                this->USBSn = USBS2;
                this->UPMn0 = UPM20;
                this->UPMn1 = UPM21;
                this->UCSZn0 = UCSZ20;
                this->UCSZn1 = UCSZ21;
                this->UCSZn2 = UCSZ22;
                this->UMSELn0 = UMSEL20;
                this->UMSELn1 = UMSEL21;
                this->UDRIEn = UDRIE2;
                Serial2 = this;
            }
            break;

        case 3:
            if (Serial3 == nullptr) {
                this->UDRn = &UDR3;
                this->UCSRnA = &UCSR3A;
                this->UCSRnB = &UCSR3B;
                this->UCSRnC = &UCSR3C;
                this->UBRRnH = &UBRR3H;
                this->UBRRnL = &UBRR3L;
                this->TXENn = TXEN3;
                this->RXENn = RXEN3;
                this->UDREn = UDRE3;
                this->U2Xn = U2X3;
                this->RXCIEn = RXCIE3;
                this->USBSn = USBS3;
                this->UPMn0 = UPM30;
                this->UPMn1 = UPM31;
                this->UCSZn0 = UCSZ30;
                this->UCSZn1 = UCSZ31;
                this->UCSZn2 = UCSZ32;
                this->UMSELn0 = UMSEL30;
                this->UMSELn1 = UMSEL31;
                this->UDRIEn = UDRIE3;
                Serial3 = this;
            }
            break;

        default:break;

    }
}

/*!
 * Function to initialise USART communication
 * @param baud The USART communication baud rate - defaulted to 9600
 * @param mode Sets the operating mode. Either Asynchronous Normal mode, Asynchronous Double Speed mode or Synchronous
 * Master mode. Possible value are:
 *      ASYNC_NORMAL        = 0,
 *      ASYNC_DOUBLE_SPEED  = 1,
 *      SYNC_MASTER         = 2
 * @param parity Sets the parity bit - defaulted to NONE. Possible values are:
 *      NONE    = 0,
 *      EVEN    = 2,
 *      ODD     = 3
 * @param stopBits Sets the number of stop bits - default to ONE. Possible values are:
 *      ONE = 1,
 *      TWO = 2
 * @param frameLength Sets the number of bits per frame - default value of 8 bits(EIGHT_BITS). Possible values are:
 *      FIVE_BITS   = 5,
 *      SIX_BITS    = 6,
 *      SEVEN_BITS  = 7,
 *      EIGHT_BITS  = 8,
 *      NINE_BITS   = 9
 */
void Serial::begin(const uint32_t baud,
                   const USARTOperatingMode mode,
                   const USARTParity parity,
                   const USARTStopBits stopBits,
                   const USARTFrameLength frameLength)
{
    this->Parity = parity;
    this->setParity(Parity);

    this->StopBits = stopBits;
    this->setStopBit(StopBits);

    this->FrameLength = frameLength;
    this->setFrameLength(FrameLength);

    this->flushBuffer();

    /* Enable receiver and transmitter */
    *(this->UCSRnB) |= (1 << this->TXENn);
    *(this->UCSRnB) |= (1 << this->RXENn);
    *(this->UCSRnB) |= (1 << this->RXCIEn);

    this->OperatingMode = mode;
    this->setOpMode(baud, OperatingMode);
}

/*!
 * Function sets the Sets the operating mode. Either Asynchronous Normal mode, Asynchronous Double Speed mode or
 * Synchronous Master mode
 * @param baud The USART communication baud rate
 * @param mode The desired operating mode
 */
void Serial::setOpMode(const uint32_t baud,
                       const enum USARTOperatingMode mode)
{
    uint16_t bRate = 0;

    // compute baud rate value for UBRR register
    /***********************************************************************/
    /*UMSELn Bits Settings                                                 */
    /*      UMSELn1         UMSELn0         Mode                           */
    /*        0                0            Asynchronous USART             */
    /*        0                1            Synchronous USART              */
    /*        1                0            (Reserved)                     */
    /*        1                1            Master SPI (MSPIM)             */
    /***********************************************************************/
    switch (mode) {

        case USARTOperatingMode::SYNC_MASTER:bRate = static_cast<uint16_t>((F_CPU / 2 / baud - 1) / 2);
            *(this->UCSRnC) |= (1 << this->UMSELn0);
            *(this->UCSRnC) &= ~(1 << this->UMSELn1);
            break;

        case USARTOperatingMode::ASYNC_DOUBLE_SPEED:bRate = static_cast<uint16_t>((F_CPU / 4 / baud - 1) / 2);
            // UMSELn0 = 0 and UMSELn1 = 0 for asynchronous mode
            *(this->UCSRnC) &= ~(1 << this->UMSELn0);
            *(this->UCSRnC) &= ~(1 << this->UMSELn1);
            break;

        case USARTOperatingMode::ASYNC_NORMAL:bRate = static_cast<uint16_t>((F_CPU / 16 / baud) - 1);
            // UMSELn0 = 0 and UMSELn1 = 0 for asynchronous mode
            *(this->UCSRnC) &= ~(1 << this->UMSELn0);
            *(this->UCSRnC) &= ~(1 << this->UMSELn1);
            break;
    }

    *(this->UBRRnH) = (uint8_t) (bRate >> 8);
    *(this->UBRRnL) = (uint8_t) (bRate & 0xFF);
}

/*!
 * Function to set the number of bits per frame
 * @param frameLength The frame length value. Possible values are:
 *      FIVE_BITS   = 5,
 *      SIX_BITS    = 6,
 *      SEVEN_BITS  = 7,
 *      EIGHT_BITS  = 8,
 *      NINE_BITS   = 9
 */
void Serial::setFrameLength(enum USARTFrameLength frameLength)
{
    /************************************************************************/
    /* @method                                                              */
    /* Set UART/USART frame length (5 to 9 bits)                            */
    /* @param frameLength                                                   */
    /*          the frame length - values from USARTFrameLength.xxx       */
    /* UCSZn Bits Settings                                                  */
    /*  UCSZn2       UCSZn1      UCSZn0       Character Size                */
    /*    0            0           0               5-bit                    */
    /*    0            0           1               6-bit                    */
    /*    0            1           0               7-bit                    */
    /*    0            1           1               8-bit                    */
    /*    1            1           1               9-bit                    */
    /************************************************************************/
    switch (frameLength) {
        case USARTFrameLength::FIVE_BITS:*(this->UCSRnC) &= ~(1 << this->UCSZn0);
            *(this->UCSRnC) &= ~(1 << this->UCSZn1);
            *(this->UCSRnB) &= ~(1 << this->UCSZn2);
            break;

        case USARTFrameLength::SIX_BITS:*(this->UCSRnC) |= (1 << this->UCSZn0);
            *(this->UCSRnC) &= ~(1 << this->UCSZn1);
            *(this->UCSRnB) &= ~(1 << this->UCSZn2);
            break;

        case USARTFrameLength::SEVEN_BITS:*(this->UCSRnC) &= ~(1 << this->UCSZn0);
            *(this->UCSRnC) |= (1 << this->UCSZn1);
            *(this->UCSRnB) &= ~(1 << this->UCSZn2);
            break;

        case USARTFrameLength::EIGHT_BITS:*(this->UCSRnC) |= (1 << this->UCSZn0);
            *(this->UCSRnC) |= (1 << this->UCSZn1);
            *(this->UCSRnB) &= ~(1 << this->UCSZn2);
            break;

        case USARTFrameLength::NINE_BITS:
            // set data transmission mode: 9-bit (UCSZn2 = 1; UCSZn1 = 1; UCSZn0 = 1;)
            *(this->UCSRnC) |= (1 << this->UCSZn0);
            *(this->UCSRnC) |= (1 << this->UCSZn1);
            *(this->UCSRnB) |= (1 << this->UCSZn2);
            break;

        default: // Defaulting to 8 bits
            *(this->UCSRnC) |= (1 << this->UCSZn0);
            *(this->UCSRnC) |= (1 << this->UCSZn1);
            *(this->UCSRnB) &= ~(1 << this->UCSZn2);
            break;
    }
}

/*!
 * Function sets the number of stop bits to be used in the USART communication
 * @param stopBit Sets the number of stop bits - default to ONE. Possible values are:
 *      ONE = 1,
 *      TWO = 2
 */
void Serial::setStopBit(USARTStopBits stopBit)
{
    /************************************************************************/
    /* @method                                                              */
    /* Set UART/USART transmission stop bit                                 */
    /* @param stopBit                                                       */
    /*          the stop bit - values from StopBits.xxx               */
    /* USBS Bit Settings                                                    */
    /* USBSn = 0 => 1 Stop bit                                              */
    /* USBSn = 1 => 2 Stop bit                                              */
    /************************************************************************/
    switch (stopBit) {
        // one stop bit
        case USARTStopBits::ONE:*(this->UCSRnC) &= ~(1 << this->USBSn);
            break;
            // two stop bits
        case USARTStopBits::TWO:*(this->UCSRnC) |= (1 << this->USBSn);
            break;
    }
}

/*!
 * Function sets the partity bits to be used for the USART communication
 * @param parity Sets the parity bit - defaulted to NONE. Possible values are:
 *      NONE    = 0,
 *      EVEN    = 2,
 *      ODD     = 3
 */
void Serial::setParity(enum USARTParity parity)
{
    /************************************************************************/
    /* @method                                                              */
    /* Set UART/USART transmission parity                                   */
    /* @param parity                                                        */
    /*          the parity - values from USARTParity.xxx                  */
    /*  UPMn Bits Settings                                                  */
    /*  UPMn1       UPMn0           Parity Mode                             */
    /*    0           0             Disabled                                */
    /*    0           1             Reserved                                */
    /*    1           0             Enabled, Even Parity                    */
    /*    1           1             Enabled, Odd Parity                     */
    /************************************************************************/
    switch (parity) {
        case USARTParity::NONE:*(this->UCSRnC) &= ~(1 << this->UPMn0);
            *(this->UCSRnC) &= ~(1 << this->UPMn1);
            break;

        case USARTParity::EVEN:*(this->UCSRnC) &= ~(1 << this->UPMn0);
            *(this->UCSRnC) |= (1 << this->UPMn1);
            break;

        case USARTParity::ODD:*(this->UCSRnC) |= (1 << this->UPMn0);
            *(this->UCSRnC) |= (1 << this->UPMn1);
            break;
    }
}

/*!
 * Clear the buffers - reset buffer pointers to their initial state
 * @return Either true or False.
 */
bool Serial::clear()
{
    /************************************************************************/
    /* @method                                                              */
    /* Clear the buffers - reset buffer pointers to their initial state     */
    /* @return true so it can be used in logical expressions with ease      */
    /************************************************************************/
    bool txEnabled = (*(this->UCSRnB) & (1 << this->TXENn)) > 0;
    // disable USART data receiving until clearing the buffer
    if (txEnabled) {
        *(this->UCSRnB) &= ~(1 << this->TXENn);
    }
    this->flushBuffer();
    if (txEnabled) {
        *(this->UCSRnB) |= (1 << this->TXENn);
    }
    return true;
}

/*!
 * Function flushes the transmission and receive buffer indices
 */
void Serial::flushBuffer()
{
    this->rxHeadIndex = 0;
    this->rxTailIndex = 0;
    this->txHeadIndex = 0;
    this->txTailIndex = 0;
}

/*!
 * Function reads one byte at a time from the receiver buffer, and increments the tail index by one
 * @return one byte from the receiver buffer
 */
uint8_t Serial::read()
{
    uint8_t tmptail;
    /* calculate /store buffer index */
    tmptail = static_cast<uint8_t>((this->rxTailIndex + 1) & (BUFFER_SIZE - 1));
    this->rxTailIndex = tmptail;
    /* get data from receive buffer */
    if (rxBuffer[tmptail] == TERMINATOR) this->pending--; //Read one complete msg
    return rxBuffer[tmptail];
}

/*!
 * Function to transmit one byte of data
 * @param data Data to be transmitted
 */
void Serial::write(const uint8_t data)
{
    // Create a temporary index to point to the head of the buffer
    uint8_t tmp_head_index;
    // Calculate the next value of the head index
    tmp_head_index = static_cast<uint8_t>((txHeadIndex + 1) & (BUFFER_SIZE - 1));

    while (tmp_head_index == Serial::txTailIndex) { ;/* wait for free space in buffer */
    }
    // Load data into the transmission buffer and the next free position i.e., tmp_head_index
    txBuffer[tmp_head_index] = data;
    // Update global index to the incremented value
    txHeadIndex = tmp_head_index;
    // Enable UDRE interrupt
    *(this->UCSRnB) |= (1 << this->UDRIEn); // enable UDRE interrupt
}

/*!
 * Function to transmit a char array. Overloaded write() function
 * @param data Pointer to the a char array
 */
void Serial::write(const char *data)
{
    while ((*data) != '\0')   // Looping until end of char array '\0' is encountered
        write(static_cast<uint8_t>(*data++));
}
void Serial::writeln(const char *data = "")
{
    while ((*data) != '\0')   // Looping until end of char array '\0' is encountered
        write(static_cast<uint8_t>(*data++));
    write(static_cast<uint8_t>('\n'));
}
void Serial::write(char data)
{
        write(static_cast<const uint8_t>(data));
}
void Serial::write(const uint8_t *data)
{
    while ((*data) != '\0')   // Looping until end of char array '\0' is encountered
        write(static_cast<const uint8_t>(*data++));
}
uint8_t Serial::msgPending()
{
    return this->pending;
}
/*!
 * Functions lets know if there is any data received on the receiver buffer
 * @return
 */
bool Serial::rxDataAvailable()
{
    return rxHeadIndex != rxTailIndex;
}

/*!
 * Destructor
 */
Serial::~Serial()
{

}

// Rx & UDRE vectors for USART0
#if defined(HAS_USART0)
void USART0_RX_vect()
{
    Serial::Serial0->handleRXInterrupt();
}

void USART0_UDRE_vect()
{
    Serial::Serial0->handleUDREInterrupt();
}
#endif

// Rx & UDRE vectors for USART1
#if defined(HAS_USART1)
void USART1_RX_vect()
{
    Serial::Serial1->handleRXInterrupt();
}

void USART1_UDRE_vect()
{
    Serial::Serial1->handleUDREInterrupt();
}
#endif

// Rx & UDRE vectors for USART2
#if defined(HAS_USART2)
void USART2_RX_vect()
{
    Serial::Serial2->handleRXInterrupt();
}

void USART2_UDRE_vect()
{
    Serial::Serial2->handleUDREInterrupt();
}
#endif

// Rx & UDRE vectors for USART3
#if defined(HAS_USART3)
void USART3_RX_vect()
{
    Serial::Serial3->handleRXInterrupt();
}

void USART3_UDRE_vect()
{
    Serial::Serial3->handleUDREInterrupt();
}
#endif

/*!
 * Function handles the RX interrupts across all the static instances
 */
void Serial::handleRXInterrupt()
{
    /* Calculate buffer index */
    uint8_t temp_head = static_cast<uint8_t>((this->rxHeadIndex + 1) & (BUFFER_SIZE - 1));
    if (temp_head == this->rxTailIndex) {
        /* ERROR! Receive buffer overflow */
        /* Do nothing for now */
    }
    else {
        this->rxHeadIndex = temp_head;
        this->rxBuffer[this->rxHeadIndex] = *(this->UDRn);
        if (this->rxBuffer[this->rxHeadIndex]== TERMINATOR) this->pending++;
    }
}

/*!
 * Function handles the UDRE interrupts across all the static instances
 */
void Serial::handleUDREInterrupt()
{
    unsigned char temp_tail;
    /* Check if all data is transmitted */
    if (this->txHeadIndex != this->txTailIndex) {
        /* Calculate buffer index */
        temp_tail = static_cast<unsigned char>((this->txTailIndex + 1) & (BUFFER_SIZE - 1));
        /* Store new index */
        this->txTailIndex = temp_tail;
        /* Start transmission */
        *(this->UDRn) = this->txBuffer[temp_tail];
    }
    else {
        *(this->UCSRnB) &= ~(1 << this->UDRIEn); // disable UDRE interrupt
    }
}