//----------------------------------------------------------------------------
///
/// \file   uart.cpp
/// \brief  UART interface class for campo
///
///         Provides UART connection.
///         Sending and receiving is handled via interrupts. The data is managed
///         via ringbuffers.
///         A ringbuffer has 2 semaphores(should have). One for the supplier and
///         one for the consument. For not getting any deadlocks the roles are
///         assigned according to this table:
///
///        |    | Aplplication   |  Interrupt|
///        |----|----------------|-----------|
///        |TX  | Supply         |  Consume  |
///        |RX  |                |           |
///
///         Bitbreite bei 115200 Baud: 8.68us
///
/// \date   20121111
/// \author Maximilian Seesslen
///
//----------------------------------------------------------------------------


//---Includes-----------------------------------------------------------------


#include <biwak/uart.hpp>
#include <biwak/stm32/uart.hpp>
#include <biwak/handler.h>              // need C-Declaration of IRQ handler
#include <biwak/lltools.h>              // setUsart
#include <biwak/nvic.h>                 // buffers
#include <biwak/rcc.h>                  // rccUsartClockEnable()
#include <biwak/exception.h>
#include <biwak/interruptObjects.hpp>

#include <lepto/tools.h>
#include <lepto/log.h>
#include <lepto/ring.h>                 // buffers

#include <HALWrapper/stm32_usart_ex.h>
#include <HALWrapper/stm32_usart.h>     // __HAL_USART_GET_IT

#include <string.h>  // memset

#include <biwak/systick.h>


//---Forward declarations-----------------------------------------------------


#define UART_BLOCK_SIZE    0x10000


//---Implementation-----------------------------------------------------------


#undef USE_DYNAMIC_DATA
#undef USE_CURRENT_DATA
// #define USE_DYNAMIC_DATA
// #define USE_CURRENT_DATA


void initUart(UART_HandleTypeDef &handle, const SUartConfig &configData)
{
   // The buffers, e.g. pTxBuffPtr, are set within hal to the data e.g. from
   // HAL_UART_Transmit_IT

   //handle.hdmarx=(DMA_HandleTypeDef *)-1;
   //handle.hdmatx=(DMA_HandleTypeDef *)-1;

   if ( HAL_UART_Init( &handle ) != HAL_OK)
   {
      exception();
   }
   #if defined (STM32G4) || defined (STM32G0)

      if (HAL_UARTEx_SetTxFifoThreshold(&handle, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
      {
        exception();
      }
      if (HAL_UARTEx_SetRxFifoThreshold(&handle, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
      {
        exception();
      }
      // Has to be done or UART will stay busy. Also done in examples.
      if (HAL_UARTEx_DisableFifoMode(&handle) != HAL_OK)
      {
        exception();
      }
   #endif
}


static constexpr const UART_InitTypeDef uartDefaultInit {
   .BaudRate { 115200 },
   .WordLength { UART_WORDLENGTH_8B },
   .StopBits = UART_STOPBITS_1,
   .Parity = UART_PARITY_NONE,
   .Mode = UART_MODE_TX_RX,
   .HwFlowCtl = UART_HWCONTROL_NONE,
   .OverSampling = UART_OVERSAMPLING_16,
#if defined(UART_FIFOMODE_ENABLE) && ! defined(STM32G4) && ! defined(STM32G0)
      .FifoMode = UART_FIFOMODE_ENABLE,
#endif
#if ! defined STM32F1 && ! defined STM32F4 && ! defined STM32L1
   .OneBitSampling =
#ifdef STM32F3
      //UART_ONEBIT_SAMPLING_DISABLED;
      UART_ONE_BIT_SAMPLE_ENABLE,
#else
      //UART_ONE_BIT_SAMPLE_ENABLE;
      UART_ONE_BIT_SAMPLE_DISABLE,
#endif
#endif
  
#if defined(STM32G4) || defined(STM32G0)
   .ClockPrescaler=UART_PRESCALER_DIV1,
#endif
  
#if defined(USART_POLARITY_LOW__)
   .CLKPolarity = USART_POLARITY_LOW;
   .CLKPhase = USART_PHASE_1EDGE;
   .CLKLastBit = USART_LASTBIT_DISABLE;
#endif
};


/// \brief  Le constructeur
///
CUart::CUart( const SUartConfig &_configData )
   //:CIOStream(2, 2)
   //:m_configData(_configData)
{
   #if defined ( BIWAK_AUTO_RCC_ENABLE )
   rccUsartClockEnable(configData.instance);
   #endif
   
   #if defined ( BIWAK_AUTO_GPIO_INIT )
   CGpio::init( _configData.eGpioPeripheralRx, _configData.eGpioPinRx, gpioModeUartRx, _configData.altFunc );
   CGpio::init( _configData.eGpioPeripheralTx, _configData.eGpioPinTx, gpioModeUartTx, _configData.altFunc );
   #endif // ? BIWAK_AUTO_GPIO_INIT
   
   m_pHandle = new UART_HandleTypeDef{
      .Instance { _configData.instance },
      .Init { uartDefaultInit },
      #if defined UART_ADVFEATURE_NO_INIT
         .AdvancedInit
         {
           .AdvFeatureInit = UART_ADVFEATURE_NO_INIT,
         }
      #endif
   };

   _configData.interrupt=this;

   //receiveBuffer.setFatal(false);
   initUart( *m_pHandle, _configData /*, txBuf, rxBuf */);

   //#if ! defined(____NO_INTERRUPTS)
   //__HAL_USART_ENABLE_IT( m_pHandle, USART_IT_TC );
   //__HAL_USART_ENABLE_IT( m_pHandle, USART_IT_TXE);
   //__HAL_USART_ENABLE_IT( m_pHandle, USART_IT_RXNE);

   setNVInterrupt( _configData.irq , true, NVI_DEFAULT_PRIORITY );
   startReceive();
   //#endif
}

CUart::~CUart()
{
   //setNVInterrupt( _configData.irq , false, NVI_DEFAULT_PRIORITY );
}

void CUart::deinit()
{
   // This is needed when entering application from custom bootloader:
   // Either wait for uart to have finished any transactions or disable the
   // IRQ. Not storing the configData in the object saves some bytes of flash.
   // setNVInterrupt( m_configData.irq , false, 0 );
   waitBusy();
   HAL_UART_DeInit( m_pHandle );
}
#if 0
int CUart::putch(int ch)
{
   HAL_StatusTypeDef status=HAL_OK;
   // Must not use stack; HAL Uart-IT uses pointer to buffer
   static int _ch;
   waitBusy();
   _ch=ch;
   status=HAL_UART_Transmit_IT(m_pHandle, (uint8_t *)&(_ch), sizeof(uint8_t));
   //status=HAL_UART_Transmit(m_pHandle, (uint8_t *)&(_ch), sizeof(uint8_t), 100);

   return(status);
}
#endif

bool CUart::waitBusy()
{
   int timeout=0x100;

   while(
             (m_pHandle->gState!=HAL_UART_STATE_READY)
          && (m_pHandle->gState!=HAL_UART_STATE_BUSY_RX)
          && (timeout--)
   )
   {
      usleep(5);
   }

   return( timeout != 0 );
}

#if 0

int CUart::puts(const char *str)
{
   return( write( str, strlen(str) ) );
}

#endif

int CUart::write(const void *buf, int size)
{
   HAL_StatusTypeDef status;
   int timeout=0xE0000000;
   //int retry=32;

#if 0
   =HAL_OK;
   //m_busy=true;
   
   int partSize;
   const uint8_t *bBuf=(const uint8_t *)buf;

   while(size)
   {
      partSize=size;
      waitBusy();
      status=HAL_UART_Transmit(m_pHandle, (uint8_t *)bBuf, partSize, timeout);
      if(status!=HAL_OK)
      {
        exception( "" );
      }
      bBuf+=partSize;
      size-=partSize;
   }
#endif
   //do{
      status=HAL_UART_Transmit(m_pHandle, (uint8_t *)buf, size, timeout);
      /*
      retry--;
      if (status==HAL_BUSY)
      {
           usleep(5);
      }
      */
   //}while( (status==HAL_BUSY) && retry );
   bDebugAssert( status == HAL_OK, sta );

   return(status);
}


#if 0
int CUart::write(const void *buf, int size)
{
   HAL_StatusTypeDef status=HAL_OK;
   bool ready=waitBusy();
   if(ready)
   {
      m_busy=true;
      //size=MIN(size, 0x100);
      //memcpy(txBuffer, buf, size);
      sendBuffer.putString((const char *)buf);

      //HAL_UART_Transmit_IT( m_pHandle, (uint8_t *)txBuffer, size );
      HAL_UART_Transmit_IT( m_pHandle, (uint8_t *)sendBuffer.getBuffer(), size );
   }
   else
   {
      status=HAL_ERROR;
   }

   return(status);
}
#endif

void CUart::txInterrupt()
{
   #if defined( BLOCKS )
   totalTxSize-=currentTxSize;
   txAddress+=currentTxSize;
   if(totalTxSize)
   {
      currentTxSize=MIN(UART_BLOCK_SIZE, totalTxSize);
      HAL_UART_Transmit_IT( m_pHandle, txAddress, currentTxSize );
   }
   #endif

   return;
}

void CUart::interrupt() /* virtual */
{
   HAL_UART_IRQHandler( m_pHandle );
}

void uart_setstdio();

void CUart::setStdio()
{
   uart_setstdio();
}

/*
CUart *CUart::verifyHandle(void *handle)
{
   if( m_pHandle == handle )
   {
      return(this);
   }
   return(nullptr);
}
*/

INIT_IOBJECTS(CUart *, uart);
CUart *interruptUart1 IOBJECT(uart, 1);
CUart *interruptUart2 IOBJECT(uart, 2);
CUart *interruptUart3 IOBJECT(uart, 3);
CUart *interruptUart4 IOBJECT(uart, 4);

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"

// STM32L1
IRQ_Funcion_imp( USART1_IRQHandler )
{
   callInterruptFix(interruptUart1);
}

IRQ_Funcion_imp ( USART2_IRQHandler )
{
   callInterruptFix(interruptUart2);
}

// STM32F4
IRQ_Funcion_imp( USART3_IRQHandler )
{
   callInterruptFix(interruptUart3);
}

// STM32F0illed
IRQ_Funcion_imp( USART3_4_IRQHandler )
{
   callInterrupt(interruptUart3);
   callInterrupt(interruptUart4);
}

#pragma GCC diagnostic pop

void CUart::flushOutput()
{
   int cnt=0;

   while(
          (
            sendBuffer.isDataAvailable()
            ||  ( (m_pHandle->gState != HAL_UART_STATE_READY) && (m_pHandle->gState != HAL_UART_STATE_BUSY_RX) )
          ) && ( cnt < 100000)
        )
   {
      cnt++;
   };

   return;
};


void CUart::startReceive()
{
   bAssert( HAL_UART_Receive_IT(m_pHandle, (unsigned char *)&rxByte, 1) == HAL_OK, 1 );
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *pHandler)
{
#if 1
    iobject_uart->txInterrupt();
#else
    for ( CUart **iter=&iobject_uart; iter < &iobjects_end_uart; ++iter)
   {
      CUart *uart=*iter;
      if( uart && ( uart->getHandle() == pHandler ) )
      {
         uart->txInterrupt();
      }
   }
#endif
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *pHandler)
{
#if 1
    iobject_uart->receiveBuffer.putEntry( iobject_uart->getRxByte() );
    iobject_uart->startReceive();
#else
   for ( CUart **iter=&iobject_uart; iter < &iobjects_end_uart; ++iter)
   {
      CUart *uart=*iter;
      if( uart && ( uart->getHandle() == pHandler ) )
      {
         uart->receiveBuffer.putEntry( uart->getRxByte() );
         uart->startReceive();
         return;
      }
   }
#endif
}

void HAL_UARTEx_WakeupCallback(UART_HandleTypeDef *pHandler)
{
   exception( );
}


extern "C"
{
   int uart_putc(char ch, __file *f);
   int uart_getc(__file *f);
   int uart_flush(__file *f);
   
   static FILE uart_stdio =
       FDEV_SETUP_STREAM(uart_putc, uart_getc, uart_flush, _FDEV_SETUP_RW);
}

int uart_putc(char ch, __file *f)
{
   iobject_uart->write(&ch, 1);
   
   if(ch=='\n')
      iobject_uart->write("\r", 1);
   return(0);
}


int uart_getc(__file *f)
{
   int ch=iobject_uart->getch();
   return( ch );
}


int uart_flush(__file *f)
{
   iobject_uart->flushOutput();
   return(0);
}

void setStdio( FILE* stdio );

void uart_setstdio()
{
   setStdio( &uart_stdio );
}


//---fin----------------------------------------------------------------------
