/**************************************************************************/
/*!
    @file     BLEUart.cpp
    @author   hathach (tinyusb.org)

    @section LICENSE

    Software License Agreement (BSD License)

    Copyright (c) 2018, Adafruit Industries (adafruit.com)
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:
    1. Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
    3. Neither the name of the copyright holders nor the
    names of its contributors may be used to endorse or promote products
    derived from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**************************************************************************/

#include "bluefruit.h"
#include "utility/TimeoutTimer.h"

/* UART Serivce: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
 * UART RXD    : 6E400002-B5A3-F393-E0A9-E50E24DCCA9E
 * UART TXD    : 6E400003-B5A3-F393-E0A9-E50E24DCCA9E
 */

const uint8_t BLEUART_UUID_SERVICE[] =
{
    0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0,
    0x93, 0xF3, 0xA3, 0xB5, 0x01, 0x00, 0x40, 0x6E
};

const uint8_t BLEUART_UUID_CHR_RXD[] =
{
    0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0,
    0x93, 0xF3, 0xA3, 0xB5, 0x02, 0x00, 0x40, 0x6E
};

const uint8_t BLEUART_UUID_CHR_TXD[] =
{
    0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0,
    0x93, 0xF3, 0xA3, 0xB5, 0x03, 0x00, 0x40, 0x6E
};

/**
 * Constructor
 */
BLEUart::BLEUart(uint16_t fifo_depth)
  : BLEService(BLEUART_UUID_SERVICE), _txd(BLEUART_UUID_CHR_TXD), _rxd(BLEUART_UUID_CHR_RXD)
{
  _rx_fifo       = NULL;
  _rx_cb         = NULL;
  _rx_fifo_depth = fifo_depth;

  _tx_fifo       = NULL;
  _tx_buffered   = 0;
  _buffered_th   = NULL;
}

/**
 * Destructor
 */
BLEUart::~BLEUart()
{
  if ( _tx_fifo ) delete _tx_fifo;
}

/**
 * Callback when received new data
 * @param chr
 * @param data
 * @param len
 * @param offset
 */
void bleuart_rxd_cb(BLECharacteristic& chr, uint8_t* data, uint16_t len, uint16_t offset)
{
  (void) offset;

  BLEUart& svc = (BLEUart&) chr.parentService();
  svc._rx_fifo->write(data, len);

#if CFG_DEBUG >= 2
  LOG_LV2("BLEUART", "RX: ");
  PRINT_BUFFER(data, len);
#endif

  // invoke user callback
  if ( svc._rx_cb ) svc._rx_cb();
}

/**
 * Timer callback periodically to send TX packet (if enabled).
 * @param timer
 */
void bleuart_txd_buffered_hdlr(TimerHandle_t timer)
{
  BLEUart* svc = (BLEUart*) pvTimerGetTimerID(timer);

  // skip if null (unlikely)
  if ( !svc->_tx_fifo ) return;

  // flush tx data
  (void) svc->flush_tx_buffered();
}

void bleuart_txd_cccd_cb(BLECharacteristic& chr, uint16_t value)
{
  BLEUart& svc = (BLEUart&) chr.parentService();

  if ( svc._buffered_th == NULL) return;

  // Enable TXD timer if configured
  if (value & BLE_GATT_HVX_NOTIFICATION)
  {
    xTimerStart(svc._buffered_th, 0); // if started --> timer got reset
  }else
  {
    xTimerStop(svc._buffered_th, 0);
  }
}

void BLEUart::setRxCallback( rx_callback_t fp)
{
  _rx_cb = fp;
}

/**
 * Enable packet buffered for TXD
 * Note: packet is sent right away if it reach MTU bytes
 * @param enable true or false
 */
void BLEUart::bufferTXD(uint8_t enable)
{
  _tx_buffered = enable;

  if ( enable )
  {
    // enable cccd callback to start timer when enabled
    _txd.setCccdWriteCallback(bleuart_txd_cccd_cb);

    // Create FIFO for TX TODO Larger MTU Size
    if ( _tx_fifo == NULL )
    {
      _tx_fifo = new Adafruit_FIFO(1);
      _tx_fifo->begin( Bluefruit.Gap.getMaxMtuByConnCfg(CONN_CFG_PERIPHERAL) );
    }
  }else
  {
    _txd.setCccdWriteCallback(NULL);

    if ( _tx_fifo ) delete _tx_fifo;
  }
}

err_t BLEUart::begin(void)
{
  _rx_fifo = new Adafruit_FIFO(1);
  _rx_fifo->begin(_rx_fifo_depth);

  // Invoke base class begin()
  VERIFY_STATUS( BLEService::begin() );

  uint16_t max_mtu = Bluefruit.Gap.getMaxMtuByConnCfg(CONN_CFG_PERIPHERAL);

  // Add TXD Characteristic
  _txd.setProperties(CHR_PROPS_NOTIFY);
  // TODO enable encryption when bonding is enabled
  _txd.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS);
  _txd.setMaxLen( max_mtu );
  _txd.setUserDescriptor("TXD");
  VERIFY_STATUS( _txd.begin() );

  // Add RXD Characteristic
  _rxd.setProperties(CHR_PROPS_WRITE | CHR_PROPS_WRITE_WO_RESP);
  _rxd.setWriteCallback(bleuart_rxd_cb);

  // TODO enable encryption when bonding is enabled
  _rxd.setPermission(SECMODE_NO_ACCESS, SECMODE_OPEN);
  _rxd.setMaxLen( max_mtu );
  _rxd.setUserDescriptor("RXD");
  VERIFY_STATUS(_rxd.begin());

  return ERROR_NONE;
}

bool BLEUart::notifyEnabled(void)
{
  return _txd.notifyEnabled();
}

void BLEUart::_disconnect_cb(void)
{
  if (_buffered_th)
  {
    xTimerDelete(_buffered_th, 0);
    _buffered_th = NULL;

    if (_tx_fifo) _tx_fifo->clear();
  }
}

void BLEUart::_connect_cb (void)
{
  if ( _tx_buffered )
  {
    // create TXD timer TODO take connInterval into account
    // ((5*ms2tick(Bluefruit.connInterval())) / 4) / 2
    _buffered_th = xTimerCreate(NULL, ms2tick(10), true, this, bleuart_txd_buffered_hdlr);

    // Start the timer
    xTimerStart(_buffered_th, 0);
  }
}

/*------------------------------------------------------------------*/
/* STREAM API
 *------------------------------------------------------------------*/
int BLEUart::read (void)
{
  uint8_t ch;
  return read(&ch, 1) ? (int) ch : EOF;
}

int BLEUart::read (uint8_t * buf, size_t size)
{
  return _rx_fifo->read(buf, size);
}

size_t BLEUart::write (uint8_t b)
{
  return write(&b, 1);
}

size_t BLEUart::write (const uint8_t *content, size_t len)
{
  // notify right away if txd buffered is not enabled
  if ( !(_tx_buffered && _tx_fifo) )
  {
    return _txd.notify(content, len) ? len : 0;
  }else
  {
    // skip if not enabled
    if ( !notifyEnabled() ) return 0;

    uint16_t written = _tx_fifo->write(content, len);

    // TODO multiple prph connections
    // Not up to GATT MTU, notify will be sent later by TXD timer handler
    if ( _tx_fifo->count() < (Bluefruit.Gap.getMTU( Bluefruit.connHandle() ) - 3) )
    {
      return len;
    }
    else
    {
      // TX fifo has enough data, send notify right away
      VERIFY( flush_tx_buffered(), 0);

      // still more data left, send them all
      if ( written < len )
      {
        VERIFY( _txd.notify(content+written, len-written), written);
      }

      return len;
    }
  }
}

int BLEUart::available (void)
{
  return _rx_fifo->count();
}

int BLEUart::peek (void)
{
  uint8_t ch;
  return _rx_fifo->peek(&ch) ? (int) ch : EOF;
}

void BLEUart::flush (void)
{
  _rx_fifo->clear();
}

bool BLEUart::flush_tx_buffered(void)
{
  uint16_t max_hvx = Bluefruit.Gap.getMTU( Bluefruit.connHandle() ) - 3;
  uint8_t* ff_data = (uint8_t*) rtos_malloc( max_hvx );

  if (!ff_data) return false;

  uint16_t len = _tx_fifo->read(ff_data, max_hvx);
  bool result = true;

  if ( len )
  {
    result = _txd.notify(ff_data, len);
  }

  rtos_free(ff_data);

  return result;
}