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


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


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

#include <biwak/usb.hpp>
#include <biwak/usb_device.h>
#include <biwak/usb_defines.h>
#include <biwak/usb_class.h>
#include <biwak/biwak.h>

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



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


CUsbDevice::CUsbDevice(CUsb &_usb)
   :usb(_usb)
{
}

USBD_StatusTypeDef CUsbDevice::dataOutStage( uint8_t epnum, uint8_t *pdata, int len)
{
  USBD_EndpointTypeDef    *pep;

  if(epnum == 0)
  {
    pep = &ep_out[0];

    if ( ep0_state == USBD_EP0_DATA_OUT)
    {
      if(pep->rem_length > pep->maxpacket)
      {
        pep->rem_length -=  pep->maxpacket;

        ctlContinueRx ( pdata, MIN(pep->rem_length ,pep->maxpacket));
      }
      else
      {
        if(/*(pdev->pClass->EP0_RxReady != NULL)&&*/
           (dev_state == USBD_STATE_CONFIGURED))
        {
          pClass->EP0_RxReady();
        }
        ctlSendStatus();
         }
      }
   }
   else if(/*(pdev->pClass->DataOut != NULL)&&*/
          (dev_state == USBD_STATE_CONFIGURED))
   {
      //lInfo("dataOutStage len=%d", len);
      pClass->DataOut(epnum, (char *)pdata, len);
   }
   return USBD_OK;
}


USBD_StatusTypeDef CUsbDevice::dataInStage(uint8_t epnum, uint8_t *pdata, int len)
{
  USBD_EndpointTypeDef    *pep;

  if(epnum == 0)
  {
    pep = &ep_in[0];

    if ( ep0_state == USBD_EP0_DATA_IN)
    {
      if(pep->rem_length > pep->maxpacket)
      {
        pep->rem_length -=  pep->maxpacket;

        ctlContinueSendData ( pdata, pep->rem_length);

        /* Prepare endpoint for premature end of transfer */
        usb.receive(0, NULL, 0);
      }
      else
      { /* last packet is MPS multiple, so send ZLP packet */
        if((pep->total_length % pep->maxpacket == 0) &&
           (pep->total_length >= pep->maxpacket) &&
             (pep->total_length < ep0_data_len ))
        {

          ctlContinueSendData(NULL, 0);
          ep0_data_len = 0;

        /* Prepare endpoint for premature end of transfer */
         usb.receive(0, NULL, 0);
        }
        else
        {
          if( /*(pClass->EP0_TxSent != NULL)&& */
             (dev_state == USBD_STATE_CONFIGURED))
          {
            pClass->EP0_TxSent();
          }
          ctlReceiveStatus();
        }
      }
    }
    if (dev_test_mode == 1)
    {
      runTestMode();
      dev_test_mode = 0;
    }
  }
  else if( /*(pClass->DataIn != NULL)&&*/
          (dev_state == USBD_STATE_CONFIGURED))
  {
    pClass->DataIn( epnum, (char *)pdata, len);
  }
  return USBD_OK;
}


USBD_StatusTypeDef CUsbDevice::setupStage( uint8_t *pdata )
{
    CUsbRequest req( pdata );
    ep0_state = USBD_EP0_SETUP;
    ep0_data_len = req.length;

    switch (req.mRequest & 0x1F)
    {
    case USB_REQ_RECIPIENT_DEVICE:
       switch (req.request)
       {
       case USB_REQ_GET_DESCRIPTOR:
         getDescriptor(req) ;
         break;

       case USB_REQ_SET_ADDRESS:
         setAddress(req);
         break;

       case USB_REQ_SET_CONFIGURATION:
         setConfig (req);
         break;

       case USB_REQ_GET_CONFIGURATION:
         getConfig (req);
         break;

       case USB_REQ_GET_STATUS:
         getStatus (req);
         break;


       case USB_REQ_SET_FEATURE:
         setFeature (req);
         break;

       case USB_REQ_CLEAR_FEATURE:
         clrFeature (req);
         break;

       default:
         ctlError(req);
         break;
       }

      break;

    case USB_REQ_RECIPIENT_INTERFACE:
       switch (dev_state)
       {
       case USBD_STATE_CONFIGURED:

         if (LOBYTE(req.index) <= USBD_MAX_NUM_INTERFACES)
         {
            pClass->setup (req);
            if((req.length == 0) /* && (ret == USBD_OK) */ )
            {
               ctlSendStatus();
            }
         }
         else
         {
            ctlError(req);
         }
         break;

       default:
          ctlError( req);
         break;
       }
      break;

    case USB_REQ_RECIPIENT_ENDPOINT:
      stdEPReq( req );
      break;

    default:
       usb.stallEP(req.mRequest & 0x80);
      break;
    }

   return ( USBD_OK );
}

USBD_StatusTypeDef CUsbDevice::SOF()
{
   if(dev_state == USBD_STATE_CONFIGURED)
   {
      pClass->sof();
   }
   return ( USBD_OK );
}

USBD_StatusTypeDef CUsbDevice::reset()
{
   setSpeed( USBD_SPEED_FULL);

   /* Open EP0 OUT */
   usb.openEP(   0x00,
              USBD_EP_TYPE_CTRL,
              USB_MAX_EP0_SIZE);

   ep_out[0].maxpacket = USB_MAX_EP0_SIZE;

   /* Open EP0 IN */
   usb.openEP(   0x80,
              USBD_EP_TYPE_CTRL,
              USB_MAX_EP0_SIZE);

   ep_in[0].maxpacket = USB_MAX_EP0_SIZE;
   /* Upon Reset call user call back */
   dev_state = USBD_STATE_DEFAULT;

   /* TODO */
   //if (pdev->pClassData)
   pClass->deInit(dev_config);

   return ( USBD_OK );
};

USBD_StatusTypeDef CUsbDevice::suspend()
{
   m_active=false;
   lDebug("Suspend");
   return ( USBD_OK );
};

USBD_StatusTypeDef CUsbDevice::resume()
{
   m_active=true;
   lDebug("Resume");
   return ( USBD_OK );
};

USBD_StatusTypeDef CUsbDevice::ISOOUTIncomplete()
{
   // "Incomplete"
   exception( );
   return ( USBD_OK );
};

USBD_StatusTypeDef CUsbDevice::ISOINIncomplete()
{
   // "Incomplete"
   exception( );
   return ( USBD_OK );
};

USBD_StatusTypeDef CUsbDevice::connect()
{
   // "Connect"
   exception( );
   return ( USBD_OK );
};

USBD_StatusTypeDef CUsbDevice::disconnect()
{
   // "Disconnect"
   exception( );
   return ( USBD_OK );
};

void CUsbDevice::getDescriptor( CUsbRequest &req)
{
  uint16_t len;
  uint8_t *pbuf=nullptr;


  switch (req.value >> 8)
  {
#if (USBD_LPM_ENABLED == 1)
  case USB_DESC_TYPE_BOS:
    pbuf = pdev->pDesc->GetBOSDescriptor(pdev->dev_speed, &len);
    break;
#endif
  case USB_DESC_TYPE_DEVICE:
     //exception("foo", -1);
    //pbuf = pdev->pDesc->GetDeviceDescriptor(pdev->dev_speed, &len);
     pbuf = descriptor.getDeviceDescriptor(dev_speed, &len);
    break;

  case USB_DESC_TYPE_CONFIGURATION:
    if(dev_speed == USBD_SPEED_HIGH )
    {
       //exception("foo");
      //pbuf   = (uint8_t *)pdev->pClass->GetHSConfigDescriptor(&len);
       pbuf   = pClass->getConfigurationDescription(&len);
      pbuf[1] = USB_DESC_TYPE_CONFIGURATION;
    }
    else
    {
       //(uint8_t *)pdev->pClass->GetFSConfigDescriptor(&len);
      pbuf   = pClass->getConfigurationDescription(&len);
      pbuf[1] = USB_DESC_TYPE_CONFIGURATION;
    }
    break;

  case USB_DESC_TYPE_STRING:
    switch ((uint8_t)(req.value))
    {
    case USBD_IDX_LANGID_STR:
      pbuf = descriptor.getLangIDStringDescriptor(dev_speed, &len);
      break;

    case USBD_IDX_MFC_STR:
      pbuf = descriptor.getManufacturerStrDescriptor(dev_speed, &len);
      break;

    case USBD_IDX_PRODUCT_STR:
      pbuf = descriptor.getProductStrDescriptor(dev_speed, &len);
      break;

    case USBD_IDX_SERIAL_STR:
      pbuf = descriptor.getSerialStrDescriptor(dev_speed, &len);
      break;

    case USBD_IDX_CONFIG_STR:
       exception( );
      //pbuf = pdev->pDesc->GetConfigurationStrDescriptor(pdev->dev_speed, &len);
      break;

    case USBD_IDX_INTERFACE_STR:
       exception( );
      //pbuf = pdev->pDesc->GetInterfaceStrDescriptor(pdev->dev_speed, &len);
      break;

    default:
#if (USBD_SUPPORT_USER_STRING == 1)
      pbuf = pdev->pClass->GetUsrStrDescriptor(pdev, (req->wValue) , &len);
      break;
#else
       ctlError(req);
      return;
#endif
    }
    break;
  case USB_DESC_TYPE_DEVICE_QUALIFIER:

    if(dev_speed == USBD_SPEED_HIGH  )
    {
       exception( );
      //pbuf   = (uint8_t *)pdev->pClass->GetDeviceQualifierDescriptor(&len);
       //pbuf=descriptor.getDeviceQualifierDescriptor(&len);
       ctlError(req);
      break;
    }
    else
    {
      ctlError(req);
      return;
    }

  case USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION:
    if(dev_speed == USBD_SPEED_HIGH  )
    {
      // exception("foo");
      //pbuf   = (uint8_t *)pdev->pClass->GetOtherSpeedConfigDescriptor(&len);
      //pbuf[1] = USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION;
       ctlError(req);
      break;
    }
    else
    {
      ctlError(req);
      return;
    }

  default:
     ctlError(req);
    return;
  }

  if((len != 0)&& (req.length != 0))
  {
      len = MIN(len , req.length);
      ctlSendData (pbuf, len);
  }

}


void CUsbDevice::setAddress( CUsbRequest &req)
{
  uint8_t  dev_addr;

  if ((req.index == 0) && (req.length == 0))
  {
    dev_addr = (uint8_t)(req.value) & 0x7F;

    if (dev_state == USBD_STATE_CONFIGURED)
    {
      ctlError(req);
    }
    else
    {
      dev_address = dev_addr;
      usb.setUsbAddress(dev_addr);
      ctlSendStatus();

      if (dev_addr != 0)
      {
        dev_state  = USBD_STATE_ADDRESSED;
      }
      else
      {
        dev_state  = USBD_STATE_DEFAULT;
      }
    }
  }
  else
  {
     ctlError(req);
  }
}


int CUsbDevice::stdEPReq (CUsbRequest &req)
{
  uint8_t   ep_addr;
  USBD_StatusTypeDef ret = USBD_OK;
  USBD_EndpointTypeDef   *pep;
  ep_addr  = LOBYTE(req.index);

  /* Check if it is a class request */
  if ((req.mRequest & 0x60) == 0x20)
  {
     exception( );
    //pdev->pClass->Setup (pdev, req);

    return USBD_OK;
  }

  switch (req.request)
  {

  case USB_REQ_SET_FEATURE :

    switch (dev_state)
    {
    case USBD_STATE_ADDRESSED:
      if ((ep_addr != 0x00) && (ep_addr != 0x80))
      {
        usb.stallEP(ep_addr);
      }
      break;

    case USBD_STATE_CONFIGURED:
      if (req.value == USB_FEATURE_EP_HALT)
      {
        if ((ep_addr != 0x00) && (ep_addr != 0x80))
        {
          usb.stallEP(ep_addr);
        }
      }
      exception( );
      //pdev->pClass->Setup (pdev, req);
      ctlSendStatus();

      break;

    default:
      ctlError(req);
      break;
    }
    break;

  case USB_REQ_CLEAR_FEATURE :

    switch (dev_state)
    {
    case USBD_STATE_ADDRESSED:
      if ((ep_addr != 0x00) && (ep_addr != 0x80))
      {
        usb.stallEP(ep_addr);
      }
      break;

    case USBD_STATE_CONFIGURED:
      if (req.value == USB_FEATURE_EP_HALT)
      {
        if ((ep_addr & 0x7F) != 0x00)
        {
          //exception("foo");
           usb.clearStallEP( ep_addr );
          //USBD_LL_ClearStallEP(pdev , ep_addr);
          //pdev->pClass->Setup (pdev, req);
        }
        ctlSendStatus();
      }
      break;

    default:
      ctlError(req);
      break;
    }
    break;

  case USB_REQ_GET_STATUS:
    switch (dev_state)
    {
    case USBD_STATE_ADDRESSED:
      if ((ep_addr & 0x7F) != 0x00)
      {
        usb.stallEP(ep_addr);
      }
      break;

    case USBD_STATE_CONFIGURED:
      pep = ((ep_addr & 0x80) == 0x80) ? &ep_in[ep_addr & 0x7F]:\
                                         &ep_out[ep_addr & 0x7F];
      if(usb.isStallEP(ep_addr))
      {
        pep->status = 0x0001;
      }
      else
      {
        pep->status = 0x0000;
      }

      ctlSendData ( (uint8_t *)&pep->status, 2);
      break;

    default:
      ctlError(req);
      break;
    }
    break;

  default:
    break;
  }
  return ret;
}




int  CUsbDevice::ctlSendData (
                               uint8_t *pbuf,
                               uint16_t len)
{
  /* Set EP0 State */
  ep0_state          = USBD_EP0_DATA_IN;
  ep_in[0].total_length = len;
  ep_in[0].rem_length   = len;
 /* Start the transfer */
  //USBD_LL_Transmit (pdev, 0x00, pbuf, len);
  usb.transmit(0x00, pbuf, len);

  return USBD_OK;
}


void CUsbDevice::ctlError( CUsbRequest &req)
{
  usb.stallEP(0x80);
  usb.stallEP(0);
}


void CUsbDevice::getConfig( CUsbRequest &req)
{
  if (req.length != 1)
  {
     ctlError( req);
  }
  else
  {
    switch (dev_state )
    {
    case USBD_STATE_ADDRESSED:
      dev_default_config = 0;
      ctlSendData ((uint8_t *)&dev_default_config,
                        1);
      break;

    case USBD_STATE_CONFIGURED:

      ctlSendData ((uint8_t *)&dev_config,
                        1);
      break;

    default:
       ctlError(req);
      break;
    }
  }
}


void CUsbDevice::getStatus( CUsbRequest &req)
{
  switch (dev_state)
  {
  case USBD_STATE_ADDRESSED:
  case USBD_STATE_CONFIGURED:

#if ( USBD_SELF_POWERED == 1)
    dev_config_status = USB_CONFIG_SELF_POWERED;
#else
    pdev->dev_config_status = 0;
#endif

    if (dev_remote_wakeup)
    {
       dev_config_status |= USB_CONFIG_REMOTE_WAKEUP;
    }

    ctlSendData ( (uint8_t *)& dev_config_status,
                      2);
    break;

  default :
    ctlError(req);
    break;
  }
}


void CUsbDevice::setFeature( CUsbRequest &req)
{
  if (req.value == USB_FEATURE_REMOTE_WAKEUP)
  {
    dev_remote_wakeup = 1;
    exception( );
    //pdev->pClass->Setup (pdev, req);
    ctlSendStatus();
  }

}


void CUsbDevice::clrFeature( CUsbRequest &req)
{
  switch (dev_state)
  {
  case USBD_STATE_ADDRESSED:
  case USBD_STATE_CONFIGURED:
    if (req.value == USB_FEATURE_REMOTE_WAKEUP)
    {
      dev_remote_wakeup = 0;
      exception( );
      //pdev->pClass->Setup (pdev, req);
      ctlSendStatus();
    }
    break;

  default :
     ctlError( req);
    break;
  }
}


void CUsbDevice::setConfig( CUsbRequest &req)
{
  static uint8_t  cfgidx;

  cfgidx = (uint8_t)(req.value);

  if (cfgidx > USBD_MAX_NUM_CONFIGURATION )
  {
     ctlError(req);
  }
  else
  {
    switch (dev_state)
    {
    case USBD_STATE_ADDRESSED:
      if (cfgidx)
      {
        dev_config = cfgidx;
        dev_state = USBD_STATE_CONFIGURED;
        if(setClassConfig( cfgidx) == USBD_FAIL)
        {
          ctlError( req );
          return;
        }
        ctlSendStatus();
      }
      else
      {
         ctlSendStatus();
      }
      break;

    case USBD_STATE_CONFIGURED:
      if (cfgidx == 0)
      {
        dev_state = USBD_STATE_ADDRESSED;
        dev_config = cfgidx;
        exception( );
        //clrClassConfig(cfgidx);
        ctlSendStatus();

      }
      else  if (cfgidx != dev_config)
      {
        /* Clear old configuration */
         exception( );
        //USBD_ClrClassConfig(pdev , pdev->dev_config);

        /* set new configuration */
        dev_config = cfgidx;
        exception( );
        //if(USBD_SetClassConfig(pdev , cfgidx) == USBD_FAIL)
        {
          ctlError(req);
          return;
        }
        ctlSendStatus();
      }
      else
      {
        ctlSendStatus();
      }
      break;

    default:
       ctlError( req);
      break;
    }
  }
}


void CUsbDevice::ctlSendStatus ()
{
  /* Set EP0 State */
  ep0_state = USBD_EP0_STATUS_IN;

 /* Start the transfer */
  lDebug("Sending Status");
  usb.transmit ( 0x00, nullptr, 0);
}

void CUsbDevice::setSpeed( USBD_SpeedTypeDef speed )
{
  dev_speed = speed;
}

USBD_StatusTypeDef  CUsbDevice::ctlContinueSendData ( uint8_t *pbuf, uint16_t len)
{
 /* Start the next transfer */
  usb.transmit (0x00, pbuf, len);

  return USBD_OK;
}

USBD_StatusTypeDef CUsbDevice::ctlReceiveStatus()
{
  /* Set EP0 State */
  ep0_state = USBD_EP0_STATUS_OUT;

 /* Start the transfer */
  prepareReceive ( 0, NULL, 0);

  return USBD_OK;
}

USBD_StatusTypeDef CUsbDevice::prepareReceive(uint8_t ep_addr, uint8_t *pbuf,
                                          uint16_t size)
{
  usb.receive(ep_addr, pbuf, size);
  return USBD_OK;
}

USBD_StatusTypeDef  CUsbDevice::runTestMode ()
{
  return USBD_OK;
}


USBD_StatusTypeDef CUsbDevice::ctlContinueRx (uint8_t *pbuf, uint16_t len)
{
  prepareReceive (0, pbuf, len);
  return USBD_OK;
}


USBD_StatusTypeDef CUsbDevice::setClassConfig(uint8_t cfgidx)
{
  USBD_StatusTypeDef   ret = USBD_FAIL;

  if(pClass != NULL)
  {
    /* Set configuration  and Start the Class*/
    if(pClass->init(cfgidx) == 0)
    {
      ret = USBD_OK;
    }
  }
  return ret;
}


USBD_StatusTypeDef CUsbDevice::registerClass(CUsbClass *_pClass)
{
   pClass=_pClass;
   return(USBD_OK);
}


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