#include "usbapp/usb_application_flash.h"
#include "usb/usb_bridge.h"
#include "usb/usb_config.h"
#include "usbd_vendor.h"
#include "drivers/flash/base/extmem_flash.h"
#include "core/csect.h"
#include "core/config.h"
#include "app/nfm/nfm_base.h"

#define  PROTO_REQ_READ_DATA_FLASH        0x02   // прочитать данные из флеш (режим эмуляции банков)
#define  PROTO_REQ_WRITE_DATA_FLASH       0x03   // записать данные во флеш (режим эмуляции банков)
#define  PROTO_REQ_GET_FLAG_FLASH_STATUS  0x06   // считать состояние статуса записи флеша
#define  PROTO_REQ_SET_PROTECT_FLASH      0x07   // установить защиту флеша
#define  PROTO_REQ_READ_DATA_FLASH_EX     0xA3   // прочитать данные из флеши (линейный экспертный режим)
#define  PROTO_REQ_READ_INFO              0xA4   // чтение служебной информации
#define  PROTO_REQ_WRITE_DATA_FLASH_LIN   0xA5   // записать данные во флеш (линейный режим)
#define  PROTO_REQ_READ_DATA_FLASH_LIN    0xA6   // прочитать данные из флеш (линейный режим)

static int8_t fFlashAppInit();
static int8_t fFlashAppDeInit();
static void   fFlashAppReset();
static bool   fFlashAppSetup( const tUSBSetupPacket_t * pSetup, bool bFirstStage, bool success );
static size_t fFlashAppControlRx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining );
static size_t fFlashAppControlTx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * rx, size_t idx, size_t bytesRemaining );

const sUSBAppEntry_Control_t usbapplication_ACM_planarproto_flash = {

     .fUsbInit     = fFlashAppInit,
     .fUsbDeInit   = fFlashAppDeInit,
     .fUsbSetup    = fFlashAppSetup,
     .fUsbCtlEpRx  = fFlashAppControlRx,
     .fUsbCtlEpTx  = fFlashAppControlTx,
     .fResetEvent  = fFlashAppReset,
};

//-----------------------------------------------------
static sUSBTransfer_t transf_write_flash;
static flash_address_t WritingAddress = 0;
static volatile bool bFlagWritingInProgress = false;

static inline bool fGetWritingFlag()
{
    bool   flag;
    __DI__ flag = bFlagWritingInProgress; __EI__
    return flag;
}

bool usb_application_flash_fGetWritingFlag()
{
   return fGetWritingFlag();
}

/*
static inline void fSetWritingFlag()
{
    __DI__ bFlagWritingInProgress = true; __EI__
}
*/
static inline void fClrWritingFlag()
{
    __DI__ bFlagWritingInProgress = false; __EI__
}

static inline bool fTrySetWritingFlag()
{
    bool flag = false;
    __DI__
        if( ! bFlagWritingInProgress )
        {
           bFlagWritingInProgress = flag = true;
        }
    __EI__
    return flag;
}
//-----------------------------------------------------

static size_t fFlashApp_ReadDataFlash( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * rx, size_t idx, size_t bytesRemaining );
static size_t fFlashApp_WriteDataFlash( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining );
static size_t fFlashApp_ReadReadyFlash( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining );
static bool fFlashApp_SetProtectionFlash( const tUSBSetupPacket_t * pSetup );
// &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&& &&&&

static int8_t fFlashAppInit()
{
    return 0;
}

// &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ----

static int8_t fFlashAppDeInit()
{
    return 0;
}

// &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ----

static void fFlashAppReset()
{

}

// &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ----

static bool fFlashAppSetup( const tUSBSetupPacket_t * pSetup, bool bFirstStage, bool success )
{
    switch ( pSetup->bRequest )
    {
        case PROTO_REQ_READ_DATA_FLASH:
        {
             // check device ready
             if( fGetWritingFlag() )
             {
                 return false; // device busy
             }
             else
             {
                 return (bool)fFlashApp_ReadDataFlash( pSetup, NULL, 0, 0 );
             }
        }
        break;

        case PROTO_REQ_WRITE_DATA_FLASH:
        {
             // check device ready
             if( fGetWritingFlag() )
             {
                 return false; // device busy
             }
             else
             {
                 return (bool)fFlashApp_WriteDataFlash( pSetup, NULL, 0, 0 );;
             }
        }
        break;

        case PROTO_REQ_GET_FLAG_FLASH_STATUS:
        {
             return true; // accept
        }
        break;

        case PROTO_REQ_SET_PROTECT_FLASH:
        {
             // perform factory-memory bank protection: inside this IRQ routine
             switch( pSetup->wValue )
             {
                 case 0x0000:
                 case 0x0001:
                 {
                    // 0x0000, 0x0001
                    return fFlashApp_SetProtectionFlash( pSetup );
                 }
             }
        }
        break;
    }

    return false;
}

// &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ----

static size_t fFlashAppControlRx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * rx, size_t idx, size_t bytesRemaining )
{
    switch ( pSetup->bRequest )
    {
        case PROTO_REQ_WRITE_DATA_FLASH:
        {   
             return fFlashApp_WriteDataFlash( pSetup, rx, idx, bytesRemaining );
        }
        break;
    }

    return 0;
}

// &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ---- &&&& ----

static size_t fFlashAppControlTx( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining )
{
    switch ( pSetup->bRequest )
    {
        case PROTO_REQ_READ_DATA_FLASH:
        {
             return fFlashApp_ReadDataFlash( pSetup, tx, idx, bytesRemaining );
        }
        break;

        case PROTO_REQ_GET_FLAG_FLASH_STATUS:
        {
             return fFlashApp_ReadReadyFlash( pSetup, tx, idx, bytesRemaining );
        }
        break;
    }

    return 0;
}

// &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ----

static size_t fFlashApp_ReadDataFlash( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining )
{
    flash_address_t chunk_address = idx + (flash_address_t)pSetup->wIndex + (((flash_address_t)pSetup->wValue)<<16);

    if( NULL == tx )
    {
        return ((ExtMemHandle.RangeCheck_Read( chunk_address, bytesRemaining ))?1:0);
    }

    flash_size_t  chunk_bytes = MIN( bytesRemaining, usb_space_transfer( tx ) );

    if( ExtMemHandle.Read( chunk_address, usb_transfer_raw_write(tx), chunk_bytes ) )
    {
        return usb_transfer_virtual_write( tx, chunk_bytes );
    }

    return 0;

}

// &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ----

static size_t fFlashApp_WriteDataFlash( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * rx, size_t idx, size_t bytesRemaining )
{
    flash_address_t chunk_address = idx + (flash_address_t)pSetup->wIndex + (((flash_address_t)pSetup->wValue)<<16);

    if( NULL == rx )
    {
        // For normal operation it is required that whole the packet
        // fits to the Low-Level RX-buffer (aka RX-transfer), and this
        // event fires only when all the data already received.
        // This fact guaranees that the application CAN USE MOVE SEMANTIC
        // on the RX-transfer because it is a last RX-event in the sequence.
        if( pSetup->wLength > USBD_VENDOR_get_control_rx_transfer_size() )
        {
            // No, the whole transfer length is greater than RX-transfer size.
            // Partial writing is not supported
            // Whole packet must fit into the transfer size.
            return false;
        }

        return ((ExtMemHandle.RangeCheck_Write( chunk_address, bytesRemaining ))?1:0);
    }



    // Check device ready and set busy flag
    if( fTrySetWritingFlag() )
    {
        flash_size_t    chunk_bytes   = MIN( bytesRemaining, usb_count_transfer( rx ) );

        // For normal operation it is required that whole the packet
        // fits to the Low-Level RX-buffer (aka RX-transfer), and this
        // event fires only when all the data already received.
        // This fact guaranees that the application CAN USE MOVE SEMANTIC
        // on the RX-transfer because it is a last RX-event in the sequence.

        // Check if all the data already is in the transfer:
        if( (0 == idx) && (usb_count_transfer( rx ) == bytesRemaining) )
        {
            // Ok, all conditions are true

            // use MOVE SEMANTIC on the passed transfer @rx
            // Make the source @rx transfer invalid
            // Move all resources from @rx to the new object @transf_write_flash
            // After the operation the source @rx transfer will be invalid.
            // The core will not be able use internal RX-transfer (specified by @rx).
            // Need to move the transfer back
            usb_move_transfer( &transf_write_flash, rx, false );

            // Remember region parameters
            WritingAddress = chunk_address;

            // It is required to remember the transfer object the move semantic implemented on.
            // Use user context value of the transfer objects: store the @rx pointer into the @transf_write_flash
            usb_transfer_set_context( &transf_write_flash, rx );

            // NOW USB-CORE UNABLE TO RECEIVE DATA!!!
            // Internal USB-CORE RX-buffer is moved to the @transf_write_flash object!

            // report the core that all
            return usb_count_transfer( &transf_write_flash );
        }

        // on error case: reset the flag
        fClrWritingFlag();
    }

    return 0;
}

// &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ----

static size_t fFlashApp_ReadReadyFlash( const tUSBSetupPacket_t * pSetup, sUSBTransfer_t * tx, size_t idx, size_t bytesRemaining )
{
    flash_size_t  chunk_bytes = MIN( bytesRemaining, usb_space_transfer( tx ) );

    uint8_t value[2] = { 0x00 };
    if( fGetWritingFlag() )
        value[0] = 0x00A5;
    else
        value[0] = 0x0000;

    if( (idx < sizeof( value )) && (bytesRemaining <= sizeof( value )) )
    {
         usb_push_transfer( tx, &value[idx], bytesRemaining );
    }

    return usb_count_transfer( tx );

}

// &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ----

static bool fFlashApp_SetProtectionFlash( const tUSBSetupPacket_t * pSetup )
{
    bool ret = false;

    switch( pSetup->wValue )
    {
        case 0x0000:
        {
             #if CONFIG_NFMBASECLASS
             ret = NFMClass->methods.xMemoryProtection.enable();
             #else
             ret = ExtMemHandle.BankProtect( extmem_bank_factory, true )   // Set BANK0 protection
                    &&
                   ExtMemHandle.BankProtect( extmem_bank_user, false );    // Clear BANK1 protection (always unprotected)
             #endif
        }
        break;

        case 0x0001:
        {
             #if CONFIG_NFMBASECLASS
             ret = NFMClass->methods.xMemoryProtection.disable();
             #else
             ret = ExtMemHandle.BankProtect( extmem_bank_factory, false )  // Clear BANK0 protection
                    &&
                   ExtMemHandle.BankProtect( extmem_bank_user, false );    // Clear BANK1 protection (always unprotected)
             #endif
        }
        break;
    }
    /* 30/05/19, Testing
    asm("bkpt #0");
    bool bank0 = false;
    bool bank1 = false;
    ExtMemHandle.CheckBankProtect( extmem_bank_factory, &bank0 );
    ExtMemHandle.CheckBankProtect( extmem_bank_user, &bank1 );
    asm("bkpt #0");
    ExtMemHandle.BankProtect( extmem_bank_factory, false );
    ExtMemHandle.BankProtect( extmem_bank_user, false );
    asm("bkpt #0");
    ExtMemHandle.BankProtect( extmem_bank_factory, true );
    ExtMemHandle.BankProtect( extmem_bank_user, false );
    asm("bkpt #0");
    ExtMemHandle.BankProtect( extmem_bank_factory, false );
    ExtMemHandle.BankProtect( extmem_bank_user, true );
    asm("bkpt #0");
    ExtMemHandle.BankProtect( extmem_bank_factory, true );
    ExtMemHandle.BankProtect( extmem_bank_user, true );
    asm("bkpt #0");
    (void)bank0; (void)bank1;
    */
    return ret;
}

// &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ---- &--& ----
       
void usb_application_flash_serve()
{
    // Serve the flash u-application

    // Check if the writing flag is set
    if( fGetWritingFlag() )
    {
        #if CONFIG_NFMCLASS_AUTOPROTECT_MEMORY
            // protect factory data: set memory protection
         NFMClass->methods.xMemoryProtection.disable();
         #endif
        
        // retireve the data pointer
        void * pWriteData = usb_transfer_raw_read( &transf_write_flash );

        flash_address_t WritingSize = usb_count_transfer( &transf_write_flash );

        // perform the writing
        ExtMemHandle.Write( WritingAddress, pWriteData, WritingSize );

        // It is required to return the USB-CORE buffer back.
        sUSBTransfer_t * usb_core_rx_transfer = NULL;

        // Retrieve the core-transfer object pointer from the @transf_write_flash context value:
        if( usb_transfer_get_context( &transf_write_flash, (void**)&usb_core_rx_transfer ) )
        {
            // Move the memory buffer back to the CORE:
            usb_move_transfer( usb_core_rx_transfer, &transf_write_flash, false );

            // Reset transfer (optional)
            usb_reset_transfer( usb_core_rx_transfer );
        }

        // Reset the writing flag
        fClrWritingFlag();
        #if CONFIG_NFMCLASS_AUTOPROTECT_MEMORY
        // protect factory data: set memory protection
        NFMClass->methods.xMemoryProtection.enable();
        #endif
    }
}

