//-----------------------------------------------------------------------------
///
/// \brief  USB Class
///
///         Nothing implemented yet
///
/// \date   2018025
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


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


//---Own------------------------------

// Must not debug when stdio is USB
#undef LEPTO_LOG_DEBUG
//#define LEPTO_LOG_DEBUG

#include <biwak/usb_class.h>
#include <biwak/usb_defines.h>
#include <biwak/usb_desc_cdc.h>
#include <biwak/usb_desc_hid.h>
#include <biwak/usb.hpp>
#include <biwak/biwak.h>
#include <biwak/systick.h>
#include <biwak/timer.hpp>

#include <lepto/log.h>
#include <string.h>

//---Global---------------------------


//---Defines-------------------------------------------------------------------

// #define USBD_CfgDesc         USBD_CDC_CfgFSDesc
TUsbClassDesc usbClassCdc = USBD_CDC_CfgFSDesc;
TUsbClassDesc usbClassHid = USBD_HID_CfgDesc;

#if 1
static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __attribute__((aligned(4))) =
{
      0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
      0x09, 0x06,                    // USAGE (Keyboard)
      0xa1, 0x01,                    // COLLECTION (Application)
      0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
      0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
      0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
      0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
      0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
      0x75, 0x01,                    //   REPORT_SIZE (1)
      0x95, 0x08,                    //   REPORT_COUNT (8)
      0x81, 0x02,                    //   INPUT (Data,Var,Abs)
      0x95, 0x01,                    //   REPORT_COUNT (1)
      0x75, 0x08,                    //   REPORT_SIZE (8)
      0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
      0x95, 0x05,                    //   REPORT_COUNT (5)
      0x75, 0x01,                    //   REPORT_SIZE (1)
      0x05, 0x08,                    //   USAGE_PAGE (LEDs)
      0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
      0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)
      0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)
      0x95, 0x01,                    //   REPORT_COUNT (1)
      0x75, 0x03,                    //   REPORT_SIZE (3)
      0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs)
      0x95, 0x06,                    //   REPORT_COUNT (6)
      0x75, 0x08,                    //   REPORT_SIZE (8)
      0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
      0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
      0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
      0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
      0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
      0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
      0xc0                           // END_COLLECTION

};
#endif

CUsbClass::CUsbClass(CUsb &_usb, CUsbDevice &_device, TUsbClassDesc desc)
   :CIOStream( CDC_EPIN_SIZE * 2, 0x10) // CDC_EPIN_SIZE(0x10) too small for phonybl
   #if defined USB_USE_BUFFER_RING
   // Size 0x10 was not enough for PhonyBL; 3 buffers are barely not enough
   // ,m_buffers( CDC_EPIN_SIZE + 8, 16)
   ,m_rxBuffers( CDC_EPIN_SIZE + 0x10, 0x20)
   ,m_txBuffers( 0x10, 4)
   #endif
   ,usb(_usb)
   ,device(_device)
   ,m_busy(false)
   ,m_desc(desc)
{

}

int CUsbClass::init(uint8_t cfgidx)
{
   //lWarning("Init USB class");

   usb.openEP( CDC_IN_EP,
                  USBD_EP_TYPE_INTR,
                  CDC_EPIN_SIZE);

#if 1
   usb.openEP( CDC_OUT_EP,
                  USBD_EP_TYPE_INTR,
                  CDC_EPOUT_SIZE);
#endif

   startReceive();
   return(0);
}

USBD_StatusTypeDef CUsbClass::DataIn(int epnum, char *data, int len)
{
   if(!len)
   {
      // USBD_LL_DataInStage();

      //PCD_StateTypeDef state=HAL_PCD_GetState( usb.getPcdHandle() );
      //PCD_EPTypeDef *ep=&usb.getPcdHandle()->IN_ep[epnum];

      //lWarning("Flush %d / %d / %d", epnum, CDC_IN_EP, state);
      //lWarning("Flush %d / %d / %d", epnum, CDC_IN_EP, state);

      //lWarning("Flush: %d %d %d %d", epnum, ep->xfer_len, ep->xfer_count, ep->is_stall);

      #if ! ( defined (STM32F303x8) || defined (STM32L431xx) || defined (STM32L0) || defined (STM32G0) )
         HAL_PCD_EP_Flush(usb.getPcdHandle(), CDC_IN_EP);
      #else
         // No USB support
      #endif
      //USBD_Get_USB_Status();
         
      #if defined USB_USE_BUFFER_RING
      m_txBuffers.dropBuffer();
      #endif
      m_busy=false;
      //device.ctlSendStatus();
   }
   else
   {
      exception( );
   }

   return(USBD_OK);
}

USBD_StatusTypeDef CUsbClass::DataOut(int epnum, char *data, int len)
{
   //const char *xData=(char *)m_rxBuffers.getTopData();
   lDebug("Len OUT: %d [0x%X] [0x%X] [0x%X] [0x%X]", len,
         data[0], data[1], data[2], data[3] );
   
   m_receiving=false;
   
   #if defined USB_USE_BUFFER_RING
      //lAssertSta( len <= m_buffers.getMaxBufferSize(), len );
      if ( ! m_rxBuffers.pushable() )
      {
         lCritical("USB: can not push buffer");
      }
      else
      {
         //lInfo( "DataOut %d", len );
         m_rxBuffers.pushBuffer(len);
      }
   #else
      for(int i1=0; i1<len; i1++)
      {
         receiveBuffer << m_inputData[i1];
      }
   #endif
      
   checkStart();

   return(USBD_OK);
}

void CUsbClass::startReceive()
{
   #if defined USB_USE_BUFFER_RING
      if(! m_rxBuffers.getTopData())
      {
         lWarning("USB: No free buffer");
      }
      usb.receive( CDC_OUT_EP, (uint8_t *)m_rxBuffers.getTopData(),
                   m_rxBuffers.getMaxBufferSize() );
   #else
      usb.receive( CDC_IN_EP, (uint8_t *)m_inputData, sizeof(m_inputData));
   #endif
   m_receiving=true;
   //usb.receive( CDC_IN_EP, (uint8_t *)device.ep_out[1]., sizeof(m_data));
}

void CUsbClass::EP0_RxReady()
{
   lWarning("EP0_RxReady");
   return;
}

void CUsbClass::EP0_TxSent()
{
   lWarning("EP0_TxSent");
   return;
}

uint8_t  *CUsbClass::getConfigurationDescription(uint16_t *length)
{
   *length = m_desc[2];
   lAssert(*length<0x100);
   return m_desc;
}

void CUsbClass::sof()
{
   //lWarning("SOF");
   return;
}

void CUsbClass::deInit(uint8_t cfgidx)
{
   /* Close HID EPs */
   //lWarning("deInit");
   usb.closeEP(HID_EPIN_ADDR);
}

USBD_StatusTypeDef CUsbClass::setup(CUsbRequest &req)
{
  uint16_t len = 0;
  uint8_t  *pbuf = NULL;

  switch (req.mRequest & USB_REQ_TYPE_MASK)
  {
  case USB_REQ_TYPE_CLASS :
    switch (req.request)
    {
    case HID_REQ_SET_PROTOCOL:
      protocol = (uint8_t)(req.value);
      break;

    case HID_REQ_GET_PROTOCOL:
      device.ctlSendData ((uint8_t *)&protocol, 1);
      break;

    case HID_REQ_SET_IDLE:
      idleState = (uint8_t)(req.value >> 8);
      break;

    case HID_REQ_GET_IDLE:
      device.ctlSendData ((uint8_t *)&idleState, 1);
      break;

    default:
      device.ctlError (req);
      return USBD_FAIL;
    }
    break;

  case USB_REQ_TYPE_STANDARD:
    switch (req.request)
    {
    case USB_REQ_GET_DESCRIPTOR:
      if( req.value >> 8 == HID_REPORT_DESC)
      {
        len = MIN(HID_MOUSE_REPORT_DESC_SIZE , req.length);
        pbuf = HID_MOUSE_ReportDesc;
      }
      else if( req.value >> 8 == HID_DESCRIPTOR_TYPE)
      {
        //pbuf = USBD_HID_Desc;
        len = MIN(USB_HID_DESC_SIZ , req.length);
      }
      // printf("DESCRIPTOR: %d\n", req.value >> 8);
      device.ctlSendData (pbuf, len);

      break;

    case USB_REQ_GET_INTERFACE :
      device.ctlSendData ( (uint8_t *)&altSetting, 1);
      break;

    case USB_REQ_SET_INTERFACE :
      altSetting = (uint8_t)(req.value);
      break;
    }
  }
  return USBD_OK;
};


USBD_StatusTypeDef CUsbClass::sendReport(uint8_t *data, int size)
{
   flushOutput();
  //if (device.dev_state == USBD_STATE_CONFIGURED )
  {
    //if(hhid->state == HID_IDLE)
    //{
    //  hhid->state = HID_BUSY;
    usb.transmit ( CDC_IN_EP, data, size);
  }
  return USBD_OK;
}

int CUsbClass::write(const void *buf, int size) /* virtual */
{
   if(!device.isActive())
   {
    return(-1);
   }
   flushOutput();
   
   // Setting busy=true here works, hoping DataIn will set it to false.
   // But when host is not opening the device, the app will hang in
   // flushOutput().
   m_busy=true;
   
   //static char sbuf[0x10];

   #if USB_USE_BUFFER_RING
      void *sbuf=m_txBuffers.getTopData();
      lAssert( size <= 0x10 );
      memcpy( sbuf, buf, size);
      m_txBuffers.pushBuffer(size);
   #else
      
      lAssert( size < 0x10 );
      memcpy(m_outputData, buf, size);
   #endif
   
   usb.transmit ( CDC_IN_EP, (uint8_t *)m_outputData, size);
      
   return(0);
}


void CUsbClass::flushOutput() /* virtual */
{
   lrtimer_t timeoutTimer=lrNow();
   static bool warned=false;
   
   while( m_busy && ( lrElapsedMSeconds(timeoutTimer) < 10 ) )
   {
      asm("nop;");
   };
   if(m_busy)
   {
      // This may block device when stdio is set to USB: feeding log while
      // flushing log
      if(!warned)
      {
         lCritical("USB: flush timeout");
         warned=true;
      }
   }
   else
   {
      warned=false;
   }
   return;
}


void CUsbClass::eventLoop()
{
   char *data;

   #if defined USB_USE_BUFFER_RING
   while( ( data = (char *)m_rxBuffers.getBottomData() ) )
   {
      //lHint("BufferRing: %d", m_buffers.getUsedSize());
      for(int i1=0; i1<m_rxBuffers.getBottomSize(); i1++)
      {
         receiveBuffer << data[i1];
      }
      m_rxBuffers.dropBuffer();
   }
   #else
      (void)data;
      checkStart();
   #endif
}


void CUsbClass::checkStart()
{
   if(!m_receiving)
   {
      if( receiveBuffer.getFreeCount() >= (int)sizeof(m_inputData) )
      {
         startReceive();
      }
   }
}


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