#define SCPI_CORE_STATE__PROCESSDATA_GUARD
#include "app/scpi/CommandParserStates/process_data.h"
#define SCPI_CORE_PRIVATE    // access to 'scpi_core_private.h'
#define SCPI_PROCESS_DATA_C  // access to some const variables in 'scpi_core_private.h'
#include "app/scpi/scpi_core_private.h"
#include "app/scpi/scpi_args.h"

// Refer to:
// [1] IEEE 488.2 Standard, revision IEEE Std 488.2-1987 (1992)
//     "IEEE Standard Codes, Formats, Protocols, and Common Commands for Use With IEEE Std 488.1-1987, IEEE
//      Standard Digital Interface for Programmable Instrumentation"
// [2] SCPI Specification, revision 1999.0
//     "Standard Commands for Programmable Instruments (SCPI), VERSION 1999.0, May 1999"

// =================================================================================
// @sProcessProgramDataContext_t
// State's private context typedef
typedef struct
{
   // @seqCommandProcessor
   // The SCPI command functor-sequencer
   xFSeqObj_t seqCommandProcessor;

   // @commonCtx
   // Common context for all command handlers
   sProcessProgramDataCommonContext_t commonCtx;


}
sProcessProgramDataContext_t;

// =================================================================================
// @ctx_ProcessProgramData
// State's private context
sProcessProgramDataContext_t ctx_ProcessProgramData;

// =================================================================================
// @fsqvbl_ProcessProgramData
// State's virtual table

static void fsqe_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx );
static void fsql_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx );
static const struct fFSeqEntry_t * fsqf_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx, const struct fFSeqEntry_t * * pDeferredNext );

const fFSeqVTable_t fsqvbl_ProcessProgramData = 
{
     .f = fsqf_ProcessProgramData,
     .enter = fsqe_ProcessProgramData,  
     .leave = fsql_ProcessProgramData
};

// =================================================================================
// @fsqe_ProcessProgramData
// State's enter routine
static void fsqe_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx )
{
    sScpiParserContext_t * global_ctx = ctx;
    sProcessProgramDataContext_t * private_ctx = fsq_GetPrivateCtxRef(this);

    assert ( eScpiEventWrite == global_ctx->sEvent.eCode
          || eScpiEventRead  == global_ctx->sEvent.eCode );
    
    // pass through the global context reference
    private_ctx->commonCtx.global_ctx = global_ctx;

    // pass through the handler context reference
    private_ctx->commonCtx.handler_ctx = global_ctx->sParser.xHandler->ctx;

    // forward set status-indicator
    private_ctx->commonCtx.status = eProgramDataSyntaxError;

    // reset arguments count
    private_ctx->commonCtx.args = 0;
    private_ctx->commonCtx.needParseEndOfCommand = true; // forward set
    private_ctx->commonCtx.argsParserStatus = eScpiStatus_invalid; // forward set

    private_ctx->commonCtx.event = eProgramData_Event_Write; // forward event code for handler

    // construct state machine object, pass through the global context reference
    fSeqConstruct( &private_ctx->seqCommandProcessor, 
                     &global_ctx->sParser.xHandler->entry, 
                       &private_ctx->commonCtx );

    // startup the SCPI parser state machine
    fSeqStartup( &private_ctx->seqCommandProcessor, NULL );

    // check if the command handler already set a correct parser
    // iterator to the end of current command: if not, it is required
    // search for end of the command to search the next in the line.
    if( private_ctx->commonCtx.needParseEndOfCommand )
    {
        parseArguments_helper( &private_ctx->commonCtx, NULL, 0, 0 );
    }
}

// =================================================================================
// @fsql_ProcessProgramData
// State's leave routine
static void fsql_ProcessProgramData( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx )
{
    sScpiParserContext_t * global_ctx = ctx;
    sProcessProgramDataContext_t * private_ctx = fsq_GetPrivateCtxRef(this); 

    // shutdown the SCPI parser state machine
    fSeqShutdown( &private_ctx->seqCommandProcessor );

    (void)global_ctx;
}

// =================================================================================
// @fsqf_ProcessProgramData
// State's body routine
//
// Note: processes 'read' and 'write' events for SCPI command data payload
//
static const struct fFSeqEntry_t * fsqf_ProcessProgramData( const struct fFSeqEntry_t * this, 
                                                              tFSeqCtx_t ctx, 
                                                               const struct fFSeqEntry_t * * pnext )
{
    const struct fFSeqEntry_t * nextstate = NULL;

    sScpiParserContext_t * global_ctx = ctx;
    sProcessProgramDataContext_t * private_ctx = fsq_GetPrivateCtxRef(this);
        
    // Process event:     
    switch( global_ctx->sEvent.eCode )
    {
        case eScpiEventRestart:
        {
            // go to reset state
            nextstate = fsq_GetStateById(this,eParserResetState);
            goto L_fsqf_ProcessProgramData_EXIT;
        } 
        break;

        case eScpiEventError:         // Transport error.
        {
            // Enter error state
            nextstate = fsq_GetStateById(this,eHandleError); // enter the error state
            goto L_fsqf_ProcessProgramData_EXIT;
        }
        break;

        case eScpiEventWrite:             // this dispatch() is called directly or after @eProcessCommandHeader

             my_assert( private_ctx->commonCtx.event == eProgramData_Event_Write );

        case eScpiEventRead:              // this dispatch() is called directly or after @eProcessCommandHeader
        {
             // Place response message separator
             // See [1], 8.4.1 <RESPONSE MESSAGE UNIT SEPARATOR>
             //
             if( global_ctx->sMessage.bLeadResponseSep )
             {
                 // By design the @bLeadResponseSep is set only after the previous
                 // executed command in the chain. If there no free room in the output
                 // buffer, the command handler can not place it's own response.
                 // But if there free room in the buffer the first element is placed must
                 // be response message unit separator.
                 if( global_ctx->sRead.nBufferSize > 0 )
                 {
                     // Place response unit separator before calling command handler
                     scpi_WriteCharOutput( SCPI_CHARACTER_RESPONSE_SEPARATOR );

                     global_ctx->sMessage.bLeadResponseSep = false; // reset indicator
                 }
                 else
                 {
                     // keep indicator for the next iteration
                 }
             }
        }
        break;

        default: // invalid event code
        {
            // Enter error state
            nextstate = fsq_GetStateById(this,eHandleError); // enter the error state
            goto L_fsqf_ProcessProgramData_EXIT;
        }
    }

    assert( eProgramData_Event_Done != private_ctx->commonCtx.event );

    private_ctx->commonCtx.status = eProgramDataSyntaxError; // forward set
    private_ctx->commonCtx.isQuery = global_ctx->sMessage.bQuery;
    private_ctx->commonCtx.argErrIdx = SCPI_MAX_ARGS; // reset erroneous argument index to default

    // process the internal state machine state
    // Note: @private_ctx->commonCtx.event is firstly prepared in @fsqe_ProcessProgramData
    fSeqDispatch( &private_ctx->seqCommandProcessor );

    switch( private_ctx->commonCtx.status )
    {
        case eProgramDataNeedRead: // Check for: output-buffer filled
        {
            // set global status: 'success'
            global_ctx->sEvent.eStatus = eScpiStatus_success;

            // need to continue reading, it is not done yet.
            nextstate = NULL; // stay in current state

            // Check if OUTPUT buffer is available (eScpiEventRead):
            switch( global_ctx->sEvent.eCode )
            {
                // Output buffer is available
                case eScpiEventRead:
                {
                    // "6.1.10.2.1 Message Available Message (MAV)", [1]
                    scpi_UpdateMessageAvailable();

                    // Check actual handler event code:
                    switch( private_ctx->commonCtx.event )
                    {
                        // Handler just have processed 'Write' event
                        case eProgramData_Event_Write:
                        {
                            // Handler returns NEED READ during WRITE event

                            // Change event code for handler
                            private_ctx->commonCtx.event = eProgramData_Event_Read;

                            // And call this state again now to provide 'Reading' for the handler
                            nextstate = this; 
                        }
                        break;

                        // Handler just have processed 'Read' event
                        case eProgramData_Event_Read:
                        {
                            // Handler returns NEED READ during READ event

                            // in case EOM (end-of-messsage) indicator has been reset by handler
                            if( ! global_ctx->sMessage.r_bEndOfMessage )
                            {
                                // return 'eScpiStatus_need_data' status instead of 'eScpiStatus_success'
                                // This provides another call to read the rest part of data
                                global_ctx->sEvent.eStatus = eScpiStatus_need_data;
                            }
                        }
                        break;
                        default: assert(false); // impossible code
                    }
                }
                break;

                // No output buffer is available
                case eScpiEventWrite:
                {
                    // "6.1.10.2.1 Message Available Message (MAV)", [1]
                    // .sRead structure is not initialized, but the handler respond that
                    // is there data to send.
                    // Force Set MAV bit
                    // "11.5.2.1 Function", [1]
                    // "<...> MAV becomes TRUE whenever data is ready and not necessarily in the Output Queue;  <...>"
                    GPIBMachine.fGPIB_set_message_available_bit( &global_ctx->sGPIB.registers, true );

                    // Handler status is 'eProgramDataNeedRead', so it is required to change next event code.
                    // Change event code for handler
                    private_ctx->commonCtx.event = eProgramData_Event_Read;
                }
                break;
            }
        }
        break;

        case eProgramDataNeedData:  // Check for: need data
        {
            // more data required
            // do not leave the state, keep @eScpiStatus_need_data status
            global_ctx->sEvent.eStatus = eScpiStatus_need_data;
            (void)private_ctx->commonCtx.event; // keep event code unchagned
        }
        break;

        case eProgramDataDone: // Check for: done-indicator
        {
            // Command completely done.
            
            // Check event code
            switch( global_ctx->sEvent.eCode )
            {
                // "6.1.10.2.1 Message Available Message (MAV)", [1]
                // .sRead structure is not initialized in *some* cases                
                case eScpiEventWrite:
                case eScpiEventRead:
                {
                    global_ctx->sMessage.r_bEndOfMessage = true; // assert read-event end-of-message indicator

                    // "6.1.10.2.1 Message Available Message (MAV)", [1]
                    scpi_UpdateMessageAvailable();
                }
                break;
            }

            // Fix: USBTMC does not allow empty transfer (transfer size equal to 0)
            // So, it is requried to send at least one byte if no response is available
            // after success code is returned.
            if( eScpiEventRead == global_ctx->sEvent.eCode )
            if( 0 == global_ctx->sRead.nTotalLength ) // transfer length
            if( 0 < global_ctx->sRead.nBufferSize )
            {
                 scpi_WriteCharOutput( ' ' );
            }

            private_ctx->commonCtx.event = eProgramData_Event_Done; // formal reset event code to 'Done'

            // set success status
            global_ctx->sEvent.eStatus = eScpiStatus_success;
            
            // Search for the following message unit separator
            // goto next state: eProcessEndOfProgramData
            nextstate = fsq_GetStateById(this,eProcessEndOfProgramData);
        }
        break;

        default: // error cases
        {
            // Specify the argument token to the last parsed argument token
            const sStrToken_t * pErroneousArgumentToken = &global_ctx->sParser.xArgToken;

            // Check if the erroneous argument index is set
            if( private_ctx->commonCtx.argErrIdx < SCPI_MAX_ARGS )
            // Check if index is in range of parsed arguments count
            if( private_ctx->commonCtx.argErrIdx < private_ctx->commonCtx.args )
            {
                // Handler has specified the particularly parsed token as erroneous
                pErroneousArgumentToken = &private_ctx->commonCtx.argTokens[ private_ctx->commonCtx.argErrIdx ];
            }

            switch( private_ctx->commonCtx.status )
            {
                case eProgramDataRuntimeError: // Check for: run-time error
                {
                    // Generate error message: common execution error
                    // "11.5.1.1.5 Bit 4 N Execution ERROR (E)", [2]
                    // "<...> the device shall continue parsing the input stream. <...>"
                    fsq_RaiseError( SCPI_ERROR_EXECUTION_ERROR, SCPI_ERROR_EXECUTION_ERROR_MSG, global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail );

                    // Note: Execution error shall not cause the parser stop operate,
                    // so, the error handler must process error code and pass the control to the
                    // 'endof_process_data' state to continue processing next command.
                }
                break;

                case eProgramDataSyntaxError: // Check for: syntax error
                {
                    // Generate error message
                    fsq_RaiseError( SCPI_ERROR_SYNTAX_ERROR, SCPI_ERROR_SYNTAX_ERROR_MSG, global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail  );
                }
                break;

                case eProgramDataArgumentSyntax:   // Check for: argument syntax error
                {
                    // Command handler reports about argument parsing error
                    // The specific error is already prepared in parser context
                    // Retrieve error details:
                    int32_t error; 
                    const char * pErrorMsg = getParserError( &(global_ctx->sParser.xCtxObj), &error );
                    
                    if( error != SCPI_ERROR_SUCCESS )
                    {
                        const void * shead;
                        const void * stail;
                        getParsedEntityDetails( &(global_ctx->sParser.xCtxObj), &shead, &stail, NULL );
                        
                        // Generate error message
                        fsq_RaiseErrorEx( error, pErrorMsg, 
                                          shead, stail, // character
                                          global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail // command
                        );
                    }
                }
                break;

                case eProgramDataIllegalArgument:    // Check for: illegal argument value
                { 
                    // Command handler reports about illegal argument value  
                    // Generate error message
                    fsq_RaiseErrorEx( SCPI_ERROR_ILLEGAL_PARAM, SCPI_ERROR_ILLEGAL_PARAM_MSG, 
                                          pErroneousArgumentToken->shead, pErroneousArgumentToken->stail, // character
                                          global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail // command
                    );
                }
                break;

                case eProgramDataArgumentType:    // Check for: argument type error
                case eProgramDataArgumentValue:   // Check for: argument value error
                {
                    // Command handler reports about argument value/range error
                    // The specific error must be generated here

                    // Generate error message
                    fsq_RaiseErrorEx( SCPI_ERROR_PARAMETER_ERROR, SCPI_ERROR_PARAMETER_ERROR_MSG, 
                                          pErroneousArgumentToken->shead, pErroneousArgumentToken->stail, // character
                                          global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail // command
                    );
                }
                break;

                case eProgramDataArgumentRange:   // Check for: argument value range error
                {
                    // Command handler reports about argument range error
                    // The specific error must be generated here

                    // Generate error message
                    fsq_RaiseErrorEx( SCPI_ERROR_DATA_RANGE, SCPI_ERROR_DATA_RANGE_MSG, 
                                          pErroneousArgumentToken->shead, pErroneousArgumentToken->stail, // character
                                          global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail // command
                    );
                }
                break;

                case eProgramData_SpecificError:  // Check for: specific error
                {
                    (void)fsq_RaiseError; // specific error message already generated by handler
                }
                break;

                default:
                {
                    // Generate error message (common error code)
                    fsq_RaiseError( SCPI_ERROR_COMMAND_ERROR, SCPI_ERROR_COMMAND_ERROR_MSG, global_ctx->sParser.xHandlerToken.shead, global_ctx->sParser.xHandlerToken.stail  );
                }
            }

            // formal reset event code to 'Done'
            private_ctx->commonCtx.event = eProgramData_Event_Done;

            // set failed status: see @fsql_ProcessProgramData        
            global_ctx->sEvent.eStatus = eScpiStatus_failed;
            
            // Error condition
            // goto next state: eHandleError
            nextstate = fsq_GetStateById(this,eHandleError);
        }
        break;
    }
 
L_fsqf_ProcessProgramData_EXIT:
    return nextstate;
}
