/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

The latest version of this library can always be found at
http://arduiniana.org.
*/

#include <Arduino.h>
#include <SoftwareSerial.h>
#include "wiring_LL.h" 

#define MAX_SOFT_SERIAL_SPEED 115200
#define MIN_SOFT_SERIAL_SPEED 300

// Statics
// static variable is common to all class instances
SoftwareSerial *SoftwareSerial::active_object = 0;
uint8_t SoftwareSerial::_receive_buffer[_SS_MAX_RX_BUFF]; 
volatile uint8_t SoftwareSerial::_receive_buffer_tail = 0;
volatile uint8_t SoftwareSerial::_receive_buffer_head = 0;

// Private methods

// function placed in ram. Should not call other function placed in flash
void SoftwareSerial::tunedDelay(uint32_t delayTicks)  // .ram_text
{ 
  if (delayTicks > 1)
  {
    uint64_t startTicks = SYSTICK_GET_TICKS();
    while ((SYSTICK_GET_TICKS() - startTicks) < delayTicks)
      ;
  }
}

// This function sets the current object as the "listening"
// one and returns true if it replaces another 
bool SoftwareSerial::listen()
{
  if (!_rx_delay_stopbit)
    return false;

  if (active_object != this)
  {
    if (active_object)
      active_object->stopListening();

    _buffer_overflow = false;
    _receive_buffer_head = _receive_buffer_tail = 0;
    active_object = this;

    setRxIntMsk(true);
    return true;
  }

  return false;
}

// Stop listening. Returns true if we were actually listening.
bool SoftwareSerial::stopListening()
{
  if (active_object == this)
  {
    setRxIntMsk(false);
    active_object = NULL;
    return true;
  }
  return false;
}

//
// The receive routine called by the interrupt handler
//
void SoftwareSerial::recv() // .ram_text
{  
  // If RX line is high, then we don't see any start bit
  // so interrupt is probably not for us
  bool rxState = (bool)GPIO_READ_PIN((GPIO_TypeDef *)_receivePortRegister, (HAL_PinsTypeDef)_receiveBitMask);
  if (_inverse_logic ? rxState : !rxState)
  {
    // Disable further interrupts during reception, this prevents triggering another interrupt
    // directly after we return, which can cause problems at higher baudrates.
    setRxIntMsk(false); // __always_inline__

    // Wait approximately 1/2 of a bit width to "center" the sample
    if (_rx_delay_centering > 0)
      tunedDelay(_rx_delay_centering); // .ram_text

    // Read each of the 8 bits
    uint8_t data = 0;
    for (uint8_t i=8; i > 0; --i)
    {
      tunedDelay(_rx_delay_intrabit);
      data >>= 1;
      if (GPIO_READ_PIN((GPIO_TypeDef *)_receivePortRegister, (HAL_PinsTypeDef)_receiveBitMask))
        data |= 0x80;
    }
    if (_inverse_logic)
      data = ~data;

    // if buffer full, set the overflow flag and return
    uint8_t next = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF;
    if (next != _receive_buffer_head)
    {
      // save new data in buffer: tail points to where byte goes
      _receive_buffer[_receive_buffer_tail] = data; // save new byte
      _receive_buffer_tail = next;
    } 
    else 
      _buffer_overflow = true;

    // skip the stop bit
    tunedDelay(_rx_delay_stopbit);

    // Re-enable interrupts when we're sure to be inside the stop bit
    setRxIntMsk(true);
  }
  GPIO_IRQ_CLEAR_ALL();
}

uint8_t SoftwareSerial::rx_pin_read()
{
  return (uint8_t)GPIO_READ_PIN((GPIO_TypeDef *)_receivePortRegister, (HAL_PinsTypeDef)_receiveBitMask);
}

// Interrupt handling

/* static */
void SoftwareSerial::handle_interrupt() // .ram_text
{
  if (active_object)
  {
    active_object->recv(); // .ram_text
  }
}

extern "C" void __attribute__((noinline, section(".ram_text"))) softSerial_interrupt_handler(void)
{
  SoftwareSerial::handle_interrupt();
}


//
// Constructor
//
SoftwareSerial::SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic /* = false */) : 
  _rx_delay_centering(0),
  _rx_delay_intrabit(0),
  _rx_delay_stopbit(0),
  _tx_delay(0),
  _buffer_overflow(false),
  _inverse_logic(inverse_logic)
{
  _transmitPin = transmitPin;
  _receivePin = receivePin;
}

//
// Destructor
//
SoftwareSerial::~SoftwareSerial()
{
  end();
}

void SoftwareSerial::setTX(uint8_t tx)
{
  // save pin and port info
  _transmitBitMask = (uint16_t)digitalPinToBitMask(tx);
  _transmitPortRegister = (uint32_t)digitalPinToPort(tx);
  // set state before tx pin initialization to prevent false start bit
  if (_inverse_logic)
    GPIO_CLEAR_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask);
  else
    GPIO_SET_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask);
  // init pin as output
  pinMode(tx, OUTPUT);
}

void SoftwareSerial::setRX(uint8_t rx)
{
  // save pin, port and gpio_line info
  _receiveBitMask = (uint16_t)digitalPinToBitMask(rx);
  _receivePortRegister = (uint32_t)digitalPinToPort(rx);
  _int_maskLine = digitalPinToGpioIntLine(_receivePin);

  // attach interrupt to rx pin if available
  attachInterrupt(digitalPinToInterrupt(_receivePin), softSerial_interrupt_handler, _inverse_logic ? RISING : FALLING);
  // turn on pull up for rx if logic is not inverse
  if (!_inverse_logic)
  {
    uint8_t pinNumber = PIN_MASK_TO_PIN_NUMBER(_receiveBitMask);
    if (((GPIO_TypeDef*)_receivePortRegister) == GPIO_0)
      PIN_SET_PAD_CONFIG(PORT_0_PUPD, pinNumber, HAL_GPIO_PULL_UP);
    else if (((GPIO_TypeDef*)_receivePortRegister) == GPIO_1)
      PIN_SET_PAD_CONFIG(PORT_1_PUPD, pinNumber, HAL_GPIO_PULL_UP);
    else if (((GPIO_TypeDef*)_receivePortRegister) == GPIO_2)
      PIN_SET_PAD_CONFIG(PORT_2_PUPD, pinNumber, HAL_GPIO_PULL_UP);
  }
  // turn off int line for while (it turning on in attachInterrupt())
  GPIO_IRQ_LINE_DISABLE(_int_maskLine);
}

uint32_t SoftwareSerial::subtract_cap(uint32_t num, uint16_t sub) 
{
  if (num > sub)
    return num - sub;
  else
    return 1;
}

//
// Public methods
//
void SoftwareSerial::begin(long speed)
{ 
  // delays are empirical values here
  _rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0;

  // if pins exceeds the total number of pins, return with zero delays - it blocks further library work
  if ((_transmitPin >= pinCommonQty()))
  {
    ErrorMsgHandler("SoftwareSerial.begin(): Tx pin number exceeds the total number of pins");
    return;
  }
  if ((_receivePin >= pinCommonQty()))
  {
    ErrorMsgHandler("SoftwareSerial.begin(): Rx pin number exceeds the total number of pins");
    return;
  }

  // limit speed
  if (speed > MAX_SOFT_SERIAL_SPEED) speed = MAX_SOFT_SERIAL_SPEED;
  if (speed < MIN_SOFT_SERIAL_SPEED) speed = MIN_SOFT_SERIAL_SPEED;

  // Precalculate the various delays in number of ticks
  uint32_t bit_delay = F_CPU / speed;
  
  // init tx
  setTX(_transmitPin);
  _tx_delay = subtract_cap(bit_delay, 35); // 1 bit delay while transmitting data

  // init rx only when we have a valid INT for this pin
  if (digitalPinToInterrupt(_receivePin) != NOT_AN_INTERRUPT) 
  {
    // set pin config
    setRX(_receivePin);
  
    // We want to have a total delay of 1.5 bit time from start bit. Inside the loop, we already  
    // wait for 1 bit time, so here we wait for 0.5 bit time
    _rx_delay_centering = subtract_cap(bit_delay / 2, 315); 
    
    // 1 bit time
    _rx_delay_intrabit = subtract_cap(bit_delay, 60);

    // This delay aims at 3/4 of a bit time, meaning the end of the delay will be at 1/4th of the stopbit. 
    // This allows some extra time for ISR cleanup, which makes 115200 baud at 16Mhz work more reliably   
    _rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, 142);
  }
  else
    ErrorMsgHandler("SoftwareSerial.begin(): Rx pin does not support interrupts, use different pin");

  tunedDelay(_tx_delay); // if we were low this establishes the end

  listen();
}

void SoftwareSerial::setRxIntMsk(bool enable)
{
  if (enable)
    GPIO_IRQ_LINE_ENABLE(_int_maskLine);
  else
    GPIO_IRQ_LINE_DISABLE(_int_maskLine);
}

void SoftwareSerial::end()
{
  stopListening();
}

// function placed in ram. Should not call other function placed in flash
size_t SoftwareSerial::write(uint8_t byte)
{
  if (_tx_delay == 0) 
    return 0;

  if (_inverse_logic)
    byte = ~byte;

  GLOBAL_IRQ_DISABLE(); // turn off interrupts for a clean txmit

  // Write the start bit
  if (_inverse_logic)
    GPIO_SET_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask);
  else
    GPIO_CLEAR_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask);
  tunedDelay(_tx_delay); // wait start bit

  // Write each of the 8 bits in LSB mode
  for (uint8_t i = 8; i > 0; --i)
  {
    if (byte & 1) // choose bit
      GPIO_SET_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask);
    else
      GPIO_CLEAR_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask);
    tunedDelay(_tx_delay);
    byte >>= 1;
  }

  // restore pin to natural state - stop bit
  if (_inverse_logic)
    GPIO_CLEAR_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask);
  else
    GPIO_SET_PIN((GPIO_TypeDef *)_transmitPortRegister, (HAL_PinsTypeDef)_transmitBitMask);

  // enable interrupts
  GLOBAL_IRQ_ENABLE();

  tunedDelay(_tx_delay); // wait stop bit
  
  return 1;
}

// Read data from buffer
int SoftwareSerial::read()
{
  if (!isListening())
    return -1;

  // Empty buffer?
  if (_receive_buffer_head == _receive_buffer_tail)
    return -1;

  // Read from "head"
  uint8_t data = _receive_buffer[_receive_buffer_head]; // grab next byte
  _receive_buffer_head = (_receive_buffer_head + 1) % _SS_MAX_RX_BUFF;
  return data;
}

int SoftwareSerial::available()
{
  if (!isListening())
    return 0;

  return ((unsigned int)(_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head)) % _SS_MAX_RX_BUFF;
}

void SoftwareSerial::flush()
{
  // There is no tx buffering, simply return
}

int SoftwareSerial::peek()
{
  if (!isListening())
    return -1;

  // Empty buffer?
  if (_receive_buffer_head == _receive_buffer_tail)
    return -1;

  // Read from "head"
  return _receive_buffer[_receive_buffer_head];
}