#include "iqi_fast_processing.h"
#include "iqi_comm.h"
#include "iqi_mst.h"

typedef enum 
{
    MST_ISDU_FAST_PROCESSING_NO_ISDU,
    MST_ISDU_FAST_PROCESSING_READ_ISDU,
    MST_ISDU_FAST_PROCESSING_WRITE_ISDU,
}fast_processing_NextIsduTypeE;

typedef struct
{
    uchar08 IsIsduResponseReady : 1;
    uchar08 IsCachedIsduAvailable : 1;
    uchar08 not_used : 6;
}FastProcessingStateT;

typedef union
{
    FastProcessingStateT State;
    uchar08 Byte;

} mst_FastProcessingStateU;

typedef struct
{
    //////////// ISDU FAST PROCESSING Response /////////////
    // byte 0 : ISDU FAST PROCESSING state
    // byte 1 : reserved
    // byte 2 : reserved
    // byte 3 : reserved
    // byte 4 : reserved
    // byte 5 : reserved
    // byte 6 - 7 : Index (MSB) - if response available
    // byte 8 : SubIndex - if response available
    // byte 9 : Od data length -  if response available
    // byte 10-(10+Od data length - 1) : Od data - if response available

    mst_FastProcessingStateU State;
    uint16 Index; //optional
    uchar08 Subindex; //optional
    uchar08 OdLength; //optional
    uchar08 OdValue[256]; //optional

} FastProcessingResponse;

static int16 parse_response(uchar08* buffer, uint16 bytes_read, FastProcessingResponse* response, char08* cpErrMsg);
static uint16 prepare_request(
    uchar08* buffer,
    fast_processing_NextIsduTypeE isdu_type,
    uint16 uiIndex,
    uchar08 ucSubindex,
    uchar08* isdu_buffer,
    uint16 isdu_buffer_length);

/**
 * \brief Activate fast processing
 *
 * \param[in]   uiPortNo        COM or virtual ethernet port number of iqInterface
 * \param[out]  cpErrMsg        an error message (minimum 256 symbols!)
 *
 * \return error code
 */
DLL_API
int16 fast_processing_Activate(
    uint16 uiPortNo,
    char08* cpErrMsg)
{
    return mst_SwitchISDUCachedMode(uiPortNo, MST_FAST_ISDU_PROCESSING_ENABLED, cpErrMsg);
}

/**
 * \brief Deactivate fast processing
 *
 * \param[in]   uiPortNo        COM or virtual ethernet port number of iqInterface
 * \param[out]  cpErrMsg        an error message (minimum 256 symbols!)
 *
 * \return error code
 */
DLL_API
int16 fast_processing_Deactivate(
    uint16 uiPortNo,
    char08* cpErrMsg)
{
    //wait, until all cached ISDU is transmitted
    mst_WaitODRsp(uiPortNo, cpErrMsg);
    return mst_SwitchISDUCachedMode(uiPortNo, MST_ISDU_CACHE_DISABLED, cpErrMsg);
}

/**
 * \brief Read on-request data
 *
 * Short hand for mst_StartReadOD() + mst_WaitODRsp() + mst_GetReadODRsp()
 *
 * \param[in]   uiPortNo        COM or virtual ethernet port number of iqInterface
 * \param[in]   uiIndex         an IO-Link parameter index
 * \param[in]   ucSubindex      an IO-Link parameter sub-index
 * \param[out]  ucpBuf          a data buffer
 * \param[in]   uiBufSize       a data buffer size
 * \param[out]  uipISDUErr      a pointer to store an ISDU error code
 * \param[out]  cpErrMsg        an error message (minimum 256 symbols!)
 *
 * \return the actual size of readed on-request data bytes
 */
DLL_API
int16 fast_processing_ReadOD(uint16 uiPortNo,
    uint16 uiIndex,
    uchar08 ucSubindex,
    uchar08* ucpBuf,
    uint16 uiBufSize,
    uint16* uipISDUErr,
    char08* cpErrMsg)
{
    int16 error_result = ERR_CUSTOM;

    do
    {
        uchar08 send_buffer[256] = {0};
        uint16 send_buffer_length = prepare_request(
                                        send_buffer, 
                                        MST_ISDU_FAST_PROCESSING_READ_ISDU, 
                                        uiIndex, 
                                        ucSubindex,
                                        0U, 0U);
        uchar08 recv_buffer[266] = { 0 };
        int16 bytes_read = comm_SendRecv(
                            uiPortNo, 
                            CH_IOL, 
                            CMD_FAST_PROCESSING_ISDU, 
                            send_buffer, 
                            send_buffer_length, 
                            recv_buffer, 
                            sizeof(recv_buffer), 
                            cpErrMsg);

        if (bytes_read < 0)
        {
            //error
            error_result = ERR_OPERATION;
            break;
        }
        else if (bytes_read == 0)
        {
            snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "On-request read data response size is zero.");
            error_result = ERR_INTERNAL;
            break;
        }
        else
        {
            FastProcessingResponse response;
            int16 error_response = parse_response(recv_buffer, bytes_read, &response, cpErrMsg);
            if (error_response < ERR_NONE)
            {
                error_result = error_response;
                break;
            }

            clock_t timeout = clock() + 5 * CLOCKS_PER_SEC;

            //wait for ISDU read response
            while ((response.State.State.IsIsduResponseReady == 0) && (timeout >= clock()))
            {
                // ISDU request type
                send_buffer[0] = MST_ISDU_FAST_PROCESSING_NO_ISDU; //just get a response
                send_buffer_length = 1;
                bytes_read = comm_SendRecv(uiPortNo, CH_IOL, CMD_FAST_PROCESSING_ISDU, send_buffer, send_buffer_length, recv_buffer, sizeof(recv_buffer), cpErrMsg);
                if (bytes_read < ERR_NONE)
                {
                    response.State.State.IsIsduResponseReady = 0;
                    error_result = bytes_read;
                    break;
                }
                error_response = parse_response(recv_buffer, bytes_read, &response, cpErrMsg);
                if (error_response < ERR_NONE)
                {
                    response.State.State.IsIsduResponseReady = 0;
                    error_result = error_response;
                    break;
                }
            }
            if (response.State.State.IsIsduResponseReady == 0)
            {
                if (timeout < clock())
                {
                    //timeout
                    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
                        "On-request read data response timeout");
                }
                break;
            }

            memcpy(ucpBuf, response.OdValue, response.OdLength);
            error_result = response.OdLength;
        }
    } while (0);
    return error_result;
}

/**
    * \brief Write on-request data
    *
    * Short hand for mst_StartWriteOD() + mst_WaitODRsp() + mst_GetWriteODRsp()
    *
    * \param[in]   uiPortNo        COM or virtual ethernet port number of iqInterface
    * \param[in]   uiIndex         an IO-Link parameter index
    * \param[in]   ucSubindex      an IO-Link parameter sub-index
    * \param[in]   ucpBuf          a data buffer
    * \param[in]   ucBytesCount    a count of bytes to send
    * \param[out]  uipISDUErr      a pointer to store an ISDU error code
    * \param[out]  cpErrMsg        an error message (minimum 256 symbols!)
    */
DLL_API
int16 fast_processing_WriteOD(uint16 uiPortNo,
    uint16 uiIndex,
    uchar08 ucSubindex,
    uchar08* ucpBuf,
    uint16 uiBytesCount,
    uint16* uipISDUErr,
    char08* cpErrMsg)
{
    int16 error_result = ERR_CUSTOM;

    do
    {
        uchar08 send_buffer[256] = { 0 };
        uint16 send_buffer_length = prepare_request(
            send_buffer,
            MST_ISDU_FAST_PROCESSING_WRITE_ISDU,
            uiIndex,
            ucSubindex,
            ucpBuf,
            uiBytesCount);
        uchar08 recv_buffer[266] = { 0 };
        int16 bytes_read = comm_SendRecv(
            uiPortNo,
            CH_IOL,
            CMD_FAST_PROCESSING_ISDU,
            send_buffer,
            send_buffer_length,
            recv_buffer,
            sizeof(recv_buffer),
            cpErrMsg);

        if (bytes_read < 0)
        {
            //error
            *uipISDUErr = GET_BIGEND_UINT16(recv_buffer);
            snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
                "On-request read data response fail (ISDU error '0x%04X').", *uipISDUErr);
            error_result = ERR_OPERATION;
            break;
        }
        else if (bytes_read == 0)
        {
            snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "On-request read data response size is zero.");
            error_result = ERR_INTERNAL;
            break;
        }
        else
        {
            FastProcessingResponse response;
            int16 error_response = parse_response(recv_buffer, bytes_read, &response, cpErrMsg);
            if (error_response < ERR_NONE)
            {
                error_result = error_response;
                break;
            }

            clock_t timeout = clock() + 5 * CLOCKS_PER_SEC;

            //wait until cache is free for next isdu request or timeout
            while ((response.State.State.IsCachedIsduAvailable == 1) && (timeout >= clock()))
            {
                // ISDU request type
                send_buffer[0] = MST_ISDU_FAST_PROCESSING_NO_ISDU; //just get a response
                send_buffer_length = 1;
                bytes_read = comm_SendRecv(uiPortNo, CH_IOL, CMD_FAST_PROCESSING_ISDU, send_buffer, send_buffer_length, recv_buffer, sizeof(recv_buffer), cpErrMsg);
                if (bytes_read < ERR_NONE)
                {
                    response.State.State.IsCachedIsduAvailable = 1;
                    error_result = bytes_read;
                    break;
                }
                error_response = parse_response(recv_buffer, bytes_read, &response, cpErrMsg);
                if (error_response < ERR_NONE)
                {
                    response.State.State.IsCachedIsduAvailable = 1;
                    error_result = error_response;
                    break;
                }
            }
            if (response.State.State.IsCachedIsduAvailable == 1)
            {
                if (timeout < clock())
                {
                    //timeout
                    snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN,
                        "On-request write data response timeout");
                }
                break;
            }

            error_result = ERR_NONE;
        }
    } while (0);
    return error_result;
}

/**
 * \brief Prepare request header
 *
 * \param[out]  buffer          input buffer, where header will be stored
 * \param[in]   isdu_type       ISDU type, see fast_processing_NextIsduTypeE
 * \param[in]   uiIndex         an IO-Link parameter index
 * \param[in]   ucSubindex      an IO-Link parameter sub-index
 * \param[out]  isdu_buffer     pointer to ISDU data (only for write request)
 * \param[in]   isdu_buffer_length Size of ISDU data (only for write request)
 *
 * \return actual header size
 */
uint16 prepare_request(
    uchar08* buffer, 
    fast_processing_NextIsduTypeE isdu_type, 
    uint16 uiIndex, 
    uchar08 ucSubindex, 
    uchar08* isdu_buffer, 
    uint16 isdu_buffer_length)
{
    //////////// ISDU FAST PROCESSING REQUEST /////////////
    // byte 0 : ISDU request type
    // byte 1 : reserved
    // byte 2 : reserved
    // byte 3 : reserved
    // byte 4 : reserved
    // byte 5 : reserved
    // byte 6 - 7 : Index (MSB)
    // byte 8 : SubIndex
    // byte 9 : Od data length - ISDU Write only
    // byte 10-(10+Od data length - 1) : Od data - ISDU Write only
     
    // ISDU request type
    buffer[0] = (uchar08)isdu_type;

    //reserved 5 bytes
    buffer[1] = 0;
    buffer[2] = 0;
    buffer[3] = 0;
    buffer[4] = 0;
    buffer[5] = 0;

    //ISDU to read
    buffer[6] = uiIndex & 0x00FF;        // add index low            
    buffer[7] = (uiIndex >> 8) & 0x00FF;        // add index high            
    buffer[8] = ucSubindex;                      // add subIndex

    uint16 buffer_length = 9;

    if (isdu_type == MST_ISDU_FAST_PROCESSING_WRITE_ISDU)
    {
        buffer[9] = (uchar08)isdu_buffer_length;
        buffer_length++;
        memcpy(&buffer[buffer_length], isdu_buffer, isdu_buffer_length);
        buffer_length += isdu_buffer_length;
    }
    return buffer_length;
}

/**
 * \brief Parse response into structure
 *
 * \param[in]  buffer          response from master
 * \param[in]  bytes_read      response size
 * \param[out] response        output response structure
 * \param[out]  cpErrMsg        an error message (minimum 256 symbols!) *
 * \return error code
 */
int16 parse_response(uchar08* buffer, uint16 bytes_read, FastProcessingResponse* response, char08* cpErrMsg)
{
    int16 error_result = ERR_CUSTOM;

    do
    {
        //byte 0 - skip

        response->State.Byte = buffer[1];

        if (response->State.State.IsIsduResponseReady)
        {
            if (bytes_read < 10)
            {
                snprintf(cpErrMsg, MAX_ERR_STR_LEN, MAX_ERR_STR_LEN, "Fast processing, invalid response data.");
                error_result = ERR_INTERNAL;
                break;
            }
            response->Index = GET_BIGEND_UINT16(buffer + 7);
            response->Subindex = buffer[9];
            response->OdLength = buffer[10];
            memcpy(response->OdValue, &buffer[11], response->OdLength);
        }
        error_result = ERR_NONE;
    } while (0);
    return error_result;
}
