// -----------------------------------------------------------------------------
// USB Library "usblib"
// Author:  Sychov A.
// Date:    29/07/2019
// Version: 1.1
// -----------------------------------------------------------------------------
// File:    Thread-unsafe internal buffer-object 'USB-transfer' header
// Version: 1.1
// ------------------------------------------------------------------------------

#ifndef USB_LOGICAL_TRANSFER_H
#define USB_LOGICAL_TRANSFER_H

       #include <stddef.h>
       #include <stdint.h>
       #include <stdbool.h>
       

       // USB_TRANSFER_MOVE_SEMANTIC
       // Enables a move semantic on the transfers.
       // When enabled, some functions will perfrom additional checking of a transfer parameters.
       #define USB_TRANSFER_MOVE_SEMANTIC    1

       // USB_TRANSFER_SUPPORT_FLAGS
       // Implements user-defined flags support for each transfer
       #define USB_TRANSFER_SUPPORT_FLAGS    1

       // USB_TRANSFER_SUPPORT_EOTH
       // Implements the End-of-transfer-handler support
       #define USB_TRANSFER_SUPPORT_EOTH     0

       struct sUSBTransfer_t;

       // USB Transfers
       // Software module implements the logical data unit with separated
       // read- and write- methods based on linear buffer.
       
       // fEndOfTransferHandler_t
       // End-of-thansfer handler, is called when data in the transfer running out       
       // @transf - the transfer generated the the handler call
       // @ctx - user defined context, the context specified at usb_create_pipe()
       // The handler is only called when usb_read_pipe() is performed.
       // The value returned by the handler must be amount of bytes loaded into the transfer 
       typedef size_t ( * fEndOfTransferHandler_t)( struct sUSBTransfer_t * transf, void * ctx );

       // @tUSBTransferFlag_t
       // User-dependent general-purpose bit flags
       typedef uint32_t tUSBTransferFlags_t;

       // @tUSBTransferFlagMask_t
       // User-dependent lock-mask for general-purpose bit flags
       typedef uint32_t tUSBTransferFlagMask_t;

       // @eUSBTransferFlagMode_t
       // Flags checking mode for @usb_transfer_check_flags()
       typedef enum {

          eUSBTransferFlagMode_Or,   // OR-operation with mask
          eUSBTransferFlagMode_And,  // AND-operation with mask
       }
       eUSBTransferFlagMode_t;

       // Usb Data Transfer Descriptor
       struct sUSBTransfer_t
       {
            size_t size;                    // maximum buffer capacity
            
            uint8_t * pDefaultBuffer;       // Main data buffer
            uint8_t * pWriteData;           // current Write-data pointer
            uint8_t * pReadData;            // current Read-data pointer

            #if USB_TRANSFER_SUPPORT_EOTH
            // End-of-transfer handler: processes the event when the last byte of pipe transferred
            fEndOfTransferHandler_t fEndOfTransferHandler;  
            #endif

            #if USB_TRANSFER_SUPPORT_FLAGS
            tUSBTransferFlags_t flags;
            tUSBTransferFlagMask_t flagsLock;
            #endif

            void * ctx;                     // used-defined context
       }; 
       typedef struct sUSBTransfer_t sUSBTransfer_t;  
                
       // USB Logical Pipe Descriptor
       typedef struct
       {     
            sUSBTransfer_t TxTransfer;      // IN: Device->Host pipe          
            sUSBTransfer_t RxTransfer;      // OUT: Host->Device pipe
       } 
       sUSBEpChannel_t;

       // usb_reset_transfer()       
       // Non thread-safe!       
       // Resets the specified transfer.
       // Note: the specified end-of-transfer handler isn't called.
       void usb_reset_transfer( sUSBTransfer_t * pUsbTransfer );

       // usb_reset_channel()       
       // Non thread-safe!       
       // Resets either RX and TX transfers in the channel
       void usb_reset_channel( sUSBEpChannel_t * ch );

       // usb_reset_channel_tx()
       // Non thread-safe!
       // Resets the TX-transfer in the channel
       void usb_reset_channel_tx( sUSBEpChannel_t * ch );
       
       // usb_reset_channel_rx()
       // Non thread-safe!
       // Resets the RX-transfer in the channel
       void usb_reset_channel_rx( sUSBEpChannel_t * ch );

       #if USB_TRANSFER_SUPPORT_EOTH
       // usb_create_channel()
       // Creates a bidirectional linear-buffered logical-channel
       // @ch - the channel to construct
       // @pTxBuffer and @txSize - buffer and size of the buffer to Tx operations
       // @pRxBuffer and @rxSize - buffer and size of the buffer to Rx operations
       // @eotTxHandler - the event handler, is called when data run out in Tx Buffer
       // @eotRxHandler - the event handler, is called when data run out in Rx Buffer
       // @chContext - user defined context, will be passed into @eotTxHandler and @eotRxHandler
       // Returns true in case the channel is successfully created
       bool usb_create_channel( sUSBEpChannel_t * ch,
                              void * pTxBuffer, size_t txSize,
                              void * pRxBuffer, size_t rxSize, 
                              fEndOfTransferHandler_t eotTxHandler,
                              fEndOfTransferHandler_t eotRxHandler,
                              void * channelContext );
       #else
       bool usb_create_channel( sUSBEpChannel_t * ch,
                              void * pTxBuffer, size_t txSize,
                              void * pRxBuffer, size_t rxSize, 
                              const void * reserved1,
                              const void * reserved2,
                              void * channelContext );
       #endif

       // usb_create_transfer()
       // Creates a signle linear-buffered transfer
       // @transf - the transfer to construct
       // @pBuffer and @size - buffer and size of the buffer for transfer operations
       // @eotHandler - the event handler, is called when data run out in the buffer
       // @chContext - user defined context, will be passed into @eotHandler
       // Returns true in case the channel is successfully created
       #if USB_TRANSFER_SUPPORT_EOTH
       bool usb_create_transfer( sUSBTransfer_t * ch,
                             void * pBuffer, size_t size,                      
                             fEndOfTransferHandler_t eotHandler,
                             void * channelContext );
       #else
       bool usb_create_transfer( sUSBTransfer_t * ch,
                             void * pBuffer, size_t size,                      
                             const void * reserved,
                             void * channelContext );
       #endif

       // usb_push_transfer()
       // Non thread-safe!
       // Writes data into the transfer.
       // @transf - specifies the transfer to operate with
       // @pWriteBuffer - data to be written
       // @pushSize - amount of bytes to write
       // Returns the @pushSize in case all the data has been written into the transfer
       // Otherwise, returns 0. In this case no write operation has been performed.
       // Note: the write operation can be split up to several write operations,
       // until the buffer is full.
       // To prepare the transfer for next portion of data, use usb_reset_transfer()
       size_t usb_push_transfer( sUSBTransfer_t * transf, const void * pWriteBuffer, size_t pushSize );

       // usb_write_transfer()
       // Non thread-safe!
       // Writes data into the transfer.
       // @transf - specifies the transfer to operate with
       // @pWriteBuffer - data to be written
       // @writeSize - amount of bytes to write
       // Returns amount of bytes written into the transfer
       // In this case no write operation has been performed the function returns 0.
       // Note: the write operation can be split up to several write operations,
       // until the buffer is full.
       // To prepare the transfer for next portion of data, use usb_reset_transfer()
       size_t usb_write_transfer( sUSBTransfer_t * transf, const void * pWriteBuffer, size_t writeSize );


       // usb_read_transfer()
       // Non thread-safe!
       // Reads the data from the transfer.
       // @transf - specifies the transfer to operate with
       // @pReadBuffer - the buffer to receive data, can be NULL
       // @readSize - amount of data to copy into the @pReadBuffer, can be 0.
       // @enableEotHandler - enables/disables calling of End-Of-Transfer handler 
       // during the call if the transfer is empty.
       // In case, @readSize is equal or greater than amount of data in the transfer,
       // the function reads actual amount of data and returns count of read bytes.
       // Since all data is read from the transfer, the transfer is being reset 
       // automatically. If the End-of-transfer handler is specified for this transfer, 
       // it will be invoked after the transfer is reset only if @enableEotHandler is 
       // true, so the handler can write the next portion of data into the transfer.
       // Otherwise, if @readSize is less than amount of data in the transfer, then 
       // only @readSize bytes will be copyied into @pReadBuffer, the rest data are 
       // still stored in the transfer-buffer and should be either read by next call, 
       // or reset by calling usb_reset_transfer().
       // Note: if the data in transfer is reset by calling usb_reset_transfer(), the 
       // End-of-pipe handler is not called.
       // You can call the function with @pReadBuffer = NULL and @readSize = 0, and
       // @enableEotHandler = true to check if the transfer empty and call the End-of-
       // -transfer handler. In this case the function always returns zero.
       // Note: if you specify @pReadBuffer = NULL, you must set @readSize = 0.
       // To determine amount of bytes in the transfer use usb_count_transfer().
       //
       size_t usb_read_transfer( sUSBTransfer_t * transf, void * pReadBuffer, size_t readSize, bool enableEotHandler );

       // usb_count_transfer():
       // Non thread-safe!
       // Returns amount of data in the transfer.
       // @transf - the transfer to operate with, Non-NULL
       // Returns amount of data to be read.
       size_t usb_count_transfer( sUSBTransfer_t * transf );


       // usb_truncate_transfer()
       //
       // Non thread-safe!
       //
       // Truncates the data into the transfer.
       // @transf - the transfer to operate with, Non-NULL
       // @size - the size limit to apply
       // The function truncates the data in the transfer to the specified @size
       // Returns amount of data truncated.
       size_t usb_truncate_transfer( sUSBTransfer_t * transf, size_t size );

       #if USB_TRANSFER_SUPPORT_EOTH
       // usb_transfer_override_handler:
       // Replaces the End-of-transfer handler for the specified transfer @transf
       // @transf - the transfer to operate with
       // @handler - new end-of-transfer handler
       // If succeeded, the previous handler pointer is returned by function, and NULL otherwise.
       fEndOfTransferHandler_t usb_transfer_override_handler( sUSBTransfer_t * transf, fEndOfTransferHandler_t handler );
       #endif

       // usb_transfer_overridelength()
       //
       // Non thread-safe!
       //
       // Overrides the transfer data length.
       // @transf - the transfer to operate with, Non-NULL
       // @length - new length
       // The function overrides the original transfer data length with the specified @length
       // Returns new length value.
       size_t usb_transfer_overridelength( sUSBTransfer_t * transf, size_t length );

       // usb_transfer_virtual_read()
       //
       // Non thread-safe!
       //
       // Performs virtual reading from transfer without actual data retrieving from.       
       // @transf - the transfer to operate with, Non-NULL
       // @length - the number of bytes for virtual reading
       // The function changes the transfer data length.
       // Returns the number of read bytes
       size_t usb_transfer_virtual_read( sUSBTransfer_t * transf, size_t length );

       // usb_transfer_virtual_write()
       //
       // Non thread-safe!
       //
       // Performs virtual writing to transfer without actual data placing in.       
       // @transf - the transfer to operate with, Non-NULL
       // @length - the number of bytes for virtual writing
       // The function changes the transfer data length.
       // Returns the number of written bytes
       size_t usb_transfer_virtual_write( sUSBTransfer_t * transf, size_t length );

       // usb_transfer_compress()
       //
       // Non thread-safe!
       //
       // Optimizes the transfer by size by packing data inside.
       // @transf - the transfer to operate with, Non-NULL
       // The function moves the data from middle of the transfer to the beginning
       // with keeping actual size.
       // Returns new length value.
       size_t usb_transfer_compress( sUSBTransfer_t * transf );

       // usb_size_transfer()
       //
       // Non thread-safe!
       //
       // Returns the transfer absoulte size.
       // @transf - the transfer to operate with, Non-NULL
       // Returns the size in bytes
       size_t usb_size_transfer( sUSBTransfer_t * transf );

       // usb_space_transfer()
       //
       // Non thread-safe!
       //
       // Returns the transfer available space.
       // @transf - the transfer to operate with, Non-NULL
       // Returns amount of free space in the transfer in bytes.
       size_t usb_space_transfer( sUSBTransfer_t * transf );

       // usb_transfer_raw_write()
       // Returns raw write-pointer to the transfer storage.
       // # Ponetially dangerous function, use usb_space_transfer() before.
       static inline void * usb_transfer_raw_write( sUSBTransfer_t * transf )
       {
           return transf->pWriteData;
       }

       // usb_transfer_raw_read()
       // Returns raw read-pointer to the transfer storage.
       // # Ponetially dangerous function, use usb_count_transfer() before.
       static inline void * usb_transfer_raw_read( sUSBTransfer_t * transf )
       {
           return transf->pReadData;
       }

       #if USB_TRANSFER_MOVE_SEMANTIC
       // usb_move_transfer()
       //
       // Non thread-safe!
       //
       // Implements a move-semantic on couple of transfer objects
       // @transf_to - the transfer for move to, can not be NULL
       // @transf_from - the transfer for move from, can not be NULL
       // @copyContextValue - copy the context value assotiated with the source tranfer.
       // Returns the operation result, true if object had been moved 
       // successfully, and false otherwise.
       // The source object (@transf_from) must be valid, while it does 
       // not matter if the destination object @transf_to is invalid or not.
       bool usb_move_transfer( sUSBTransfer_t * transf_to, sUSBTransfer_t * transf_from, bool copyContextValue );
       #endif

       // usb_validate_transfer()
       //
       // Non thread-safe!
       //
       // Validates the transfer parameters. Usefull for checking objects
       // after moving by usb_move_transfer().
       // @transf - the transfer to operate with, Non-NULL
       // Returns true if the specified object is valid, false otherwise.
       bool usb_validate_transfer( sUSBTransfer_t * transf );

       // usb_transfer_get_context
       //
       // Non thread-safe!
       //
       // Retrieves optional user context value.
       // @transf - the transfer to operate with, Non-NULL
       // @pCtxStore - the pointer to the variable the function will store
       //  the context value to, must not be NULL
       //
       // Returns the context value assotiated with the @transf
       // In case of success returns 'true', and returns 'false' otherwise.
       static inline bool usb_transfer_get_context( sUSBTransfer_t * transf, void ** pCtxStore )
       {
           if( NULL != transf && NULL != pCtxStore )
           {
               *pCtxStore = transf->ctx;

               return true;
           }

           return false;
       }

       // usb_transfer_set_context
       //
       // Non thread-safe!
       //
       // Assotiate optional user context value with the transfer.
       // @transf - the transfer to operate with, Non-NULL
       // @pCtx - user-dependent context value to be assotiated with the transfer, can be NULL       
       //
       // Returns the context value assotiated with the @transf
       // In case of success returns 'true', and returns 'false' otherwise.
       static inline bool usb_transfer_set_context( sUSBTransfer_t * transf, void * pCtx )
       {
           if( NULL != transf )
           {
               transf->ctx = pCtx;

               return true;
           }

           return false;
       }

       // usb_copy_transfer()
       // Copies the data from one transfer to another
       // The source transfer is not being modified if @keepSource is true.
       // Otherwise, if @keepSource is false, the source transfer will be changed, 
       // and the data copied to destination transfer will be removed from the source transfer.
       // @count - specifies amount of data to be copied.
       // If the source transfer contains less bytes that specified in @count, or the 
       // destination transfer does not have enough free space, only the minimum between
       // these number of bytes will be copied.
       // Returns: number of copied bytes
       size_t usb_copy_transfer( sUSBTransfer_t * transf_to, sUSBTransfer_t * transf_from, size_t count, bool keepSource );

       #if USB_TRANSFER_SUPPORT_FLAGS
       // @usb_transfer_check_flags
       // Check if the user-dependent general-purpose flags fit to the specified mask @mask  and value @value using comparing method @mode.
       // @transf - the transfer object to operate with;
       // @mask - specified mask to check
       // @value - specified value to check
       // @mode - comparing method:
       //  - eUSBTransferFlagMode_Or: at least one transfer bit-flag must be equal to the specified value;
       //  - eUSBTransferFlagMode_And: all the transfer bit-flag must be equal to the specified value;
       // @pResult = the pointer to receive output result
       // Returns: true if no error occurred, false otherwise.
       bool usb_transfer_check_flags( sUSBTransfer_t * transf, tUSBTransferFlags_t mask, tUSBTransferFlags_t value, eUSBTransferFlagMode_t mode, bool * pResult );
       
       // @usb_transfer_check_flags_fast
       // Check if the user-dependent general-purpose flags fit to the specified mask @mask  and value @value using comparing method @mode.
       // @transf - the transfer object to operate with;
       // @mask - specified mask to check
       // @value - specified value to check
       // @mode - comparing method:
       //  - eUSBTransferFlagMode_Or: at least one transfer bit-flag must be equal to the specified value;
       //  - eUSBTransferFlagMode_And: all the transfer bit-flag must be equal to the specified value;
       // Returns: true if no error occurred and the comparing output is TRUE, false otherwise.
       // Note: you can use this function instead of @usb_transfer_check_flags if you are sure that the @transf pointer is valid.
       bool usb_transfer_check_flags_fast( sUSBTransfer_t * transf, tUSBTransferFlags_t mask, tUSBTransferFlags_t value, eUSBTransferFlagMode_t mode );
       
       // @usb_transfer_modify_flags
       // Modifies the user-dependent general-purpose flags using specified mask @mask and value @value
       // Returns: true if no error occurred, false otherwise.
       bool usb_transfer_modify_flags( sUSBTransfer_t * transf, tUSBTransferFlags_t mask, tUSBTransferFlags_t value );

       // @usb_transfer_modify_flags_locks
       // Modifies the lock mask for user-dependent general-purpose flags using specified mask @mask and value @value
       // Returns: true if no error occurred, false otherwise.
       bool usb_transfer_modify_flags_locks( sUSBTransfer_t * transf, tUSBTransferFlags_t lockMask, tUSBTransferFlags_t lockValue );

       #endif
#endif