/*
 * FreeRTOS V202212.00
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */


/**
 * @file comm_if_windows.c
 * @brief Windows Simulator file for cellular comm interface
 */

/*-----------------------------------------------------------*/

/* Windows include file for COM port I/O. */
#include <windows.h>

/* Platform layer includes. */
#include "cellular_platform.h"

/* Cellular comm interface include file. */
#include "cellular_config.h"
#include "cellular_config_defaults.h"
#include "cellular_comm_interface.h"

/*-----------------------------------------------------------*/

/* Define the COM port used as comm interface. */
#ifndef CELLULAR_COMM_INTERFACE_PORT
    #error "Define CELLULAR_COMM_INTERFACE_PORT in cellular_config.h"
#endif
#define CELLULAR_COMM_PATH                   "\\\\.\\"CELLULAR_COMM_INTERFACE_PORT

/* Define the simulated UART interrupt number. */
#define portINTERRUPT_UART                   ( 2UL )

/* Define the read write buffer size. */
#define COMM_TX_BUFFER_SIZE                  ( 8192 )
#define COMM_RX_BUFFER_SIZE                  ( 8192 )

/* Receive thread timeout in ms. */
#define COMM_RECV_THREAD_TIMEOUT             ( 5000 )

/* Write operation timeout in ms. */
#define COMM_WRITE_OPERATION_TIMEOUT         ( 500 )

/* Comm status. */
#define CELLULAR_COMM_OPEN_BIT               ( 0x01U )

/* Comm task event. */
#define COMMTASK_EVT_MASK_STARTED            ( 0x0001UL )
#define COMMTASK_EVT_MASK_ABORT              ( 0x0002UL )
#define COMMTASK_EVT_MASK_ABORTED            ( 0x0004UL )
#define COMMTASK_EVT_MASK_ALL_EVENTS \
    ( COMMTASK_EVT_MASK_STARTED      \
      | COMMTASK_EVT_MASK_ABORT      \
      | COMMTASK_EVT_MASK_ABORTED )
#define COMMTASK_POLLING_TIME_MS             ( 1UL )

/* Comm port event. */
#define COMMPORT_EVT_RXCHAR                  ( 0x0001UL )
#define COMMPORT_EVT_TXEMPTY                 ( 0x0002UL )

/* COMM_IF_REOPEN_DELAY. */
#define COMM_IF_REOPEN_DELAY                 ( 100U )

/* Platform thread stack size and priority. */
#define COMM_IF_THREAD_DEFAULT_STACK_SIZE    ( 2048U )
#define COMM_IF_THREAD_DEFAULT_PRIORITY      ( tskIDLE_PRIORITY + 5U )

/*-----------------------------------------------------------*/

typedef struct _cellularCommContext
{
    CellularCommInterfaceReceiveCallback_t commReceiveCallback;
    HANDLE commReceiveCallbackThread;
    uint8_t commStatus;
    void * pUserData;
    HANDLE commFileHandle;
    CellularCommInterface_t * pCommInterface;
    bool commTaskThreadStarted;
    EventGroupHandle_t pCommTaskEvent; /* For receive callback function. */
    EventGroupHandle_t pCommPortEvent; /* Notify RX TX events. */
} _cellularCommContext_t;

/*-----------------------------------------------------------*/

/**
 * @brief CellularCommInterfaceOpen_t implementation.
 */
static CellularCommInterfaceError_t _prvCommIntfOpen( CellularCommInterfaceReceiveCallback_t receiveCallback,
                                                      void * pUserData,
                                                      CellularCommInterfaceHandle_t * pCommInterfaceHandle );

/**
 * @brief CellularCommInterfaceSend_t implementation.
 */
static CellularCommInterfaceError_t _prvCommIntfSend( CellularCommInterfaceHandle_t commInterfaceHandle,
                                                      const uint8_t * pData,
                                                      uint32_t dataLength,
                                                      uint32_t timeoutMilliseconds,
                                                      uint32_t * pDataSentLength );

/**
 * @brief CellularCommInterfaceRecv_t implementation.
 */
static CellularCommInterfaceError_t _prvCommIntfReceive( CellularCommInterfaceHandle_t commInterfaceHandle,
                                                         uint8_t * pBuffer,
                                                         uint32_t bufferLength,
                                                         uint32_t timeoutMilliseconds,
                                                         uint32_t * pDataReceivedLength );

/**
 * @brief CellularCommInterfaceClose_t implementation.
 */
static CellularCommInterfaceError_t _prvCommIntfClose( CellularCommInterfaceHandle_t commInterfaceHandle );

/**
 * @brief Get default comm interface context.
 *
 * @return On success, SOCKETS_ERROR_NONE is returned. If an error occurred, error code defined
 * in sockets_wrapper.h is returned.
 */
static _cellularCommContext_t * _getCellularCommContext( void );

/**
 * @brief UART interrupt handler.
 *
 * @return pdTRUE if the operation is successful, otherwise
 * an error code indicating the cause of the error.
 */
static uint32_t prvProcessUartInt( void );

/**
 * @brief Set COM port timeout settings.
 *
 * @param[in] hComm COM handle returned by CreateFile.
 *
 * @return On success, IOT_COMM_INTERFACE_SUCCESS is returned. If an error occurred, error code defined
 * in CellularCommInterfaceError_t is returned.
 */
static CellularCommInterfaceError_t _setupCommTimeout( HANDLE hComm );

/**
 * @brief Set COM port control settings.
 *
 * @param[in] hComm COM handle returned by CreateFile.
 *
 * @return On success, IOT_COMM_INTERFACE_SUCCESS is returned. If an error occurred, error code defined
 * in CellularCommInterfaceError_t is returned.
 */
static CellularCommInterfaceError_t _setupCommSettings( HANDLE hComm );

/**
 * @brief Thread routine to generate simulated interrupt.
 *
 * @param[in] pUserData Pointer to _cellularCommContext_t allocated in comm interface open.
 */
static void commTaskThread( void * pUserData );

/**
 * @brief Helper function to setup and create commTaskThread.
 *
 * @param[in] pCellularCommContext Cellular comm interface context allocated in open.
 *
 * @return On success, IOT_COMM_INTERFACE_SUCCESS is returned. If an error occurred, error code defined
 * in CellularCommInterfaceError_t is returned.
 */
static CellularCommInterfaceError_t setupCommTaskThread( _cellularCommContext_t * pCellularCommContext );

/**
 * @brief Helper function to clean commTaskThread.
 *
 * @param[in] pCellularCommContext Cellular comm interface context allocated in open.
 *
 * @return On success, IOT_COMM_INTERFACE_SUCCESS is returned. If an error occurred, error code defined
 * in CellularCommInterfaceError_t is returned.
 */
static CellularCommInterfaceError_t cleanCommTaskThread( _cellularCommContext_t * pCellularCommContext );

/*-----------------------------------------------------------*/

CellularCommInterface_t CellularCommInterface =
{
    .open  = _prvCommIntfOpen,
    .send  = _prvCommIntfSend,
    .recv  = _prvCommIntfReceive,
    .close = _prvCommIntfClose
};

static _cellularCommContext_t _iotCellularCommContext =
{
    .commReceiveCallback       = NULL,
    .commReceiveCallbackThread = NULL,
    .pCommInterface            = &CellularCommInterface,
    .commFileHandle            = NULL,
    .pUserData                 = NULL,
    .commStatus                = 0U,
    .commTaskThreadStarted     = false,
    .pCommTaskEvent            = NULL,
    .pCommPortEvent            = NULL
};

/* Indicate RX event is received in comm driver. */
static bool rxEvent = false;

static bool txEmptyEvent = false;

/*-----------------------------------------------------------*/

static _cellularCommContext_t * _getCellularCommContext( void )
{
    return &_iotCellularCommContext;
}

/*-----------------------------------------------------------*/

static uint32_t prvProcessUartInt( void )
{
    _cellularCommContext_t * pCellularCommContext = _getCellularCommContext();
    CellularCommInterfaceError_t callbackRet = IOT_COMM_INTERFACE_FAILURE;
    uint32_t retUartInt = pdTRUE;

    if( pCellularCommContext->commReceiveCallback != NULL )
    {
        callbackRet = pCellularCommContext->commReceiveCallback( pCellularCommContext->pUserData,
                                                                 ( CellularCommInterfaceHandle_t ) pCellularCommContext );
    }

    if( callbackRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        retUartInt = pdTRUE;
    }
    else
    {
        retUartInt = pdFALSE;
    }

    return retUartInt;
}

/*-----------------------------------------------------------*/

/**
 * @brief Communication receiver thread function.
 *
 * @param[in] pArgument windows COM port handle.
 * @return 0 if thread function exit without error. Others for error.
 */
DWORD WINAPI _CellularCommReceiveCBThreadFunc( LPVOID pArgument )
{
    DWORD dwCommStatus = 0;
    HANDLE hComm = ( HANDLE ) pArgument;
    BOOL retWait = FALSE;
    DWORD retValue = 0;
    _cellularCommContext_t * pCellularCommContext = _getCellularCommContext();

    if( hComm == ( HANDLE ) INVALID_HANDLE_VALUE )
    {
        retValue = ERROR_INVALID_HANDLE;
    }

    while( retValue == 0 )
    {
        retWait = WaitCommEvent( hComm, &dwCommStatus, NULL );

        if( ( retWait != FALSE ) && ( ( dwCommStatus & ( EV_RXCHAR | EV_TXEMPTY ) ) != 0 ) )
        {
            if( ( dwCommStatus & EV_RXCHAR ) != 0 )
            {
                /* The RXECHAR event. */
                rxEvent = true;
            }

            if( ( dwCommStatus & EV_TXEMPTY ) != 0 )
            {
                /* The TXEMPTY event. */
                txEmptyEvent = true;
            }
        }
        else
        {
            if( ( GetLastError() == ERROR_INVALID_HANDLE ) || ( GetLastError() == ERROR_OPERATION_ABORTED ) )
            {
                /* COM port closed. */
                LogInfo( ( "Cellular COM port %p closed", hComm ) );
            }
            else
            {
                LogInfo( ( "Cellular receiver thread wait comm error %p %d", hComm, GetLastError() ) );
            }

            retValue = GetLastError();

            break;
        }
    }

    return retValue;
}

/*-----------------------------------------------------------*/

static CellularCommInterfaceError_t _setupCommTimeout( HANDLE hComm )
{
    CellularCommInterfaceError_t commIntRet = IOT_COMM_INTERFACE_SUCCESS;
    COMMTIMEOUTS xCommTimeouts = { 0 };
    BOOL Status = TRUE;

    /* Set ReadIntervalTimeout to MAXDWORD and zero values for both
     * ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier to return
     * immediately with the bytes that already been received. */
    xCommTimeouts.ReadIntervalTimeout = MAXDWORD;
    xCommTimeouts.ReadTotalTimeoutConstant = 0;
    xCommTimeouts.ReadTotalTimeoutMultiplier = 0;
    xCommTimeouts.WriteTotalTimeoutConstant = COMM_WRITE_OPERATION_TIMEOUT;
    xCommTimeouts.WriteTotalTimeoutMultiplier = 0;
    Status = SetCommTimeouts( hComm, &xCommTimeouts );

    if( Status == FALSE )
    {
        LogError( ( "Cellular SetCommTimeouts fail %d", GetLastError() ) );
        commIntRet = IOT_COMM_INTERFACE_FAILURE;
    }

    return commIntRet;
}

/*-----------------------------------------------------------*/

static CellularCommInterfaceError_t _setupCommSettings( HANDLE hComm )
{
    CellularCommInterfaceError_t commIntRet = IOT_COMM_INTERFACE_SUCCESS;
    DCB dcbSerialParams = { 0 };
    BOOL Status = TRUE;

    ( void ) memset( &dcbSerialParams, 0, sizeof( dcbSerialParams ) );
    dcbSerialParams.DCBlength = sizeof( dcbSerialParams );
    dcbSerialParams.BaudRate = CBR_115200;
    dcbSerialParams.fBinary = 1;
    dcbSerialParams.ByteSize = 8;
    dcbSerialParams.StopBits = ONESTOPBIT;
    dcbSerialParams.Parity = NOPARITY;

    dcbSerialParams.fOutxCtsFlow = FALSE;
    dcbSerialParams.fOutxDsrFlow = FALSE;
    dcbSerialParams.fDtrControl = DTR_CONTROL_ENABLE;
    dcbSerialParams.fRtsControl = RTS_CONTROL_ENABLE;

    Status = SetCommState( hComm, &dcbSerialParams );

    if( Status == FALSE )
    {
        LogError( ( "Cellular SetCommState fail %d", GetLastError() ) );
        commIntRet = IOT_COMM_INTERFACE_FAILURE;
    }

    return commIntRet;
}

/*-----------------------------------------------------------*/

static void commTaskThread( void * pUserData )
{
    _cellularCommContext_t * pCellularCommContext = ( _cellularCommContext_t * ) pUserData;
    EventBits_t uxBits = 0;

    /* Inform thread ready. */
    LogInfo( ( "Cellular commTaskThread started" ) );

    if( pCellularCommContext != NULL )
    {
        ( void ) xEventGroupSetBits( pCellularCommContext->pCommTaskEvent,
                                     COMMTASK_EVT_MASK_STARTED );
    }

    while( true )
    {
        /* Wait for notification from eventqueue. */
        uxBits = xEventGroupWaitBits( ( pCellularCommContext->pCommTaskEvent ),
                                      ( ( EventBits_t ) COMMTASK_EVT_MASK_ABORT ),
                                      pdTRUE,
                                      pdFALSE,
                                      pdMS_TO_TICKS( COMMTASK_POLLING_TIME_MS ) );

        if( ( uxBits & ( EventBits_t ) COMMTASK_EVT_MASK_ABORT ) != 0U )
        {
            LogDebug( ( "Abort received, cleaning up!" ) );
            break;
        }
        else
        {
            /* Polling the global share variable to trigger the interrupt. */
            if( rxEvent == true )
            {
                rxEvent = false;
                vPortGenerateSimulatedInterrupt( portINTERRUPT_UART );
                ( void ) xEventGroupSetBits( pCellularCommContext->pCommPortEvent,
                                             COMMPORT_EVT_RXCHAR );
            }

            if( txEmptyEvent == true )
            {
                txEmptyEvent = false;
                ( void ) xEventGroupSetBits( pCellularCommContext->pCommPortEvent,
                                             COMMPORT_EVT_TXEMPTY );
            }
        }
    }

    /* Inform thread ready. */
    if( pCellularCommContext != NULL )
    {
        ( void ) xEventGroupSetBits( pCellularCommContext->pCommTaskEvent, COMMTASK_EVT_MASK_ABORTED );
    }

    LogInfo( ( "Cellular commTaskThread exit" ) );
}

/*-----------------------------------------------------------*/

static CellularCommInterfaceError_t setupCommTaskThread( _cellularCommContext_t * pCellularCommContext )
{
    BOOL Status = TRUE;
    EventBits_t uxBits = 0;
    CellularCommInterfaceError_t commIntRet = IOT_COMM_INTERFACE_SUCCESS;

    pCellularCommContext->pCommTaskEvent = xEventGroupCreate();

    if( pCellularCommContext->pCommTaskEvent != NULL )
    {
        /* Create the FreeRTOS thread to generate the simulated interrupt. */
        Status = Platform_CreateDetachedThread( commTaskThread,
                                                ( void * ) pCellularCommContext,
                                                COMM_IF_THREAD_DEFAULT_PRIORITY,
                                                COMM_IF_THREAD_DEFAULT_STACK_SIZE );

        if( Status != true )
        {
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }
    }
    else
    {
        commIntRet = IOT_COMM_INTERFACE_FAILURE;
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        uxBits = xEventGroupWaitBits( ( pCellularCommContext->pCommTaskEvent ),
                                      ( ( EventBits_t ) COMMTASK_EVT_MASK_STARTED | ( EventBits_t ) COMMTASK_EVT_MASK_ABORTED ),
                                      pdTRUE,
                                      pdFALSE,
                                      portMAX_DELAY );

        if( ( uxBits & ( EventBits_t ) COMMTASK_EVT_MASK_STARTED ) == COMMTASK_EVT_MASK_STARTED )
        {
            pCellularCommContext->commTaskThreadStarted = true;
        }
        else
        {
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
            pCellularCommContext->commTaskThreadStarted = false;
        }
    }

    return commIntRet;
}

/*-----------------------------------------------------------*/

static CellularCommInterfaceError_t cleanCommTaskThread( _cellularCommContext_t * pCellularCommContext )
{
    EventBits_t uxBits = 0;
    CellularCommInterfaceError_t commIntRet = IOT_COMM_INTERFACE_SUCCESS;

    /* Wait for the commTaskThreadStarted exit. */
    if( ( pCellularCommContext->commTaskThreadStarted == true ) && ( pCellularCommContext->pCommTaskEvent != NULL ) )
    {
        ( void ) xEventGroupSetBits( pCellularCommContext->pCommTaskEvent,
                                     COMMTASK_EVT_MASK_ABORT );
        uxBits = xEventGroupWaitBits( ( pCellularCommContext->pCommTaskEvent ),
                                      ( ( EventBits_t ) COMMTASK_EVT_MASK_ABORTED ),
                                      pdTRUE,
                                      pdFALSE,
                                      portMAX_DELAY );

        if( ( uxBits & ( EventBits_t ) COMMTASK_EVT_MASK_ABORTED ) != COMMTASK_EVT_MASK_ABORTED )
        {
            LogDebug( ( "Cellular close wait commTaskThread fail" ) );
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }

        pCellularCommContext->commTaskThreadStarted = false;
    }

    /* Clean the event group. */
    if( pCellularCommContext->pCommTaskEvent != NULL )
    {
        vEventGroupDelete( pCellularCommContext->pCommTaskEvent );
        pCellularCommContext->pCommTaskEvent = NULL;
    }

    return commIntRet;
}

/*-----------------------------------------------------------*/

static CellularCommInterfaceError_t _prvCommIntfOpen( CellularCommInterfaceReceiveCallback_t receiveCallback,
                                                      void * pUserData,
                                                      CellularCommInterfaceHandle_t * pCommInterfaceHandle )
{
    CellularCommInterfaceError_t commIntRet = IOT_COMM_INTERFACE_SUCCESS;
    HANDLE hComm = ( HANDLE ) INVALID_HANDLE_VALUE;
    BOOL Status = TRUE;
    _cellularCommContext_t * pCellularCommContext = _getCellularCommContext();
    DWORD dwRes = 0;

    if( pCommInterfaceHandle == NULL )
    {
        LogError( ( "Cellular comm pCommInterfaceHandle invalid" ) );
        commIntRet = IOT_COMM_INTERFACE_BAD_PARAMETER;
    }
    else if( ( pCellularCommContext->commStatus & CELLULAR_COMM_OPEN_BIT ) != 0 )
    {
        LogError( ( "Cellular comm interface opened already" ) );
        commIntRet = IOT_COMM_INTERFACE_FAILURE;
    }
    else
    {
        /* Clear the context. */
        memset( pCellularCommContext, 0, sizeof( _cellularCommContext_t ) );
        pCellularCommContext->pCommInterface = &CellularCommInterface;

        /* If CreateFile fails, the return value is INVALID_HANDLE_VALUE. */
        hComm = CreateFile( TEXT( CELLULAR_COMM_PATH ),
                            GENERIC_READ | GENERIC_WRITE,
                            0,
                            NULL,
                            OPEN_EXISTING,
                            FILE_FLAG_OVERLAPPED,
                            NULL );
    }

    /* Comm port is just closed. Wait 1 second and retry. */
    if( ( hComm == ( HANDLE ) INVALID_HANDLE_VALUE ) && ( GetLastError() == 5 ) )
    {
        vTaskDelay( pdMS_TO_TICKS( COMM_IF_REOPEN_DELAY ) );
        hComm = CreateFile( TEXT( CELLULAR_COMM_PATH ),
                            GENERIC_READ | GENERIC_WRITE,
                            0,
                            NULL,
                            OPEN_EXISTING,
                            FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH,
                            NULL );
    }

    if( hComm == ( HANDLE ) INVALID_HANDLE_VALUE )
    {
        LogError( ( "Cellular open COM port fail %d", GetLastError() ) );
        commIntRet = IOT_COMM_INTERFACE_FAILURE;
    }
    else
    {
        Status = SetupComm( hComm, COMM_TX_BUFFER_SIZE, COMM_RX_BUFFER_SIZE );

        if( Status == FALSE )
        {
            LogError( ( "Cellular setup COM port fail %d", GetLastError() ) );
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        commIntRet = _setupCommTimeout( hComm );
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        commIntRet = _setupCommSettings( hComm );
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        Status = SetCommMask( hComm, EV_RXCHAR | EV_TXEMPTY );

        if( Status == FALSE )
        {
            LogError( ( "Cellular SetCommMask fail %d", GetLastError() ) );
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        pCellularCommContext->pCommPortEvent = xEventGroupCreate();

        if( pCellularCommContext->pCommPortEvent == NULL )
        {
            LogError( ( "Cellular SetCommMask fail %d", GetLastError() ) );
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        pCellularCommContext->commReceiveCallback = receiveCallback;
        commIntRet = setupCommTaskThread( pCellularCommContext );
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        vPortSetInterruptHandler( portINTERRUPT_UART, prvProcessUartInt );
        pCellularCommContext->commReceiveCallbackThread =
            CreateThread( NULL, 0, _CellularCommReceiveCBThreadFunc, hComm, 0, NULL );

        /* CreateThread return NULL for error. */
        if( pCellularCommContext->commReceiveCallbackThread == NULL )
        {
            LogError( ( "Cellular CreateThread fail %d", GetLastError() ) );
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        pCellularCommContext->pUserData = pUserData;
        pCellularCommContext->commFileHandle = hComm;
        *pCommInterfaceHandle = ( CellularCommInterfaceHandle_t ) pCellularCommContext;
        pCellularCommContext->commStatus |= CELLULAR_COMM_OPEN_BIT;
    }
    else
    {
        /* Comm interface open fail. Clean the data. */
        if( hComm != ( HANDLE ) INVALID_HANDLE_VALUE )
        {
            ( void ) CloseHandle( hComm );
            hComm = INVALID_HANDLE_VALUE;
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }

        /* Wait for the commReceiveCallbackThread exit. */
        if( pCellularCommContext->commReceiveCallbackThread != NULL )
        {
            dwRes = WaitForSingleObject( pCellularCommContext->commReceiveCallbackThread, COMM_RECV_THREAD_TIMEOUT );

            if( dwRes != WAIT_OBJECT_0 )
            {
                LogDebug( ( "Cellular close wait receiveCallbackThread %p fail %d",
                            pCellularCommContext->commReceiveCallbackThread, dwRes ) );
            }
        }

        pCellularCommContext->commReceiveCallbackThread = NULL;

        /* Clean the com port event group. */
        if( pCellularCommContext->pCommPortEvent != NULL )
        {
            vEventGroupDelete( pCellularCommContext->pCommPortEvent );
            pCellularCommContext->pCommPortEvent = NULL;
        }

        /* Wait for the commTaskThreadStarted exit. */
        ( void ) cleanCommTaskThread( pCellularCommContext );
    }

    return commIntRet;
}

/*-----------------------------------------------------------*/

static CellularCommInterfaceError_t _prvCommIntfClose( CellularCommInterfaceHandle_t commInterfaceHandle )
{
    CellularCommInterfaceError_t commIntRet = IOT_COMM_INTERFACE_SUCCESS;
    _cellularCommContext_t * pCellularCommContext = ( _cellularCommContext_t * ) commInterfaceHandle;
    HANDLE hComm = NULL;
    BOOL Status = TRUE;
    DWORD dwRes = 0;

    if( pCellularCommContext == NULL )
    {
        LogError( ( "Cellular close context is NULL" ) );
        commIntRet = IOT_COMM_INTERFACE_FAILURE;
    }
    else if( ( pCellularCommContext->commStatus & CELLULAR_COMM_OPEN_BIT ) == 0 )
    {
        LogError( ( "Cellular close comm interface is not opened before." ) );
        commIntRet = IOT_COMM_INTERFACE_FAILURE;
    }
    else
    {
        /* clean the receive callback. */
        pCellularCommContext->commReceiveCallback = NULL;

        /* Close the COM port. */
        hComm = pCellularCommContext->commFileHandle;

        if( hComm != ( HANDLE ) INVALID_HANDLE_VALUE )
        {
            Status = CloseHandle( hComm );

            if( Status == FALSE )
            {
                LogError( ( "Cellular close CloseHandle %p fail", hComm ) );
                commIntRet = IOT_COMM_INTERFACE_FAILURE;
            }
        }
        else
        {
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }

        pCellularCommContext->commFileHandle = NULL;

        /* Wait for the thread exit. */
        if( pCellularCommContext->commReceiveCallbackThread != NULL )
        {
            dwRes = WaitForSingleObject( pCellularCommContext->commReceiveCallbackThread, COMM_RECV_THREAD_TIMEOUT );

            if( dwRes != WAIT_OBJECT_0 )
            {
                LogError( ( "Cellular close wait receiveCallbackThread %p fail %d",
                            pCellularCommContext->commReceiveCallbackThread, dwRes ) );
                commIntRet = IOT_COMM_INTERFACE_FAILURE;
            }
            else
            {
                CloseHandle( pCellularCommContext->commReceiveCallbackThread );
            }
        }

        pCellularCommContext->commReceiveCallbackThread = NULL;

        /* Clean the com port event group. */
        if( pCellularCommContext->pCommPortEvent != NULL )
        {
            vEventGroupDelete( pCellularCommContext->pCommPortEvent );
            pCellularCommContext->pCommPortEvent = NULL;
        }

        /* Clean the commTaskThread. */
        ( void ) cleanCommTaskThread( pCellularCommContext );

        /* clean the data structure. */
        pCellularCommContext->commStatus &= ~( CELLULAR_COMM_OPEN_BIT );
    }

    return commIntRet;
}

/*-----------------------------------------------------------*/

static CellularCommInterfaceError_t _prvCommIntfSend( CellularCommInterfaceHandle_t commInterfaceHandle,
                                                      const uint8_t * pData,
                                                      uint32_t dataLength,
                                                      uint32_t timeoutMilliseconds,
                                                      uint32_t * pDataSentLength )
{
    CellularCommInterfaceError_t commIntRet = IOT_COMM_INTERFACE_SUCCESS;
    _cellularCommContext_t * pCellularCommContext = ( _cellularCommContext_t * ) commInterfaceHandle;
    HANDLE hComm = NULL;
    OVERLAPPED osWrite = { 0 };
    DWORD dwRes = 0;
    DWORD dwWritten = 0;
    BOOL Status = TRUE;
    EventBits_t uxBits = 0;

    if( pCellularCommContext == NULL )
    {
        LogError( ( "Cellular send comm interface handle invalid." ) );
        commIntRet = IOT_COMM_INTERFACE_BAD_PARAMETER;
    }
    else if( ( pData == NULL ) || ( dataLength == 0 ) )
    {
        LogError( ( "Cellular send pData or dataLength invalid." ) );
        commIntRet = IOT_COMM_INTERFACE_BAD_PARAMETER;
    }
    else if( pDataSentLength == NULL )
    {
        LogError( ( "Cellular send pDataSentLength invalid." ) );
        commIntRet = IOT_COMM_INTERFACE_BAD_PARAMETER;
    }
    else if( ( pCellularCommContext->commStatus & CELLULAR_COMM_OPEN_BIT ) == 0 )
    {
        LogError( ( "Cellular send comm interface is not opened before." ) );
        commIntRet = IOT_COMM_INTERFACE_FAILURE;
    }
    else
    {
        hComm = pCellularCommContext->commFileHandle;
        osWrite.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

        if( osWrite.hEvent == NULL )
        {
            LogError( ( "Cellular CreateEvent fail %d", GetLastError() ) );
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        Status = WriteFile( hComm, pData, dataLength, &dwWritten, &osWrite );

        if( Status == TRUE )
        {
            /* Waiting for TX empty. */
            *pDataSentLength = ( uint32_t ) dwWritten;

            /* Wait for notification from eventqueue. */
            uxBits = xEventGroupWaitBits( ( pCellularCommContext->pCommPortEvent ),
                                          ( ( EventBits_t ) COMMPORT_EVT_TXEMPTY ),
                                          pdTRUE,
                                          pdFALSE,
                                          pdMS_TO_TICKS( timeoutMilliseconds ) );

            if( ( uxBits & COMMPORT_EVT_TXEMPTY ) == 0 )
            {
                LogError( ( "Cellular WriteFile fail timeout" ) );
                commIntRet = IOT_COMM_INTERFACE_TIMEOUT;
            }
        }
        else
        {
            /* WriteFile fail and error is not the ERROR_IO_PENDING. */
            if( GetLastError() != ERROR_IO_PENDING )
            {
                LogError( ( "Cellular WriteFile fail %d", GetLastError() ) );
                commIntRet = IOT_COMM_INTERFACE_FAILURE;
            }
        }
    }

    /* Handle pending I/O. */
    if( ( commIntRet == IOT_COMM_INTERFACE_SUCCESS ) && ( Status == FALSE ) )
    {
        dwRes = WaitForSingleObject( osWrite.hEvent, timeoutMilliseconds );

        switch( dwRes )
        {
            case WAIT_OBJECT_0:

                if( GetOverlappedResult( hComm, &osWrite, &dwWritten, FALSE ) == FALSE )
                {
                    LogError( ( "Cellular GetOverlappedResult fail %d", GetLastError() ) );
                    commIntRet = IOT_COMM_INTERFACE_FAILURE;
                }

                break;

            case STATUS_TIMEOUT:
                LogError( ( "Cellular WaitForSingleObject timeout" ) );
                commIntRet = IOT_COMM_INTERFACE_TIMEOUT;
                break;

            default:
                LogError( ( "Cellular WaitForSingleObject fail %d", dwRes ) );
                commIntRet = IOT_COMM_INTERFACE_FAILURE;
                break;
        }

        *pDataSentLength = ( uint32_t ) dwWritten;
    }

    if( osWrite.hEvent != NULL )
    {
        Status = CloseHandle( osWrite.hEvent );

        if( Status == FALSE )
        {
            LogDebug( ( "Cellular send CloseHandle fail" ) );
        }
    }

    return commIntRet;
}

/*-----------------------------------------------------------*/

static CellularCommInterfaceError_t _prvCommIntfReceive( CellularCommInterfaceHandle_t commInterfaceHandle,
                                                         uint8_t * pBuffer,
                                                         uint32_t bufferLength,
                                                         uint32_t timeoutMilliseconds,
                                                         uint32_t * pDataReceivedLength )
{
    CellularCommInterfaceError_t commIntRet = IOT_COMM_INTERFACE_SUCCESS;
    _cellularCommContext_t * pCellularCommContext = ( _cellularCommContext_t * ) commInterfaceHandle;
    HANDLE hComm = NULL;
    OVERLAPPED osRead = { 0 };
    BOOL Status = TRUE;
    DWORD dwRes = 0;
    DWORD dwRead = 0;
    EventBits_t uxBits = 0;

    if( pCellularCommContext == NULL )
    {
        LogError( ( "Cellular receive comm interface handle invalid." ) );
        commIntRet = IOT_COMM_INTERFACE_BAD_PARAMETER;
    }
    else if( ( pBuffer == NULL ) || ( bufferLength == 0 ) )
    {
        LogError( ( "Cellular receive pBuffer or bufferLength invalid." ) );
        commIntRet = IOT_COMM_INTERFACE_BAD_PARAMETER;
    }
    else if( pDataReceivedLength == NULL )
    {
        LogError( ( "Cellular receive pDataReceivedLength invalid." ) );
        commIntRet = IOT_COMM_INTERFACE_BAD_PARAMETER;
    }
    else if( ( pCellularCommContext->commStatus & CELLULAR_COMM_OPEN_BIT ) == 0 )
    {
        LogError( ( "Cellular read comm interface is not opened before." ) );
        commIntRet = IOT_COMM_INTERFACE_FAILURE;
    }
    else
    {
        hComm = pCellularCommContext->commFileHandle;
        osRead.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

        if( osRead.hEvent == NULL )
        {
            LogError( ( "Cellular CreateEvent fail %d", GetLastError() ) );
            commIntRet = IOT_COMM_INTERFACE_FAILURE;
        }
    }

    if( commIntRet == IOT_COMM_INTERFACE_SUCCESS )
    {
        Status = ReadFile( hComm, pBuffer, bufferLength, &dwRead, &osRead );

        if( ( Status == TRUE ) && ( dwRead == 0 ) )
        {
            /* Wait for notification from eventqueue. */
            uxBits = xEventGroupWaitBits( ( pCellularCommContext->pCommPortEvent ),
                                          ( ( EventBits_t ) COMMPORT_EVT_RXCHAR ),
                                          pdTRUE,
                                          pdFALSE,
                                          pdMS_TO_TICKS( timeoutMilliseconds ) );

            if( ( uxBits & COMMPORT_EVT_RXCHAR ) == 0 )
            {
                LogDebug( ( "Cellular ReadFile timeout" ) );
                commIntRet = IOT_COMM_INTERFACE_TIMEOUT;
            }
            else
            {
                Status = ReadFile( hComm, pBuffer, bufferLength, &dwRead, &osRead );
            }
        }

        if( Status == TRUE )
        {
            *pDataReceivedLength = ( uint32_t ) dwRead;
        }
        else
        {
            if( GetLastError() != ERROR_IO_PENDING )
            {
                LogError( ( "Cellular ReadFile fail %d", GetLastError() ) );
                commIntRet = IOT_COMM_INTERFACE_FAILURE;
            }
        }
    }

    /* Handle pending I/O. */
    if( ( commIntRet == IOT_COMM_INTERFACE_SUCCESS ) && ( Status == FALSE ) )
    {
        dwRes = WaitForSingleObject( osRead.hEvent, timeoutMilliseconds );

        switch( dwRes )
        {
            case WAIT_OBJECT_0:

                if( GetOverlappedResult( hComm, &osRead, &dwRead, FALSE ) == FALSE )
                {
                    LogError( ( "Cellular receive GetOverlappedResult fail %d", GetLastError() ) );
                    commIntRet = IOT_COMM_INTERFACE_FAILURE;
                }

                break;

            case STATUS_TIMEOUT:
                LogError( ( "Cellular receive WaitForSingleObject timeout" ) );
                commIntRet = IOT_COMM_INTERFACE_TIMEOUT;
                break;

            default:
                LogError( ( "Cellular receive WaitForSingleObject fail %d", dwRes ) );
                commIntRet = IOT_COMM_INTERFACE_FAILURE;
                break;
        }

        *pDataReceivedLength = ( uint32_t ) dwRead;
    }

    if( osRead.hEvent != NULL )
    {
        Status = CloseHandle( osRead.hEvent );

        if( Status == FALSE )
        {
            LogDebug( ( "Cellular recv CloseHandle fail" ) );
        }
    }

    return commIntRet;
}

/*-----------------------------------------------------------*/
