/*
 * AWS IoT Over-the-air Update v3.4.0
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * 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.
 */

/**
 * @file ota_os_freertos.c
 * @brief Example implementation of the OTA OS Functional Interface for
 * FreeRTOS.
 */

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "timers.h"
#include "queue.h"

/* OTA OS POSIX Interface Includes.*/
#include "ota_os_freertos.h"

/* OTA Library include. */
#include "ota.h"
#include "ota_private.h"

/* OTA Event queue attributes.*/
#define MAX_MESSAGES    20
#define MAX_MSG_SIZE    sizeof( OtaEventMsg_t )

/* Array containing pointer to the OTA event structures used to send events to the OTA task. */
static OtaEventMsg_t queueData[ MAX_MESSAGES * MAX_MSG_SIZE ];

/* The queue control structure.  .*/
static StaticQueue_t staticQueue;

/* The queue control handle.  .*/
static QueueHandle_t otaEventQueue;

/* OTA App Timer callback.*/
static OtaTimerCallback_t otaTimerCallbackPtr;

/* OTA Timer handles.*/
static TimerHandle_t otaTimer[ OtaNumOfTimers ];

/* OTA Timer callbacks.*/
static void requestTimerCallback( TimerHandle_t T );
static void selfTestTimerCallback( TimerHandle_t T );
void ( * timerCallback[ OtaNumOfTimers ] )( TimerHandle_t T ) = { requestTimerCallback, selfTestTimerCallback };

OtaOsStatus_t OtaInitEvent_FreeRTOS( OtaEventContext_t * pEventCtx )
{
    OtaOsStatus_t otaOsStatus = OtaOsSuccess;

    ( void ) pEventCtx;

    otaEventQueue = xQueueCreateStatic( ( UBaseType_t ) MAX_MESSAGES,
                                        ( UBaseType_t ) MAX_MSG_SIZE,
                                        ( uint8_t * ) queueData,
                                        &staticQueue );

    if( otaEventQueue == NULL )
    {
        otaOsStatus = OtaOsEventQueueCreateFailed;

        LogError( ( "Failed to create OTA Event Queue: "
                    "xQueueCreateStatic returned error: "
                    "OtaOsStatus_t=%d ",
                    ( int ) otaOsStatus ) );
    }
    else
    {
        LogDebug( ( "OTA Event Queue created." ) );
    }

    return otaOsStatus;
}

OtaOsStatus_t OtaSendEvent_FreeRTOS( OtaEventContext_t * pEventCtx,
                                     const void * pEventMsg,
                                     unsigned int timeout )
{
    OtaOsStatus_t otaOsStatus = OtaOsSuccess;
    BaseType_t retVal = pdFALSE;

    ( void ) pEventCtx;
    ( void ) timeout;

    /* Send the event to OTA event queue.*/
    retVal = xQueueSendToBack( otaEventQueue, pEventMsg, ( TickType_t ) 0 );

    if( retVal == pdTRUE )
    {
        LogDebug( ( "OTA Event Sent." ) );
    }
    else
    {
        otaOsStatus = OtaOsEventQueueSendFailed;

        LogError( ( "Failed to send event to OTA Event Queue: "
                    "xQueueSendToBack returned error: "
                    "OtaOsStatus_t=%d ",
                    ( int ) otaOsStatus ) );
    }

    return otaOsStatus;
}

OtaOsStatus_t OtaReceiveEvent_FreeRTOS( OtaEventContext_t * pEventCtx,
                                        void * pEventMsg,
                                        uint32_t timeout )
{
    OtaOsStatus_t otaOsStatus = OtaOsSuccess;
    BaseType_t retVal = pdFALSE;

    /* Temp buffer.*/
    uint8_t buff[ sizeof( OtaEventMsg_t ) ];

    ( void ) pEventCtx;

    retVal = xQueueReceive( otaEventQueue, &buff, pdMS_TO_TICKS( timeout ) );

    if( retVal == pdTRUE )
    {
        /* copy the data from local buffer.*/
        memcpy( pEventMsg, buff, MAX_MSG_SIZE );
        LogDebug( ( "OTA Event received" ) );
    }
    else
    {
        otaOsStatus = OtaOsEventQueueReceiveFailed;

        LogDebug( ( "Failed to receive event or timeout from OTA Event Queue: "
                    "xQueueReceive returned error: "
                    "OtaOsStatus_t=%d ",
                    ( int ) otaOsStatus ) );
    }

    return otaOsStatus;
}

OtaOsStatus_t OtaDeinitEvent_FreeRTOS( OtaEventContext_t * pEventCtx )
{
    OtaOsStatus_t otaOsStatus = OtaOsSuccess;

    ( void ) pEventCtx;

    /* Remove the event queue.*/
    if( otaEventQueue != NULL )
    {
        vQueueDelete( otaEventQueue );

        LogDebug( ( "OTA Event Queue Deleted." ) );
    }

    return otaOsStatus;
}

static void selfTestTimerCallback( TimerHandle_t T )
{
    ( void ) T;

    LogDebug( ( "Self-test expired within %ums",
                ( unsigned ) otaconfigSELF_TEST_RESPONSE_WAIT_MS ) );

    if( otaTimerCallbackPtr != NULL )
    {
        otaTimerCallbackPtr( OtaSelfTestTimer );
    }
    else
    {
        LogWarn( ( "Self-test timer event unhandled." ) );
    }
}

static void requestTimerCallback( TimerHandle_t T )
{
    ( void ) T;

    LogDebug( ( "Request timer expired in %ums",
                ( unsigned ) otaconfigFILE_REQUEST_WAIT_MS ) );

    if( otaTimerCallbackPtr != NULL )
    {
        otaTimerCallbackPtr( OtaRequestTimer );
    }
    else
    {
        LogWarn( ( "Request timer event unhandled." ) );
    }
}

OtaOsStatus_t OtaStartTimer_FreeRTOS( OtaTimerId_t otaTimerId,
                                      const char * const pTimerName,
                                      const uint32_t timeout,
                                      OtaTimerCallback_t callback )
{
    OtaOsStatus_t otaOsStatus = OtaOsSuccess;
    BaseType_t retVal = pdFALSE;

    configASSERT( callback != NULL );
    configASSERT( pTimerName != NULL );

    /* Set OTA lib callback. */
    otaTimerCallbackPtr = callback;

    /* If timer is not created.*/
    if( otaTimer[ otaTimerId ] == NULL )
    {
        /* Create the timer. */
        otaTimer[ otaTimerId ] = xTimerCreate( pTimerName,
                                               pdMS_TO_TICKS( timeout ),
                                               pdFALSE,
                                               NULL,
                                               timerCallback[ otaTimerId ] );

        if( otaTimer[ otaTimerId ] == NULL )
        {
            otaOsStatus = OtaOsTimerCreateFailed;

            LogError( ( "Failed to create OTA timer: "
                        "timerCreate returned NULL "
                        "OtaOsStatus_t=%d ",
                        ( int ) otaOsStatus ) );
        }
        else
        {
            LogDebug( ( "OTA Timer created." ) );

            /* Start the timer. */
            retVal = xTimerStart( otaTimer[ otaTimerId ], portMAX_DELAY );

            if( retVal == pdTRUE )
            {
                LogDebug( ( "OTA Timer started." ) );
            }
            else
            {
                otaOsStatus = OtaOsTimerStartFailed;

                LogError( ( "Failed to start OTA timer: "
                            "timerStart returned error." ) );
            }
        }
    }
    else
    {
        /* Reset the timer. */
        retVal = xTimerReset( otaTimer[ otaTimerId ], portMAX_DELAY );

        if( retVal == pdTRUE )
        {
            LogDebug( ( "OTA Timer restarted." ) );
        }
        else
        {
            otaOsStatus = OtaOsTimerRestartFailed;

            LogError( ( "Failed to set OTA timer timeout: "
                        "timer_settime returned error: "
                        "OtaOsStatus_t=%d ",
                        ( int ) otaOsStatus ) );
        }
    }

    return otaOsStatus;
}

OtaOsStatus_t OtaStopTimer_FreeRTOS( OtaTimerId_t otaTimerId )
{
    OtaOsStatus_t otaOsStatus = OtaOsSuccess;
    BaseType_t retVal = pdFALSE;

    if( otaTimer[ otaTimerId ] != NULL )
    {
        /* Stop the timer. */
        retVal = xTimerStop( otaTimer[ otaTimerId ], portMAX_DELAY );

        if( retVal == pdTRUE )
        {
            LogDebug( ( "OTA Timer Stopped for Timerid=%d.", ( int ) otaTimerId ) );
        }
        else
        {
            LogError( ( "Failed to stop OTA timer: "
                        "timer_settime returned error: "
                        "OtaOsStatus_t=%d ",
                        ( int ) otaOsStatus ) );

            otaOsStatus = OtaOsTimerStopFailed;
        }
    }
    else
    {
        LogWarn( ( "OTA Timer handle NULL for Timerid=%d, can't stop.", ( int ) otaTimerId ) );
    }

    return otaOsStatus;
}

OtaOsStatus_t OtaDeleteTimer_FreeRTOS( OtaTimerId_t otaTimerId )
{
    OtaOsStatus_t otaOsStatus = OtaOsSuccess;
    BaseType_t retVal = pdFALSE;

    if( otaTimer[ otaTimerId ] != NULL )
    {
        /* Delete the timer. */
        retVal = xTimerDelete( otaTimer[ otaTimerId ], portMAX_DELAY );

        if( retVal == pdTRUE )
        {
            otaTimer[ otaTimerId ] = NULL;
            LogDebug( ( "OTA Timer deleted." ) );
        }
        else
        {
            otaOsStatus = OtaOsTimerDeleteFailed;

            LogError( ( "Failed to delete OTA timer: "
                        "timer_delete returned error: "
                        "OtaOsStatus_t=%d ",
                        ( int ) otaOsStatus ) );
        }
    }
    else
    {
        otaOsStatus = OtaOsTimerDeleteFailed;

        LogWarn( ( "OTA Timer handle NULL for Timerid=%d, can't delete.", ( int ) otaTimerId ) );
    }

    return otaOsStatus;
}

void * Malloc_FreeRTOS( size_t size )
{
    return pvPortMalloc( size );
}

void Free_FreeRTOS( void * ptr )
{
    vPortFree( ptr );
}
