#include "usbd_usbtmc.h"
#include "usbd_desc.h"
#include "usbd_ctlreq.h"
#include "usbd_usbtmc_desc.h"

// @g_husbtmc:
// Due to some methods can not use USBD_HandleTypeDef parameter, it is required
// to get the device handle @pdev in the other way: the class handle contains the 
// "backward" pointer to the device handle @pdev, and in the same time,
// the device handle contains the class handle @pClassData.
// Metods refers to @g_husbtmc: USBD_USBTMC_DataIn_BeginSend, USBD_USBTMC_StallEP, USBD_USBTMC_ClearEP
static USBD_USBTMC_HandleTypeDef * g_husbtmc = NULL; 

static uint8_t  USBD_USBTMC_Init (USBD_HandleTypeDef *pdev, 
                               uint8_t cfgidx);

static uint8_t  USBD_USBTMC_DeInit (USBD_HandleTypeDef *pdev, 
                                 uint8_t cfgidx);

static uint8_t  USBD_USBTMC_Setup (USBD_HandleTypeDef *pdev, 
                                USBD_SetupReqTypedef *req);

static uint8_t  USBD_USBTMC_Error (USBD_HandleTypeDef *pdev, 
                                uint8_t epnum);

static uint8_t  USBD_USBTMC_EP0_RxReady (USBD_HandleTypeDef *pdev, size_t * );

static uint8_t  USBD_USBTMC_EP0_TxSent (USBD_HandleTypeDef *pdev, size_t * );

static uint8_t  USBD_USBTMC_DataIn (USBD_HandleTypeDef *pdev, uint8_t );

static uint8_t  USBD_USBTMC_DataOut (USBD_HandleTypeDef *pdev, uint8_t, bool );

/* Vendor interface class callbacks structure */
const USBD_ClassTypeDef USBD_USBTMC = 
{
   .Init        = USBD_USBTMC_Init,
   .DeInit      = USBD_USBTMC_DeInit,
   .Error       = USBD_USBTMC_Error,
   .Setup       = USBD_USBTMC_Setup,
   .EP0_TxSent  = USBD_USBTMC_EP0_TxSent,
   .EP0_RxReady = USBD_USBTMC_EP0_RxReady,
   .DataIn      = USBD_USBTMC_DataIn,
   .DataOut     = USBD_USBTMC_DataOut,
   .SOF         = NULL,   
   .pDesc       = &USBD_USBTMC_DescriptorHandlers,   
};

// @USBD_USBTMC_GetDescriptorHandlers
// Returns the GetDesciptor handlers set pointer for current class
const USBD_DescriptorsTypeDef * USBD_USBTMC_GetDescriptorHandlers()
{
   if( NULL != g_husbtmc )
   return g_husbtmc->pdev->pClass->pDesc;
   return NULL;
}

/**
  * @brief  USBD_USBTMC_Init
  *         Initialize the Vendor interface
  * @param  pdev: device instance
  * @param  cfgidx: Configuration index
  * @retval status
  */
static uint8_t  USBD_USBTMC_Init (USBD_HandleTypeDef *pdev, 
                               uint8_t cfgidx)
{
  uint8_t ret = USBD_OK;
  USBD_USBTMC_HandleTypeDef   *husbtmc;  
  pdev->pClassData = USBD_malloc(sizeof(USBD_USBTMC_HandleTypeDef));
  
  if(pdev->pClassData == NULL)
  {
     ret = USBD_FAIL; 
  }
  else
  {
     husbtmc = (USBD_USBTMC_HandleTypeDef*) pdev->pClassData;
     
     husbtmc->pdev = pdev; // for @USBD_USBTMC_DataIn_BeginSend()
     g_husbtmc = husbtmc;  // for @USBD_USBTMC_DataIn_BeginSend()

     USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);
     
     /* Open EP1 OUT */
     USBD_LL_OpenEP(pdev,
                 USBTMC_BULKOUT_EP,
                 USBD_EP_TYPE_BULK,
                 USBTMC_DATAOUT_MAX_PACKET_SIZE);
     
     pdev->ep_out[ (USBTMC_BULKOUT_EP) & 0x7F ].maxpacket = USBTMC_DATAOUT_MAX_PACKET_SIZE;

     //USBD_LL_ClearStallEP( pdev, USBTMC_BULKOUT_EP );

     /* Open EP1 IN */
     USBD_LL_OpenEP(pdev,
                 USBTMC_BULKIN_EP,
                 USBD_EP_TYPE_BULK,
                 USBTMC_DATAIN_MAX_PACKET_SIZE);
     
     pdev->ep_in[ (USBTMC_BULKIN_EP) & 0x7F ].maxpacket = USBTMC_DATAIN_MAX_PACKET_SIZE;

     //USBD_LL_ClearStallEP( pdev, USBTMC_BULKIN_EP );

     /* Open EP2 IN */
     USBD_LL_OpenEP(pdev,
                 USBTMC_INTERRUPTIN_EP,
                 USBD_EP_TYPE_INTR,
                 USBTMC_DATAIN_MAX_PACKET_SIZE);
     
     // USBD_LL_ClearStallEP( pdev, USBTMC_INTERRUPTIN_EP );

     pdev->ep_in[ (USBTMC_INTERRUPTIN_EP) & 0x7F ].maxpacket = USBTMC_INTIN_MAX_PACKET_SIZE;

     // Update PMA addresses for endpoints
     USBD_LL_UpdatePMA( pdev );

     usb_create_transfer( &husbtmc->ep0_tx_transfer,
                          &husbtmc->ep0_tx_buffer[0],
                          sizeof(husbtmc->ep0_tx_buffer),
                          NULL, pdev );
     
     usb_create_transfer( &husbtmc->ep0_rx_transfer,
                          &husbtmc->ep0_rx_buffer[0],
                          sizeof(husbtmc->ep0_rx_buffer),
                          NULL, pdev );

     usb_create_transfer( &husbtmc->ep1_tx_transfer,
                          &husbtmc->ep1_tx_buffer[0],
                          sizeof(husbtmc->ep1_tx_buffer),
                          NULL, pdev );
     
     usb_create_transfer( &husbtmc->ep1_rx_transfer,
                          &husbtmc->ep1_rx_buffer[0],
                          sizeof(husbtmc->ep1_rx_buffer),
                          NULL, pdev );

     usb_create_transfer( &husbtmc->ep2_tx_transfer,
                          &husbtmc->ep2_tx_buffer[0],
                          sizeof(husbtmc->ep2_tx_buffer),
                          NULL, pdev );
    
     if( 0 == pUserIface->fUsbInit() )
     {
         // Prepare BulkOUT endpoint for the next transaction
         USBD_COMMON_DataOutBegin( pdev, USBTMC_BULKOUT_EP, &husbtmc->ep1_rx_transfer );

         if( USBD_STATE_DEFAULT == pdev->dev_state )  // useless
         {
             if( NULL != pUserIface->fResetEvent )
             {
                 pUserIface->fResetEvent();
             }
         }

         if( pdev->dev_state == USBD_STATE_CONFIGURED  )
         {
             if( NULL != pUserIface->fSetIface )
             {
                 //--------------------------------------------------------------------------------------
                 // Enumerate interface descriptors and call SetInterface callback        
                 uint16_t nConfDescLength = 0;
                 const uint8_t * pDescBytePtr = NULL;

                 #if USBD_GETDESCRIPTORS_ALTMETHOD == 0 
                 ret = USBD_CallGetDescriptorHandler( pdev, 
                                                      eGetDescriptorHandlerType_Config,
                                                      &pbuf,
                                                      &len ); 
                 #else
                 USBD_CallGetDescriptorHandlerAlt( ret, pdev, pDescBytePtr, nConfDescLength, GetConfigDescriptor );
                 #endif

                 if( USBD_OK == ret )
                 {                     
                     const sUSBConfigurationDescriptor_t * conf  = (const sUSBConfigurationDescriptor_t *)pDescBytePtr;
                 
                     size_t ifidx = 0;
                     while( nConfDescLength > 0 )
                     {
                        sUSBDescriptorHeader_t * pDescHeader = (sUSBDescriptorHeader_t*)pDescBytePtr;
                 
                        if( pDescHeader->bLength == 0 ) break;
                 
                        if( USB_DESC_TYPE_INTERFACE == pDescHeader->bDescriptorType )
                        {                     
                            const sUSBInterfaceDescriptor_t * ifdesc  = (const sUSBInterfaceDescriptor_t *)pDescBytePtr;
                            
                            if( ! pUserIface->fSetIface( cfgidx, ifidx++, ifdesc ) )
                            {
                                ret = USBD_FAIL;  
                                break; 
                            }
                        }
                 
                        nConfDescLength -= pDescHeader->bLength; 
                 
                        pDescBytePtr += (pDescHeader->bLength);
                     }

                     (void)conf;
                 }
                 //--------------------------------------------------------------------------------------
             }
         }
     }
     else
     {
         ret = USBD_FAIL;  
     }
  }
  return ret;
}

/* UNUSED FUNCTION
static const sUSBInterfaceDescriptor_t * USBD_USBTMC_SearchInterfaceDescriptor( USBD_HandleTypeDef *pdev, uint8_t cfgidx, uint8_t nIf )
{
    //--------------------------------------------------------------------------------------
    // Enumerate interface descriptors and call ClearInterface callback
    (void)cfgidx; // to do - use cfgidx to get configuration descriptor

    uint16_t nConfDescLength = 0;
    const uint8_t * pDescBytePtr = (const uint8_t * )pdev->pClass->GetFSConfigDescriptor(&nConfDescLength);

    const sUSBConfigurationDescriptor_t * conf  = (const sUSBConfigurationDescriptor_t *)pDescBytePtr;
    
    while( nConfDescLength > 0 )
    {
       sUSBDescriptorHeader_t * pDescHeader = (sUSBDescriptorHeader_t*)pDescBytePtr;
    
       if( pDescHeader->bLength == 0 ) break;
    
       if( USB_DESC_TYPE_INTERFACE == pDescHeader->bDescriptorType )
       {                     
           const sUSBInterfaceDescriptor_t * ifdesc  = (const sUSBInterfaceDescriptor_t *)pDescBytePtr;
               
           if( ifdesc->bInterfaceNumber == nIf )
           {
               return ifdesc; 
           }
       }
    
       nConfDescLength -= pDescHeader->bLength; 
    
       pDescBytePtr += (pDescHeader->bLength);
    }
    //--------------------------------------------------------------------------------------
    return NULL;
}
*/

/**
  * @brief  USBD_USBTMC_Init
  *         DeInitialize the Vendor layer
  * @param  pdev: device instance
  * @param  cfgidx: Configuration index
  * @retval status
  */
static uint8_t  USBD_USBTMC_DeInit (USBD_HandleTypeDef *pdev, 
                                 uint8_t cfgidx)
{
  uint8_t ret = USBD_OK;
      
  /* DeInit  physical Interface components */
  if(pdev->pClassData != NULL)
  {
     if( pdev->pUserData != NULL )
     {
         USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);

         if( NULL != pUserIface->fClrIface )
         {
             if(  ((pdev->dev_old_state == USBD_STATE_CONFIGURED) 
                   || (pdev->dev_old_state == USBD_STATE_SUSPENDED) )
               && ((pdev->dev_state != USBD_STATE_CONFIGURED) 
                   || (pdev->dev_state != USBD_STATE_SUSPENDED) ) )
             {
                  //--------------------------------------------------------------------------------------
                  // Enumerate interface descriptors and call ClearInterface callback
                  (void)cfgidx; // to do - use cfgidx to get configuration descriptor

                  uint16_t nConfDescLength = 0; 
                  const uint8_t * pDescBytePtr = NULL;

                  #if USBD_GETDESCRIPTORS_ALTMETHOD == 0 
                  ret = USBD_CallGetDescriptorHandler( pdev, 
                                                       eGetDescriptorHandlerType_Config,
                                                       &pbuf,
                                                       &len ); 
                  #else
                  USBD_CallGetDescriptorHandlerAlt( ret, pdev, pDescBytePtr, nConfDescLength, GetConfigDescriptor );
                  #endif

                  if( USBD_OK == ret )
                  {
                      const sUSBConfigurationDescriptor_t * conf  = (const sUSBConfigurationDescriptor_t *)pDescBytePtr;

                      size_t ifidx = 0;
                      while( nConfDescLength > 0 )
                      {
                         sUSBDescriptorHeader_t * pDescHeader = (sUSBDescriptorHeader_t*)pDescBytePtr;

                         if( pDescHeader->bLength == 0 ) break;

                         if( USB_DESC_TYPE_INTERFACE == pDescHeader->bDescriptorType )
                         {                     
                             const sUSBInterfaceDescriptor_t * ifdesc  = (const sUSBInterfaceDescriptor_t *)pDescBytePtr;
                           
                             pUserIface->fClrIface( cfgidx, ifidx++, ifdesc ); 
                         }

                         nConfDescLength -= pDescHeader->bLength; 

                         pDescBytePtr += (pDescHeader->bLength);
                      }

                      (void)conf;
                  }
                  //--------------------------------------------------------------------------------------
             }
         }
         if( USBD_STATE_DEFAULT == pdev->dev_state )
         {
             if( NULL != pUserIface->fResetEvent )
             {
                 pUserIface->fResetEvent();
             }
         }
         
         pUserIface->fUsbDeInit();
         g_husbtmc = NULL; // for USBD_USBTMC_DataIn_BeginSend() 
         USBD_free(pdev->pClassData);
         pdev->pClassData = NULL;

         USBD_LL_CloseEP( pdev, USBTMC_BULKOUT_EP );
         USBD_LL_CloseEP( pdev, USBTMC_BULKIN_EP );
         USBD_LL_CloseEP( pdev, USBTMC_INTERRUPTIN_EP );
     }
  }
  
  return ret;
}


static uint8_t  USBD_USBTMC_Error (USBD_HandleTypeDef *pdev, uint8_t epnum)
{
    if(pdev->pClassData != NULL)
    {
        if( pdev->pUserData != NULL )
        {
            USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);
            
            if( epnum == 0 )
            {
                if( NULL != pUserIface->fUsbSetup )
                {
                    pUserIface->fUsbSetup( &pdev->request, false, false );
                }
            }
            else
            {
                // Call user error handler
                if( NULL != pUserIface->fDataErrHandler )
                {
                    pUserIface->fDataErrHandler( epnum, 0 ); 
                }

                #if 0
                // // Recover EP after the error
                // USBD_USBTMC_HandleTypeDef  * husbtmc = (USBD_USBTMC_HandleTypeDef*) pdev->pClassData;
                // 
                // switch( epnum )
                // {
                //    case USBTMC_BULKOUT_EP:
                //    {
                //        // Prepare BulkOUT endpoint for the next transaction
                //        USBD_COMMON_DataOutBegin( pdev, USBTMC_BULKOUT_EP, &husbtmc->ep1_rx_transfer );
                //    }
                //    break;
                // }
                #endif
            }
        }
    }

    return USBD_OK;
}

/**
  * @brief  USBD_USBTMC_Setup
  *         Handle the class specific requests
  * @param  pdev: instance
  * @param  req: usb requests
  * @retval status
  */
static uint8_t  USBD_USBTMC_Setup (USBD_HandleTypeDef *pdev, 
                                USBD_SetupReqTypedef *req)
{
  uint8_t ret = USBD_OK;

  USBD_USBTMC_HandleTypeDef   *husbtmc = (USBD_USBTMC_HandleTypeDef*) pdev->pClassData;
  USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);
  
  if( NULL == pUserIface->fUsbSetup )
  {
      return USBD_FAIL; // @fUsbSetup is missing
  }

  if( ! pUserIface->fUsbSetup( req, true, false ) )
  {
      return USBD_FAIL; // do not call @fUsbSetup second time
  }
  else
  {      
      switch (req->bmRequest & USB_REQ_TYPE_MASK)
      {
        case USB_REQ_TYPE_CLASS :      // Class I/O
        case USB_REQ_TYPE_VENDOR :     // Debug, Benchmarks
        {
            if (req->wLength > 0)
            {
               if (req->bmRequest & 0x80)
               {
                  #if RECREATE_CONTROL_TRANSFER_OBJ
                  usb_create_transfer( &husbtmc->ep0_tx_transfer,
                  &husbtmc->ep0_tx_buffer[0],
                  sizeof(husbtmc->ep0_tx_buffer),
                  NULL, pdev ); 
                  #endif

                  ret = USBD_COMMON_Setup_DataProcess( pdev, req, &husbtmc->ep0_tx_transfer );
               }
               else
               {
                  #if RECREATE_CONTROL_TRANSFER_OBJ
                  usb_create_transfer( &husbtmc->ep0_rx_transfer,
                  &husbtmc->ep0_rx_buffer[0],
                  sizeof(husbtmc->ep0_rx_buffer),
                  NULL, pdev );
                  #endif

                  ret = USBD_COMMON_Setup_DataProcess( pdev, req, &husbtmc->ep0_rx_transfer );
               }
            }
            else
            {
                USBD_CtlSendStatus( pdev ); // need answer for ZeroLength requests
            }
        }
        break;

        case USB_REQ_TYPE_STANDARD:
        {
            switch (req->bRequest)
            {      
              case USB_REQ_GET_INTERFACE: 
              {
                   if( NULL != pUserIface->fGetIface )
                   {
                       uint8_t nIf = req->wIndex;
                       uint8_t ifalt = 0;
                       
                       if( pUserIface->fGetIface( pdev->dev_config, nIf, &ifalt ) )
                       {
                           USBD_CtlSendData (pdev, &ifalt, sizeof(ifalt));
                       }
                       else
                       {
                           ret = USBD_FAIL;
                       }
                   }
                   else
                   {
                       ret = USBD_FAIL;
                   }
              }
              break;
              
              case USB_REQ_SET_INTERFACE:
              break;
            }
        }
        break;

        default: 
          break;
      }
  }

  if( ret != USBD_OK )
  {
      pUserIface->fUsbSetup( req, false, false );
  }

  return ret;
}

#if USBTMC_USE_COMMON_EP0RXREADY 
static uint8_t  USBD_USBTMC_EP0_RxReady (USBD_HandleTypeDef *pdev, size_t * pnBytesRollback )
{ 
  USBD_USBTMC_HandleTypeDef   *husbtmc = (USBD_USBTMC_HandleTypeDef*) pdev->pClassData;

  return USBD_COMMON_EP0_RxReady( pdev, &husbtmc->ep0_rx_transfer, pnBytesRollback );  
}
#else
static uint8_t  USBD_USBTMC_EP0_RxReady (USBD_HandleTypeDef *pdev, size_t * pnBytesRollback )
{ 
  USBD_USBTMC_HandleTypeDef   *husbtmc = (USBD_USBTMC_HandleTypeDef*) pdev->pClassData;
  USBD_EndpointTypeDef  * pep = &pdev->ep_out[0];
  USBD_StatusTypeDef ret = USBD_FAIL;
  sUSBTransfer_t * rx = &husbtmc->ep0_rx_transfer;

  if( pdev->pUserData != NULL && usb_validate_transfer(rx) )
  {
      USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);

      if( NULL != pUserIface->fUsbCtlEpRx )
      {
          size_t nBytesReceived = ((pep->rem_length > pep->maxpacket)?pep->maxpacket:pep->rem_length); 
          size_t nBytesRemaining = (pep->rem_length - nBytesReceived);

          // Since the core uses raw @ep0_rx_transfer->pWriteData pointer, it is required to update the transfer information.
          // Use 'virutal write' to shift the pointer:
          usb_transfer_virtual_write( rx, nBytesReceived );

          // Check if the trasfer have enough free space for receiving another packet?
          // Or maybe it is a last packet?
          if( (usb_space_transfer(rx) < MIN(nBytesRemaining, pep->maxpacket)) || (0 == nBytesRemaining) )
          {
              // No, there no enough free space
              // Need to call user handler.

              // Retirieve actual amount of data in transfer
              size_t nBytesInTransfer = usb_count_transfer(rx);

              // Calculate current data offset
              size_t nOffset = (pep->total_length - nBytesRemaining - nBytesInTransfer);
    
              // pass the data to user
              size_t nBytesProcessed = pUserIface->fUsbCtlEpRx( &pdev->request, 
                                       rx,
                                       nOffset,
                                       nBytesRemaining + nBytesInTransfer );

              // user can perform move semantic on transfer @rx.
              // It is valid condition if no remaining bytes left only.
              // Let's check it:
              bool bValid = usb_validate_transfer( rx );

              #warning Check
              if( 0 == nBytesRemaining || bValid )
              {
                  // Actually, it does not matter, what exactly the function @fUsbCtlEpRx returns.
                  // The only that matters is the amount of data in the transfer.
                  // Just check the return value for zero:
                  if( nBytesProcessed > 0 )
                  {
                      // By default ( usb_count_transfer() is ZERO ) ==> @nCompressed equals to the number of bytes in the transfer before user handler call.
                      size_t nCompressed = nBytesInTransfer; 
                      
                      // It is required that the transfer be valid only if @nBytesRemaining greater than zero
                      if( bValid )  
                      {
                          // If the transfer still contains data, it means user didn't perform full read.
                          if( usb_count_transfer(rx) > 0 )
                          {
                              // it is required to compress the data in the transfer
                              // optimize the transfer to compress stored data
                              nCompressed = usb_transfer_compress( rx );

                              // @bNeedPacketSpace - true if the core deadly needs at least MAX_PACKET free bytes in transfer
                              bool bNeedPacketSpace = false;

                              // After compressing: check if the transfer have enough space for next packet?
                              if( usb_space_transfer(rx) >= MIN(nBytesRemaining, pep->maxpacket) )
                              {
                                  // yes, ok
                                  ret = USBD_OK;
                              }
                              else
                              {
                                  // nope, user didn't read enough data from the transfer to free enough space for next packet

                                  // Need to free at least MAX_PACKET free bytes in transfer
                                  bNeedPacketSpace = true;                         
                              }

                              // try to call user handler again:
                              if( bNeedPacketSpace || (0 == nBytesRemaining) )
                              {
                                  // Here: two reasons:
                                  // 1) if it is required to free MAX_PACKET bytes in transfer for next packet
                                  // 2) or this is a last packet: last chance to retrieve data. It is required to feed all data to user... Even if he resists!!!! >:D                      

                                  // For USB-core it does not matter if user processed all bytes or not...
                                  // There no incoming packets expected.
                                  // The only problem is to send a confirmation for this transaction.
                                  // As soon user process all bytes from the transfer @rx, this function returns control to the           
                                  // core to send a confirmation.

                                  // Ok, lets feed user..
                                  while( nBytesProcessed > 0 )
                                  {
                                      nBytesInTransfer = usb_count_transfer(rx);
                                      size_t nLeftBytes = nBytesInTransfer + ((bNeedPacketSpace)?nBytesRemaining:0);
                                      size_t nOffset = (pep->total_length - nLeftBytes);

                                      // Call user handler, again...
                                      nBytesProcessed = pUserIface->fUsbCtlEpRx( &pdev->request, 
                                                       rx,
                                                       nOffset,
                                                       nLeftBytes );
                                      
                                      // user can perform move semantic on transfer @rx.
                                      // It is valid condition if no remaining bytes left only.
                                      // Let's check it:
                                      #warning Check
                                      if( 0 == nBytesRemaining || usb_validate_transfer( rx ) )
                                      {
                                          // ~~~ do not do this here, extra compressing possible (reduces performance) ~~ /* usb_transfer_compress( rx );                                */
                                          // ~~~ do not do this here, extra compressing possible (reduces performance) ~~ /* nCompressed += (nBytesInTransfer - usb_count_transfer(rx)); */


                                          // Reason 1: (only if @bNeedPacketSpace is true)
                                          // check if the transfer has enough data for next packet
                                          if(     bNeedPacketSpace
                                              && (usb_space_transfer(rx) >= MIN(nBytesRemaining, pep->maxpacket)) )
                                          {
                                              // Reason 1: exit, because transfer has enough free space for next packet

                                              // User have read data portion from the transfer, need to compress it again
                                              // Need to update @nCompressed variable to take in account new pointer to the free memory.
                                              usb_transfer_compress( rx );
                                              nCompressed += (nBytesInTransfer - usb_count_transfer(rx));

                                              ret = USBD_OK;

                                              break;
                                          }
                                          else
                                          {
                                              // User have read data portion from the transfer, need to compress it again
                                              // Need to update @nCompressed variable to take in account new pointer to the free memory.
                                              usb_transfer_compress( rx );
                                              nCompressed += (nBytesInTransfer - usb_count_transfer(rx));
                                          }

                                          // check if the transfer is still not empty
                                          if( 0 == usb_count_transfer(rx) )
                                          {
                                              // Reason 2: exit if transfer is empty
                                              // Empty! => Success (nBytesProcessed>0)

                                              break; // yee, at last!
                                          }
                                      }
                                      else
                                      {
                                          // Failed!
                                          ret = USBD_FAIL;
                                      }
                                  }

                                  // Check the last user call status
                                  if( 0 == nBytesProcessed )
                                  {
                                      // Failed!
                                      ret = USBD_FAIL;
                                  }
                              }
                          }
                          else
                          {
                              // ~~~~~~~~~~~~ ADDED 18/04/19
                              // Check if the trasfer have enough free space for receiving another packet?
                              // Or maybe it is a last packet?
                              if( (usb_space_transfer(rx) < MIN(nBytesRemaining, pep->maxpacket)) )
                              {
                                   // it is required to compress the data in the transfer
                                   // optimize the transfer to compress stored data
                                   nCompressed = usb_transfer_compress( rx );
                              }
                              // ~~~~~~~~~~~~ 
                              ret = USBD_OK;
                          }
                      }
                      else
                      {
                          // If there no bytes left to receive
                          // it is normal that the transfer is invalid (user may use move semantic)
                          if( 0 == nBytesRemaining )
                          {
                              ret = USBD_OK;
                          }
                      }

                      // let the core know how many excaclty bytes is required to shift the pointer back.
                      if( (USBD_OK == ret) && (NULL != pnBytesRollback) )
                      {
                          // pass the exact number of bytes for rollback to the core
                          *pnBytesRollback = nCompressed;
                      }
                  }
                  else
                  {
                      // user must not return zero to proceed
                      usb_reset_transfer( rx );

                      ret = USBD_FAIL;
                  }
              }
              else
              {
                  ret = USBD_FAIL;
              }
          }
          else
          {
              // yep, there is free space for the next packet, let's receive it
              ret = USBD_CONTINUE;
          }
      }
  }

  return ret;
}
#endif

#if USBTMC_USE_COMMON_EP0TXSENT
static uint8_t  USBD_USBTMC_EP0_TxSent (USBD_HandleTypeDef *pdev, size_t * pnBytesRollback )
{
  USBD_USBTMC_HandleTypeDef   *husbtmc = (USBD_USBTMC_HandleTypeDef*) pdev->pClassData;

  return USBD_COMMON_EP0_TxSent( pdev, &husbtmc->ep0_tx_transfer, pnBytesRollback );  
}
#else
// USBD_USBTMC_EP0_TxSent()
// This function is called when at least one packet had been sent to the host
// This function continues the transmitting
static uint8_t  USBD_USBTMC_EP0_TxSent (USBD_HandleTypeDef *pdev, size_t * pnBytesRollback )
{ 
  USBD_USBTMC_HandleTypeDef   *husbtmc = (USBD_USBTMC_HandleTypeDef*) pdev->pClassData;
  
  USBD_EndpointTypeDef  * pep = &pdev->ep_in[0];
  sUSBTransfer_t * tx = &husbtmc->ep0_tx_transfer;

  uint8_t ret = USBD_FAIL;

  if( pdev->pUserData != NULL && usb_validate_transfer(tx) )
  {
      USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);

      if( NULL != pUserIface->fUsbCtlEpTx )
      {
          // Due to the low-level driver uses dry transfer->@pReadData pointer, the
          // tranfer object must be modified.

          size_t nBytesRequested = 0;

          if( 0 == pep->rem_length )
          {
              // Update transfer information:
              // If @pep->rem_length is zero, the last packet sent was a last packet
              // The size of the short packet is a remainder of the division the
              // full transaction size and the endpoint size.
              // If the result of operation is non zero, the last packet was a short packet.
              // If the result of operation is zero, the last packet was full packet
              size_t last_packet_size = (pep->total_length % pep->maxpacket);

              if( last_packet_size == 0 ) 
              { 
                  last_packet_size = pep->maxpacket; 
              }

              usb_transfer_virtual_read( tx, last_packet_size );
          }
          else
          {
              nBytesRequested = MIN( pep->maxpacket, pep->rem_length ); 

              // Update transfer information:
              // If @pep->rem_length is non zero, the last packet send was a full packet.
              // The size of full packet is pep->maxpacket
              usb_transfer_virtual_read( tx, pep->maxpacket );
          }

          // // Update transfer information:
          // usb_transfer_virtual_read( tx,
          //     // The variable @nBytesRequested have quite confusing name, but this argument
          //     // requires the length of already sent data. Actually the number of bytes already sent 
          //     // is numerially equals to @nBytesRequested          
          //     nBytesRequested  // for pep->rem_length >= pep->maxpacket: the pep->maxpacket bytes just have been sent
          // );                   // for pep->rem_length < pep->maxpacket: the pep->rem_length bytes just have been sent
          // 

          // If the transaction is still not completed:
          if( pep->rem_length == 0 )
          {
              // transfer full reset
              usb_reset_transfer( tx );

              ret = USBD_OK;
          }
          else
          {
              size_t nBytesLeft = usb_count_transfer( tx );

              // If the transfer has enougth data to send another packet:
              if( nBytesLeft >= nBytesRequested ) 
              {
                  ret = USBD_CONTINUE; // ask the core to continue sending the transfer (without rollback)
              }
              else
              {
                  if( nBytesLeft > 0 )
                  {
                      // optimize the transfer to compress stored data
                      
                      size_t nCompressed = usb_transfer_compress( tx );

                      if( NULL != pnBytesRollback ) 
                      {  
                          // pass the exact number of bytes for rollback to the core
                          *pnBytesRollback = nCompressed; 
                      } 
                  }
                  else
                  {
                      // // transfer full reset
                      // usb_reset_transfer( tx );
                      size_t nCompressed = usb_transfer_compress( tx );

                      if( NULL != pnBytesRollback ) 
                      {  
                          // pass the exact number of bytes for rollback to the core
                          *pnBytesRollback = nCompressed; 
                      }
                  }

                  size_t nOffset = (pep->total_length - pep->rem_length + nBytesLeft);

                  size_t nBytesProcessed = pUserIface->fUsbCtlEpTx( &pdev->request, 
                                       tx,
                                       nOffset,
                                       pep->rem_length );
                  
                  // User can perform move semantic on the @tx transfer.
                  // This is an error condition, let's check it:
                  #warning Check
                  if( usb_validate_transfer( tx ) )
                  {
                      // Actually, does not matter what exactly the function @fUsbCtlEpTx returns,
                      // because only transfer size really matters ( usb_count_transfer() ).
                      // Just check the return value for zero:
                      if( nBytesProcessed > 0 )
                      {
                          // If more than one FULL packet is required to send:
                          if( pep->rem_length >= pep->maxpacket )
                          {
                              // This packet must be full: transfer must contain at least @pep->maxpacket bytes
                              if( usb_count_transfer( tx ) >= pep->maxpacket )
                              {
                                  ret = USBD_OK;
                              }
                          }
                          else
                          // No, it is required to send "short packet"
                          {
                              // This packet must be short: transfer must contain exactly @pep->rem_length bytes 
                              if( usb_count_transfer( tx ) == pep->rem_length )
                              {
                                  ret = USBD_OK;
                              }
                          }
                      }
                      else
                      {
                          ret = USBD_FAIL;
                      }
                  }
                  else
                  {
                      ret = USBD_FAIL;
                  }
              }
          }
      }
  }

  return ret;
}
#endif


// USBD_USBTMC_DataIn 
// Handle IN requests for Bulk or Interrupt requests
// Note: this function is only called after the first packet sent!
static uint8_t USBD_USBTMC_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum )
{   
   USBD_USBTMC_HandleTypeDef * husbtmc = (USBD_USBTMC_HandleTypeDef*)pdev->pClassData;
   
   switch( epnum | 0x80 )
   {
       case USBTMC_BULKIN_EP:
       {
          return USBD_COMMON_DataInProcess( pdev, epnum, &husbtmc->ep1_tx_transfer );
       }

       case USBTMC_INTERRUPTIN_EP:
       {            
          return USBD_COMMON_DataInProcess( pdev, epnum, &husbtmc->ep2_tx_transfer );
       }
   }

   return USBD_FAIL;
}

#if DATAIN_INITIATESEND_REQUIRED
// USBD_USBTMC_DataIn_BeginSend
// Initiates the first packet sending using virtual DataIN event 
// @bTxAlreadyPrepared - if the TX-transfer is already prepared to transmission, do not reset transfer, do not call TX-handler
uint8_t USBD_USBTMC_DataIn_BeginSend( uint8_t epnum, bool bTxAlreadyPrepared )
{        
    USBD_USBTMC_HandleTypeDef * husbtmc = g_husbtmc; // impossible: "(USBD_USBTMC_HandleTypeDef*)pdev->pClassData"
    USBD_HandleTypeDef *pdev = husbtmc->pdev; // hack, I'm not satisfated with this hack, but it is required

    switch( epnum | 0x80 )
    {
        case USBTMC_BULKIN_EP:
        {
           return USBD_COMMON_DataIn_BeginSend( pdev, epnum, &husbtmc->ep1_tx_transfer, bTxAlreadyPrepared );
        }
    
        case USBTMC_INTERRUPTIN_EP:
        {
           return USBD_COMMON_DataIn_BeginSend( pdev, epnum, &husbtmc->ep2_tx_transfer, bTxAlreadyPrepared );
        }
    }

    return USBD_FAIL;
}
#endif

// USBD_USBTMC_DataIn_ZeroSend
// Queue zero packet in EP IN
uint8_t USBD_USBTMC_DataIn_ZeroSend( uint8_t epnum )
{        
    USBD_USBTMC_HandleTypeDef * husbtmc = g_husbtmc; // impossible: "(USBD_USBTMC_HandleTypeDef*)pdev->pClassData"
    USBD_HandleTypeDef *pdev = husbtmc->pdev; // hack, I'm not satisfated with this hack, but it is required

    switch( epnum | 0x80 )
    {
        case USBTMC_BULKIN_EP:
        case USBTMC_INTERRUPTIN_EP:
        {
           return USBD_COMMON_DataIn_ZeroSend( pdev, epnum );
        }
    }

    return USBD_FAIL;
}

// USBD_USBTMC_StallEP()
// Halt specfied data-endpoint by it's address and reset the buffer
void USBD_USBTMC_StallEP( uint8_t epaddr )
{
    USBD_USBTMC_HandleTypeDef * husbtmc = g_husbtmc; // impossible: "(USBD_USBTMC_HandleTypeDef*)pdev->pClassData"
    USBD_HandleTypeDef *pdev = husbtmc->pdev; // hack, I'm not satisfated with this hack, but it is required

    switch( epaddr )
    {
        case USBTMC_BULKIN_EP:
        {
           USBD_LL_StallEP( pdev, epaddr );
           usb_reset_transfer( &husbtmc->ep1_tx_transfer );
        }
        break;

        case USBTMC_BULKOUT_EP:
        {   
           USBD_LL_StallEP( pdev, epaddr );
           usb_reset_transfer( &husbtmc->ep1_rx_transfer );
        }
        break;

        case USBTMC_INTERRUPTIN_EP:
        {
           USBD_LL_StallEP( pdev, epaddr );
           usb_reset_transfer( &husbtmc->ep2_tx_transfer );
        } 
        break;
    }
}

// USBD_USBTMC_ClearEP()
// Restore specfied halted data-endpoint by it's address and reset the buffer
// Recover the Rx-transfer for specified endpoint
// Restarts the receiving for the specified endpoint
void USBD_USBTMC_ClearEP( uint8_t epaddr )
{
    USBD_USBTMC_HandleTypeDef * husbtmc = g_husbtmc; // impossible: "(USBD_USBTMC_HandleTypeDef*)pdev->pClassData"
    USBD_HandleTypeDef *pdev = husbtmc->pdev; // hack, I'm not satisfated with this hack, but it is required

    switch( epaddr )
    {
        case USBTMC_BULKIN_EP:
        {
           usb_reset_transfer( &husbtmc->ep1_tx_transfer );
           USBD_LL_ClearStallEP( pdev, epaddr );
        }
        break;

        case USBTMC_BULKOUT_EP:
        {             
           USBD_COMMON_DataOutBegin( pdev, epaddr, &husbtmc->ep1_rx_transfer );
           USBD_LL_ClearStallEP( pdev, epaddr );
        }
        break;

        case USBTMC_INTERRUPTIN_EP:
        {
           usb_reset_transfer( &husbtmc->ep2_tx_transfer );
           USBD_LL_ClearStallEP( pdev, epaddr );
        } 
        break;
    }
}

// USBD_USBTMC_NakEP()
// Reset hardware EP buffer and setup the end-point to Not-acknowlage mode
// No data will be returned by EP util the data will be queued again
void USBD_USBTMC_NakEP( uint8_t epaddr )
{
    USBD_USBTMC_HandleTypeDef * husbtmc = g_husbtmc; // impossible: "(USBD_USBTMC_HandleTypeDef*)pdev->pClassData"
    USBD_HandleTypeDef *pdev = husbtmc->pdev; // hack, I'm not satisfated with this hack, but it is required

    switch( epaddr )
    {
        case USBTMC_BULKIN_EP:
        {
           usb_reset_transfer( &husbtmc->ep1_tx_transfer );
           USBD_LL_NakEP( pdev, epaddr );
        }
        break;

        case USBTMC_BULKOUT_EP:
        {  
           usb_reset_transfer( &husbtmc->ep1_rx_transfer );           
           USBD_LL_NakEP( pdev, epaddr );
        }
        break;

        case USBTMC_INTERRUPTIN_EP:
        {
           usb_reset_transfer( &husbtmc->ep2_tx_transfer );
           USBD_LL_NakEP( pdev, epaddr );
        } 
        break;
    }
}

// USBD_USBTMC_DataOut
// Handle OUT requests for Bulk or Interrupt requests
static uint8_t USBD_USBTMC_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum, bool ShortPacketReceived )
{
   USBD_USBTMC_HandleTypeDef * husbtmc = (USBD_USBTMC_HandleTypeDef*)pdev->pClassData;
   switch( epnum )
   {
       case USBTMC_BULKOUT_EP:       
       {
          return USBD_COMMON_DataOutProcess( pdev, epnum, &husbtmc->ep1_rx_transfer, ShortPacketReceived );
       }
   }
   
   return USBD_FAIL;
}

sUSBTransfer_t * USBD_USBTMC_GetDataTxTransferHandle()
{
   return &g_husbtmc->ep1_tx_transfer;
}

sUSBTransfer_t * USBD_USBTMC_GetDataRxTransferHandle()
{
   return &g_husbtmc->ep1_rx_transfer;
} 

sUSBTransfer_t * USBD_USBTMC_GetInterruptTxTransferHandle()
{
   return &g_husbtmc->ep2_tx_transfer;
}

// USBD_USBTMC_GetTxQueuedSize
// Returns amound of bytes queued in USBTMC IN endpoint.
// @epaddr - USBTMC EP-IN number
// @pCount - pointer to the value to store the packet length, can be NULL
// Return: true if there is a queued packet in EP-IN, the length is stored in @pCount
//         false if there no queued packet in EP-IN, @pCount has not been modified
bool USBD_USBTMC_GetTxQueuedSize( uint8_t epaddr, uint32_t * pCount )
{
   USBD_USBTMC_HandleTypeDef * husbtmc = g_husbtmc; // impossible: "(USBD_USBTMC_HandleTypeDef*)pdev->pClassData"
   USBD_HandleTypeDef *pdev = husbtmc->pdev; // hack, I'm not satisfated with this hack, but it is required
   uint32_t len = 0;

   if( -1l == ( len = USBD_GetTxCount( pdev, epaddr ), len ) )
   {
       return false; // no packet queued in TX
   }

   if( NULL != pCount ) *pCount = len;

   return true; // return the packet len in @pCount
} 



