#include "drivers/usb/class/common/class_common.h"
#include "app/usb/usb_transfer_flags.h"  // USB_TRANSFER_FLAG_SHORTPACKET
#include "usbd_desc.h"
#include "usbd_ctlreq.h"

#if DEBUG_USB > 0 // SCPI debug
volatile uint32_t gDebugUSBEPTxBytes = 0;
volatile uint32_t gDebugUSBEPTxLastPacket = 0;
#endif

// USBD_COMMON_EP0_RxReady
// This function is called when a packet via CONTROL protocol had been received from host
// This function continues the transmitting
uint8_t  USBD_COMMON_EP0_RxReady (USBD_HandleTypeDef *pdev, sUSBTransfer_t * rx, size_t * pnBytesRollback )
{   
  USBD_EndpointTypeDef  * pep = &pdev->ep_out[0];
  USBD_StatusTypeDef ret = USBD_FAIL;
  
  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 );
              
              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:                                      
                                      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;
}

// USBD_COMMON_EP0_TxSent()
// This function is called when at least one packet via CONTROL protocol had been sent to the host
// This function continues the transmitting
uint8_t  USBD_COMMON_EP0_TxSent (USBD_HandleTypeDef *pdev, sUSBTransfer_t * tx, size_t * pnBytesRollback )
{   
  USBD_EndpointTypeDef  * pep = &pdev->ep_in[0];

  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:                  
                  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;
}

// USBD_COMMON_Setup_DataProcess()
// Handler for specific vendor or class requests
// @pdev, @pUserIface, @req and @transf MUST BE NON-NULL!
// Do not call if @req->wLength is zero
uint8_t USBD_COMMON_Setup_DataProcess( USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req, sUSBTransfer_t * transf )
{
    uint8_t ret = USBD_OK;

    USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);

    if (req->wLength > 0)
    {
        if (req->bmRequest & 0x80)
        {
          // DataIN stage preparing
          if( usb_validate_transfer( transf ) )  // TX-transfer
          {
              // First packet in the stage: reset the transfer
              usb_reset_transfer( transf );
              
              // Single-transfer mode only:
              // Whole transfer is limited by the @ep0_tx_transfer size
              size_t nBytesProcessed = pUserIface->fUsbCtlEpTx( (const tUSBSetupPacket_t*)req,
                                  transf,
                                  0,
                                  req->wLength );

              // User can perform move semantic on @ep0_tx_transfer transfer
              // This is an error condition, let's check it:              
              if( usb_validate_transfer( transf ) )
              {
                  USBD_EndpointTypeDef  * pep = &pdev->ep_in[0];

                  // If SETUP data stage assumes trasmitting more than one MAX_PACKET bytes,
                  // this packet must include MAX_PACKET bytes (not to be "short packet")
                  size_t min_limit = ((req->wLength > pep->maxpacket)?(pep->maxpacket-1):(0));
                   
                  // Actually, @ep0_tx_transfer can store more data than @nBytesProcessed.
                  // @nBytesProcessed: bytes processed by last @fUsbCtlEpTx call.
                  // Just check it for zero:
                  size_t nBytesCount = usb_count_transfer( transf );
                  if( nBytesProcessed > 0 )
                  {
                      // Check the transfer for minimal bytes limit:
                      if( min_limit < nBytesCount )
                      {
                          // truncate the transfer in case if the transfer size is 
                          // greater than requested length.
                          usb_truncate_transfer( transf, req->wLength );

                          USBD_CtlSendData (pdev, 
                                            (const uint8_t *)transf->pReadData,
                                            req->wLength ); 
                      }
                      else
                      {
                          USBD_CtlSendData (pdev, 
                                            (const uint8_t *)transf->pReadData,
                                            nBytesCount );
                          //ret = USBD_FAIL;
                      }
                  }
                  else
                  {
                      ret = USBD_FAIL;
                  }
              }
              else
              {
                  ret = USBD_FAIL;
              }
           }
           else
           {
               ret = USBD_FAIL;
           }
            
        }
        else
        {
            // DataOUT stage preparing
            if( usb_validate_transfer( transf ) )  // RX-transfer
            {
                // Reset the RX-transfer
                usb_reset_transfer( transf );
                
                // Begin receiving first packet
                USBD_CtlPrepareRx (pdev, 
                                   (uint8_t *)transf->pWriteData,
                                    req->wLength );
            }
            else
            {
                ret = USBD_FAIL;
            } 
        }
    }
    else
    {
        ret = USBD_FAIL;  // must be processed by caller
    }

    return ret;
}


#if DATAIN_INITIATESEND_REQUIRED
// USBD_COMMON_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
//
// Note: e.g., is called from @USBD_USBTMC_DataIn_BeginSend
//
uint8_t USBD_COMMON_DataIn_BeginSend( USBD_HandleTypeDef *pdev, uint8_t epnum, sUSBTransfer_t * transf, bool bTxAlreadyPrepared )
{
    USBD_EndpointTypeDef * pep = &pdev->ep_in[ epnum & 0x7F ];
    USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);

    if( !bTxAlreadyPrepared )
    {
        if( NULL == pUserIface->fDataTxHandler )
        {
            // error: no user handler specified
            return USBD_FAIL;
        }

        // restore transfer
        usb_reset_transfer( transf );

#warning SCPI: note, that setting 'short-packet' indicator is useless here
        // forward set short packet indicator:
        // (just to be compatible to @USBD_COMMON_DataInProcess function)
        usb_transfer_set_shortpacket( transf );

        // call user handler: generate virtual DataIN event        
        if( ! pUserIface->fDataTxHandler( epnum, transf ) )
        {
            // error: user handler error
            return USBD_FAIL;
        }
    }

    uint16_t bytes_to_send = usb_count_transfer( transf );

    if( bytes_to_send > pep->maxpacket )
    {
        bytes_to_send = pep->maxpacket;
    }

    if( bytes_to_send == pep->maxpacket )
    {
        // this is the last packet with size equal EP size: the next packet must be zero packet
        usb_transfer_set_shortpacket( transf );
    }

    // initiate the data sending
    // Note: @total_length and @rem_length will be modified: @bytes_to_send will be stored to these fields. 
    if( USBD_OK != USBD_EpSendData( pdev, (uint8_t*)transf->pReadData, bytes_to_send, epnum ) )
    {
#if DEBUG_USB
        gDebugUSBEPTxLastPacket = bytes_to_send;
#endif
        // error: can not send data
        return USBD_FAIL;
    }

    // perform virtual reading due to the USB Low Level have sent the data from the transfer
    usb_transfer_virtual_read( transf, bytes_to_send );

#if DEBUG_USB > 0 // SCPI debug
    gDebugUSBEPTxBytes += bytes_to_send;
#endif

    return USBD_OK;
}
#endif

// USBD_USBTMC_DataIn_ZeroSend
// Queue zero packet in EP IN
//
uint8_t USBD_COMMON_DataIn_ZeroSend( USBD_HandleTypeDef *pdev, uint8_t epnum )
{
    USBD_EndpointTypeDef * pep = &pdev->ep_in[ epnum & 0x7F ];
    USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);

    uint8_t dummy = 0;
    // initiate the data sending
    // Note: @total_length and @rem_length will be modified: @bytes_to_send will be stored to these fields. 
    if( USBD_OK != USBD_EpSendData( pdev, &dummy, 0, epnum ) )
    {
        // error: can not send data
        return USBD_FAIL;
    }
    (void)pep, (void)pUserIface;
    return USBD_OK;
}

// USBD_COMMON_DataInProcess
// Process DataIN events for Bulk and Interrupt endpoints
//
// Note: e.g., is called from @USBD_USBTMC_DataIn()
//
uint8_t USBD_COMMON_DataInProcess( USBD_HandleTypeDef *pdev, uint8_t epnum, sUSBTransfer_t * transf )
{
    #if DEBUG_USBTMC_REQUESTS > 0
    void debug_log( uint8_t value, uint16_t value2 );
    #define DEBUG_LOG_STATE_DATAIN  0x04
    debug_log( DEBUG_LOG_STATE_DATAIN, usb_count_transfer( transf ) );
    #endif
    USBD_EndpointTypeDef * pep = &pdev->ep_in[ epnum & 0x7F ];
    USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);

    #if !DATAIN_INITIATESEND_REQUIRED
    // Initiating the first packet sending is disabled,
    // the flow is being controlled by IRQ only,
    //  so the fDataTxHandler is required.
    if( NULL == pUserIface->fDataTxHandler )
    {
        return USBD_FAIL;
    }
    #endif
    
    // Note: @total_length and @rem_length had been modified: these fields stores the length of the FIRST packet sent (USBD_EpSendData)

    // Check if is there enough data for next packet
    if( usb_count_transfer( transf ) < pep->maxpacket )
    { 
        // No, the transfer does not have enough data for the full next packet

        // Check if the transfer have enough free space to store next packet?
        if( usb_space_transfer( transf ) < pep->maxpacket )
        {
            // No, the transfer must be compressed
            usb_transfer_compress( transf ); 
        }

        // Call user handler:
        // User shall reset Short-Packet indicator using @usb_transfer_modify_flags
        // ... routine to avoid sending short packet in case no data queued into
        // ... the transfer after successful call.
        if( ! pUserIface->fDataTxHandler( epnum, transf ) ) 
        {
            // restore transfer
            usb_reset_transfer( transf );

            // error
            return USBD_FAIL;
        }
    }

    // retrieve amount of bytes to send
    uint16_t bytes_to_send = usb_count_transfer( transf );

    if( bytes_to_send > pep->maxpacket )
    {
        // limit the packet size to EP buffer size
        bytes_to_send = pep->maxpacket;

        // this is the last packet with size equal EP size: the next packet must be zero packet
        usb_transfer_set_shortpacket( transf );

#if DEBUG_USB > 0
gDebugUSBEPTxLastPacket = bytes_to_send;
#endif
    }
    else
    {
        // detect if a zero packet must be send
        if( bytes_to_send == pep->maxpacket )
        {
            // this is the last packet with size equal EP size: the next packet must be zero packet
            usb_transfer_set_shortpacket( transf );
        }
        else if( bytes_to_send > 0 ) // bytes_to_send < pep->maxpacket
        {
            usb_transfer_clear_shortpacket( transf );
        }
        else
        // Check if there is no data to send:
        if( 0 == bytes_to_send )
        {
            // probably, the short packet shall be send, check it:
            if( ! usb_transfer_check_shortpacket( transf ) )
            {
                // no, there no Short Packet to be send
                // Thus, there no more data, exiting...
                return USBD_OK;
            }

            // yes, the short packet will be send now,
            // but it is requied to clear the indicator:
            usb_transfer_clear_shortpacket( transf );
        }
    }

    // send next data part:
    if( USBD_OK == USBD_EpContinueSendData( pdev, (uint8_t*)transf->pReadData, bytes_to_send, epnum ) )
    {
#if DEBUG_USB > 0 // SCPI debug
        gDebugUSBEPTxBytes += bytes_to_send;
#endif
#if DEBUG_USB
        gDebugUSBEPTxLastPacket = bytes_to_send;
#endif

        // perform virtual reading due to the USB Low Level have sent the data from the transfer
        usb_transfer_virtual_read( transf, bytes_to_send );

        return USBD_OK;
    }

    return USBD_FAIL;
}

// USBD_COMMON_DataOutProcess
// Process DataOUT events for Bulk and Interrupt endpoints
uint8_t USBD_COMMON_DataOutProcess( USBD_HandleTypeDef *pdev, uint8_t epnum, sUSBTransfer_t * transf, bool ShortPacketReceived )
{
    USBD_EndpointTypeDef * pep = &pdev->ep_out[ epnum & 0x7F ];
    USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);

    // Retrieve amount of received bytes:
    uint32_t nBytesReceived = pep->total_length - pep->rem_length;

    if( ShortPacketReceived )
        usb_transfer_set_shortpacket( transf ); // Data-out: short packet received       
    else 
        usb_transfer_clear_shortpacket( transf );  // Data-out: Not-short packet received

    if( NULL != pUserIface->fDataRxHandler )
    {
        // If zero packet received:
        if( 0 == nBytesReceived )
        {
            // restore transfer
            usb_reset_transfer( transf );

            if( pUserIface->fDataRxHandler( epnum, transf ) )
            {
                return USBD_OK;
            }
            
            goto DataOut_failed;
        }

        // Perform virtual writing to the transfer: the transfer had been modified in unusual way
        // check if the transfer able to receive such packet with length = @nBytesReceived
        if( ! usb_transfer_virtual_write( transf, nBytesReceived ) )
        {
            // No, error

            goto DataOut_failed;
        }

        #if 0
        // Check if transfer is not empty
        while( 0 < usb_count_transfer( transf ) )
        {
            #error User-defined handler @fDataRxHandler can defer the data processing til enough data is received for correct processing
            // Call user handler
            if( ! pUserIface->fDataRxHandler( epnum, transf ) )
            {
                // error: user handler call failed
                break;
            }
        }
        #else
        // Call user handler
        if( ! pUserIface->fDataRxHandler( epnum, transf ) )
        {
            goto DataOut_failed;                            
        }
        #endif

        #if 1
        // If a short packet received - it is a last transfer transaction
        if( ShortPacketReceived )
        {
            // this case no bytes can be deferred in the transfer due to no user handler
            // call is expected for this transfer after this transaction.
            if( 0 < usb_count_transfer( transf ) )
            {
                // if the transfer is not empty - this is error condition
                goto DataOut_failed;
            }

            // it is a last transfer transaction - prepare transfer for the first transaction (next transfer)
            usb_reset_transfer( transf );
        }
        #endif

        // retrieve amount of free bytes in the transfer
        uint16_t free_space = usb_space_transfer( transf );
        
        // Check if the transfer have enough free space for next transaction?
        if( usb_space_transfer( transf ) < pep->maxpacket )
        {
            // No, not enough free space
            goto DataOut_failed;
        }
        else
        {
            // yes, let's receive

            // Optimize the transfer buffer and compress the data
            usb_transfer_compress( transf );

            // To prepare the receiving next transfer, the transfer size must be a multiply of max_packet of endpoint
            free_space -= free_space % pep->maxpacket; 
        }

        // prepare the transfer for the next OUT transaction
        return USBD_EpContinueRx( pdev, (uint8_t*)transf->pWriteData, free_space, epnum );
    }

DataOut_failed:

    // restore transfer
    usb_reset_transfer( transf );

    return USBD_FAIL;
}

// USBD_COMMON_DataOutBegin
// Prepare the EP for next OUT (receive) transaction
uint8_t USBD_COMMON_DataOutBegin( USBD_HandleTypeDef  *pdev, uint8_t epnum, sUSBTransfer_t * transf )                                  
{
    USBD_COMMON_ItfTypeDef * pUserIface = ((USBD_COMMON_ItfTypeDef *)pdev->pUserData);

    // if( NULL != pUserIface->fDataRxHandler )
    // {
    //     // If the transfer contains a data
    //     // Notify user about the data stored in the transfer
    //     while( (0 < usb_count_transfer( transf )) && pUserIface->fDataRxHandler( epnum, transf ) )
    //     {
    //         // Optimize the transfer buffer and compress the data
    //         usb_transfer_compress( transf );
    //     }
    // 
    //     // Notify user about starting new transfer: transf=NULL
    //     pUserIface->fDataRxHandler( epnum, NULL );
    // }

    // recover transfer
    usb_reset_transfer( transf );

    // retrieve amount of free bytes in the transfer
    uint16_t free_space = usb_space_transfer( transf );

    USBD_EndpointTypeDef * pep = &pdev->ep_out[ epnum & 0x7F ];

    // the transfer must be able to contain at least one full packet
    if( free_space >= pep->maxpacket )
    {
        // To prepare the receiving next transfer, the transfer size must be a multiply of max_packet of endpoint
        free_space -= free_space % pep->maxpacket; 

        // prepare the transfer for the next OUT transaction
        return USBD_EpPrepareRx( pdev, (uint8_t *)transf->pWriteData, free_space, epnum );
    }
    (void)pUserIface;
    return USBD_FAIL;
}

/*
// USBD_COMMON_DataOutBegin
// Prepare the EP for next OUT (receive) transaction
uint8_t USBD_COMMON_DataOutBegin( USBD_HandleTypeDef  *pdev, uint8_t epnum, sUSBTransfer_t * transf )                                  
{
    // Optimize the transfer buffer and compress the data
    usb_transfer_compress( transf ); 

    // retrieve amount of free bytes in the transfer
    uint16_t free_space = usb_space_transfer( transf );

    // prepare the transfer for the next OUT transaction
    return USBD_EpPrepareRx( pdev, (uint8_t *)transf->pWriteData, free_space, epnum );
}
*/
/**
* @brief  USBD_COMMON_RegisterInterface
  * @param  pdev: device instance
  * @param  fops: CD  Interface callback
  * @retval status
  */
#if USBD_CONSTSUBCLASS_IFACE
uint8_t  USBD_COMMON_RegisterInterface  (USBD_HandleTypeDef   *pdev, 
                                const USBD_COMMON_ItfTypeDef *fops)
#else
uint8_t  USBD_COMMON_RegisterInterface  (USBD_HandleTypeDef   *pdev, 
                                      USBD_COMMON_ItfTypeDef *fops)

#endif
{
  uint8_t  ret = USBD_FAIL;
  
  if(fops != NULL)
  {
    pdev->pUserData= fops;
    ret = USBD_OK;    
  }
  
  return ret;
}