#include "usbapp/usb_application_benchmark.h"
#include "usb/usb_bridge.h"
#include "usb/usb_config.h"
#include "usbd_usbtmc.h"
#include "crc32.h"

static uint8_t selectedTest = 0;
static unsigned long crc = 0;

static int8_t fBenchmarkInit();
static int8_t fBenchmarkDeInit();
static void   fBenchmarkReset();
static bool   fBenchmarkSetup( const tUSBSetupPacket_t * pSetup, bool bFirstStage, bool success );
static size_t fBenchmarkControlRx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * rx, size_t idx, size_t bytesRemaining );
static size_t fBenchmarkControlTx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining );

uint8_t BENCHMARK_datain_beginsend( uint8_t epnum );
uint8_t BENCHMARK_datain_sendprepared( uint8_t epnum );

const sUSBAppEntry_Control_t usbapplication_BENCHMARK_control = {

     .fUsbInit     = fBenchmarkInit,
     .fUsbDeInit   = fBenchmarkDeInit,
     .fUsbSetup    = fBenchmarkSetup,
     .fUsbCtlEpRx  = fBenchmarkControlRx,
     .fUsbCtlEpTx  = fBenchmarkControlTx,
     .fResetEvent  = fBenchmarkReset,
};

static bool fBenchmark_DataInitHandler( uint8_t bEpLogAddress ); 
static bool fBenchmark_DataRxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * rx );     
static bool fBenchmark_DataTxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * tx );
static bool fBenchmark_DataErrHandler( uint8_t bEpLogAddress, uint32_t error );
static void fBenchmark_DataDeInitHandler( uint8_t bEpLogAddress ); 

const sUSBAppEntry_Data_t usbapplication_BENCHMARK_dataapp = {

     .fDataInitHandler   = fBenchmark_DataInitHandler,
     .fDataRxHandler     = fBenchmark_DataRxHandler,
     .fDataTxHandler     = fBenchmark_DataTxHandler,
     .fDataErrHandler    = fBenchmark_DataErrHandler,
     .fDataDeInitHandler = fBenchmark_DataDeInitHandler,
};
 
static size_t g_TxByte = 0;
static size_t g_RxByte = 0;
static size_t g_RxBytesReceived = 0;

static uint8_t g_BenchmarkBuffer[ 64 ];

static int8_t fBenchmarkInit()
{
    // if( !usb_bridge_register_dataapp( USBTMC_BENCHMARK_EP, &usbapplication_BENCHMARK_dataapp ) )
    //     return 1;
    
    return 0;
}

static int8_t fBenchmarkDeInit()
{
    // usb_bridge_unregister_dataapp( USBTMC_BENCHMARK_EP );
    
    return 0;
}  

static void fBenchmarkReset()
{

} 

static bool fBenchmarkSetup( const tUSBSetupPacket_t * pSetup, bool bFirstStage, bool success )
{   
    
    switch( pSetup->bRequest )
    {
        // Benchmark select test:
        // bRequest = 0x0E
        // wValue = 0(None), 1(Read), 2(Write), 3(Loop) 
        case 0x0E:
        {
            switch( pSetup->wValue )
            {
                case 0: // None
                case 1: // Read
                case 2: // Write
                case 3: // Loop

                  selectedTest = pSetup->wValue;
                  g_TxByte = 0; g_RxByte = 0;
                  memset( g_BenchmarkBuffer, 0, sizeof(g_BenchmarkBuffer) );
                  g_RxBytesReceived = 0;
                  crc = ICRC32;

                  sUSBTransfer_t * tx = USBD_USBTMC_GetDataTxTransferHandle();
                  sUSBTransfer_t * rx = USBD_USBTMC_GetDataRxTransferHandle();

                  usb_reset_transfer( tx );
                  usb_reset_transfer( rx );

                  return true; //bibb
            }
        }
        break;

        // Benchmark get selected test:
        // bRequest = 0x0F        
        case 0x0F:
        {
           return true; //bibb
        }
    } 
    return false;
}

static size_t fBenchmarkControlRx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * rx, size_t idx, size_t bytesRemaining )
{
    return 0;
}
  
static size_t fBenchmarkControlTx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining )
{
    return usb_write_transfer( tx, &selectedTest, 1 ); // Select test accept or return selected test    
}

static bool fBenchmark_DataInitHandler( uint8_t bEpLogAddress )
{
    return true;
}

static bool fBenchmark_DataRxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * rx )
{
   size_t n = usb_count_transfer(rx);

   g_RxBytesReceived += n;
                         
   crc = CRC32( crc, usb_transfer_raw_read(rx), n );

   sUSBTransfer_t * tx = USBD_USBTMC_GetDataTxTransferHandle();

   bool first = ( usb_count_transfer(tx) == 0 );

   if( n < usb_copy_transfer( tx, rx, n, false ) )
   {
       // tx buffer full
       asm("bkpt #0");
   }

   usb_reset_transfer( rx );

   if( first ) 
    BENCHMARK_datain_sendprepared( bEpLogAddress ); 

   return true;

   while( n > 0 )
   {
       size_t pos = g_RxByte % sizeof(g_BenchmarkBuffer);
       size_t free = sizeof(g_BenchmarkBuffer) - pos;
       size_t bytes = usb_read_transfer( rx, &g_BenchmarkBuffer[ pos ], free, false );

       g_RxByte += bytes;
       n -= bytes;
   }

   usb_bridge_datain_beginsend( bEpLogAddress ); 

   return true;
}

static bool fBenchmark_DataTxHandler( uint8_t bEpLogAddress, sUSBTransfer_t * tx )
{
   // for( ;; )
   // {
   //      size_t TxByte = g_TxByte++;
   //      if( 0 == usb_write_transfer( tx, &g_BenchmarkBuffer[TxByte % sizeof(g_BenchmarkBuffer)], 1 ) )
   //      {
   //          break;
   //      }
   // }

   return true;
}

static bool fBenchmark_DataErrHandler( uint8_t bEpLogAddress, uint32_t error )
{
   return true;
}

static void fBenchmark_DataDeInitHandler( uint8_t bEpLogAddress )
{

}


// BENCHMARK_datain_beginsend()
// Begin the transmission using already prepared TX-transfer.
// Due to the hardware does not support NAK-sent-interrupt, it is
// required to initiate the first packet sending to start TX-flow (DataIN).
// This function issues the first packet sending using virtual DataIN event.
//
uint8_t BENCHMARK_datain_beginsend( uint8_t epnum )
{
    // issue virtual DataIN event:
    return USBD_USBTMC_DataIn_BeginSend( epnum, false );
}

// BENCHMARK_datain_sendprepared()
// Begin the transmission using DataIN handler virtual call.
// Due to the hardware does not support NAK-sent-interrupt, it is
// required to initiate the first packet sending to start TX-flow (DataIN).
// This function issues the first packet sending using virtual DataIN event.
//
uint8_t BENCHMARK_datain_sendprepared( uint8_t epnum )
{
    // issue virtual DataIN event:
    return USBD_USBTMC_DataIn_BeginSend( epnum, true );
}
 