/* Copyright (c) 2010-2012 mbed.org, MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#define __DEBUG__ 0
#ifndef __MODULE__
#define __MODULE__ "WANDongleSerialPort.cpp"
#endif

#include "core/dbg.h"
#include <cstdint>
#include "rtos.h"

#include "WANDongleSerialPort.h"

WANDongleSerialPort::WANDongleSerialPort() : cb_tx_en(false), cb_rx_en(false), listener(NULL)
{
  reset();
}

void WANDongleSerialPort::init(USBHost* pHost)
{
  host = pHost;
}

void WANDongleSerialPort::reset()
{
  tx_mtx.lock();
  rx_mtx.lock();

  bulk_in = NULL;
  bulk_out = NULL;

  buf_out_len = 0;
  max_out_size = 0;
  lock_tx = false;
  cb_tx_pending = false;

  buf_in_len = 0;
  buf_in_read_pos = 0;
  lock_rx = false;
  cb_rx_pending = false;
  
  tx_mtx.unlock();
  rx_mtx.unlock();
}

int WANDongleSerialPort::readPacket()
{
  DBG("Read packet on %p", this);
  rx_mtx.lock();
  if(lock_rx)
  {
    ERR("Fail");
    rx_mtx.unlock();
    return -1;
  }
  
  if( bulk_in == NULL )
  {
    WARN("Port is disconnected");
    rx_mtx.unlock();
    return -1;
  }

  lock_rx = true; //Receiving
  rx_mtx.unlock();
//  DBG("readPacket");
  //lock_rx.lock();
  host->lock();
  USB_TYPE res = host->bulkRead(dev, (USBEndpoint *)bulk_in, buf_in, ((USBEndpoint *)bulk_in)->getSize(), false); //Queue transfer
  if(res != USB_TYPE_PROCESSING)
  {
    host->unlock();
    //lock_rx.unlock();
    ERR("host->bulkRead() returned %d", res);
    Thread::wait(100);
    return -1;
  }
  host->unlock();
  return 0;
}

int WANDongleSerialPort::writePacket()
{
  tx_mtx.lock();
  if(lock_tx)
  {
    ERR("Fail");
    tx_mtx.unlock();
    return -1;
  }
  
  if( bulk_out == NULL )
  {
    WARN("Port is disconnected");
    tx_mtx.unlock();
    return -1;
  }

  lock_tx = true; //Transmitting
  tx_mtx.unlock();
//  DBG("writePacket");

  //lock_tx.lock();
  host->lock();
  USB_TYPE res = host->bulkWrite(dev, (USBEndpoint *)bulk_out, buf_out, buf_out_len, false); //Queue transfer
  if(res != USB_TYPE_PROCESSING)
  {
    host->unlock();
    //lock_tx.unlock();
    ERR("host->bulkWrite() returned %d", res);
    Thread::wait(100);
    return -1;
  }
  host->unlock();
  return 0;
}

int WANDongleSerialPort::putc(int c)
{
  tx_mtx.lock();
  if(!lock_tx)
  {
    if(buf_out_len < max_out_size)
    {
      buf_out[buf_out_len] = (uint8_t)c;
      buf_out_len++;
    }
  }
  else
  {
    ERR("CAN'T WRITE!");
  }
  tx_mtx.unlock();
  return c;
}

int WANDongleSerialPort::getc()
{
  rx_mtx.lock();
  int c = 0;
  if(!lock_rx)
  {
    if(buf_in_read_pos < buf_in_len)
    {
      c = (int)buf_in[buf_in_read_pos];
      buf_in_read_pos++;
    }
  }
  else
  {
    ERR("CAN'T READ!");
  }
  rx_mtx.unlock();
  return c;
}

int WANDongleSerialPort::readable()
{
  rx_mtx.lock();
  if (lock_rx)
  {
    rx_mtx.unlock();
    return 0;
  }

 /* if( !lock_rx.trylock() )
  {
    return 0;
  }*/
  int res = buf_in_len - buf_in_read_pos;
  //lock_rx.unlock();
  rx_mtx.unlock();
  return res;
}

int WANDongleSerialPort::writeable()
{
  tx_mtx.lock();
  if (lock_tx)
  {
    tx_mtx.unlock();
    return 0;
  }

  /*if( !lock_tx.trylock() )
  {
    return 0;
  }*/
  int res = max_out_size - buf_out_len;
  tx_mtx.unlock();
 //lock_tx.unlock();
  return res;
}

void WANDongleSerialPort::attach(IUSBHostSerialListener* pListener)
{
  if(pListener == NULL)
  {
    setupIrq(false, RxIrq);
    setupIrq(false, TxIrq);
  }
  listener = pListener;
  if(pListener != NULL)
  {
    setupIrq(true, RxIrq);
    setupIrq(true, TxIrq);
  }
}

void WANDongleSerialPort::setupIrq(bool en, IrqType irq /*= RxIrq*/)
{
  switch(irq)
  {
  case RxIrq:
    rx_mtx.lock();
    cb_rx_en = en;
    if(en && cb_rx_pending)
    {
      cb_rx_pending = false;
      rx_mtx.unlock();
      listener->readable(); //Process the interrupt that was raised
    }
    else
    {
      rx_mtx.unlock();
    }
    break;
  case TxIrq:
    tx_mtx.lock();
    cb_tx_en = en;
    if(en && cb_tx_pending)
    {
      cb_tx_pending = false;
      tx_mtx.unlock();
      listener->writeable(); //Process the interrupt that was raised
    }
    else
    {
      tx_mtx.unlock();
    }
    break;
  }
}


void WANDongleSerialPort::connect( USBDeviceConnected* pDev, USBEndpoint* pInEp, USBEndpoint* pOutEp )
{
  dev = pDev;
  bulk_in = pInEp;
  bulk_out = pOutEp;
  max_out_size = bulk_out->getSize();
  if( max_out_size > WANDONGLE_MAX_OUTEP_SIZE )
  {
    max_out_size = WANDONGLE_MAX_OUTEP_SIZE;
  }
  bulk_in->attach(this, &WANDongleSerialPort::rxHandler);
  bulk_out->attach(this, &WANDongleSerialPort::txHandler);
  readPacket(); //Start receiving data
}

void WANDongleSerialPort::disconnect( )
{
    reset();
}

//Private methods


void WANDongleSerialPort::rxHandler()
{
  if (((USBEndpoint *) bulk_in)->getState() == USB_TYPE_IDLE) //Success
  {
    buf_in_read_pos = 0;
    buf_in_len = ((USBEndpoint *) bulk_in)->getLengthTransferred(); //Update length
    //lock_rx.unlock();
    rx_mtx.lock();
    lock_rx = false; //Transmission complete
    if(cb_rx_en)
    {
      rx_mtx.unlock();
      listener->readable(); //Call handler from the IRQ context
      //readPacket() should be called by the handler subsequently once the buffer has been emptied
    }
    else
    {
      cb_rx_pending = true; //Queue the callback
      rx_mtx.unlock();
    }

  }
  else //Error, try reading again
  {
    //lock_rx.unlock();
    DBG("Trying again");
    readPacket();
  }
}

void WANDongleSerialPort::txHandler()
{
  if (((USBEndpoint *) bulk_out)->getState() == USB_TYPE_IDLE) //Success
  {
    tx_mtx.lock();
    buf_out_len = 0; //Reset length
    lock_tx = false; //Transmission complete
    //lock_tx.unlock();
    if(cb_tx_en)
    {
      tx_mtx.unlock();
      listener->writeable(); //Call handler from the IRQ context
      //writePacket() should be called by the handler subsequently once the buffer has been filled
    }
    else
    {
      cb_tx_pending = true; //Queue the callback
      tx_mtx.unlock();
    }
  }
  else //Error, try reading again
  {
    //lock_tx.unlock();
    writePacket();
  }
}
