/*
 * FreeRTOS Platform V1.1.1
 * 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.
 *
 * http://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */

/**
 * @file iot_threads_freertos.c
 * @brief Implementation of the platform specific functions in iot_threads.h for
 * FreeRTOS.
 */

/* The config header is always included first. */
#include "iot_config.h"

#include "semphr.h"

/* Platform threads include. */
#include "platform/iot_platform_types_freertos.h"
#include "platform/iot_threads.h"
#include "types/iot_platform_types.h"

/* Configure logs for the functions in this file. */
#ifdef IOT_LOG_LEVEL_PLATFORM
    #define LIBRARY_LOG_LEVEL        IOT_LOG_LEVEL_PLATFORM
#else
    #ifdef IOT_LOG_LEVEL_GLOBAL
        #define LIBRARY_LOG_LEVEL    IOT_LOG_LEVEL_GLOBAL
    #else
        #define LIBRARY_LOG_LEVEL    IOT_LOG_NONE
    #endif
#endif

#define LIBRARY_LOG_NAME    ( "THREAD" )
#include "iot_logging_setup.h"

/*
 * Provide default values for undefined memory allocation functions based on
 * the usage of dynamic memory allocation.
 */
#ifndef IotThreads_Malloc
    #include <stdlib.h>

/**
 * @brief Memory allocation. This function should have the same signature
 * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html).
 */
    #define IotThreads_Malloc    malloc
#endif
#ifndef IotThreads_Free
    #include <stdlib.h>

/**
 * @brief Free memory. This function should have the same signature as
 * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html).
 */
    #define IotThreads_Free    free
#endif

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

static void _threadRoutineWrapper( void * pArgument )
{
    threadInfo_t * pThreadInfo = ( threadInfo_t * ) pArgument;

    /* Run the thread routine. */
    pThreadInfo->threadRoutine( pThreadInfo->pArgument );
    IotThreads_Free( pThreadInfo );

    vTaskDelete( NULL );
}

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

bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine,
                               void * pArgument,
                               int32_t priority,
                               size_t stackSize )
{
    bool status = true;

    configASSERT( threadRoutine != NULL );

    IotLogDebug( "Creating new thread." );
    threadInfo_t * pThreadInfo = IotThreads_Malloc( sizeof( threadInfo_t ) );

    if( pThreadInfo == NULL )
    {
        IotLogDebug( "Unable to allocate memory for threadRoutine %p.", threadRoutine );
        status = false;
    }

    /* Create the FreeRTOS task that will run the thread. */
    if( status )
    {
        pThreadInfo->threadRoutine = threadRoutine;
        pThreadInfo->pArgument = pArgument;

        if( xTaskCreate( _threadRoutineWrapper,
                         "iot_thread",
                         ( configSTACK_DEPTH_TYPE ) stackSize,
                         pThreadInfo,
                         priority,
                         NULL ) != pdPASS )
        {
            /* Task creation failed. */
            IotLogWarn( "Failed to create thread." );
            IotThreads_Free( pThreadInfo );
            status = false;
        }
    }

    return status;
}

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

bool IotMutex_Create( IotMutex_t * pNewMutex,
                      bool recursive )
{
    _IotSystemMutex_t * internalMutex = ( _IotSystemMutex_t * ) pNewMutex;

    configASSERT( internalMutex != NULL );

    IotLogDebug( "Creating new mutex %p.", pNewMutex );

    if( recursive )
    {
        ( void ) xSemaphoreCreateRecursiveMutexStatic( &internalMutex->xMutex );
    }
    else
    {
        ( void ) xSemaphoreCreateMutexStatic( &internalMutex->xMutex );
    }

    /* remember the type of mutex */
    if( recursive )
    {
        internalMutex->recursive = pdTRUE;
    }
    else
    {
        internalMutex->recursive = pdFALSE;
    }

    return true;
}

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

void IotMutex_Destroy( IotMutex_t * pMutex )
{
    _IotSystemMutex_t * internalMutex = ( _IotSystemMutex_t * ) pMutex;

    configASSERT( internalMutex != NULL );

    vSemaphoreDelete( ( SemaphoreHandle_t ) &internalMutex->xMutex );
}

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

bool prIotMutexTimedLock( IotMutex_t * pMutex,
                          TickType_t timeout )
{
    _IotSystemMutex_t * internalMutex = ( _IotSystemMutex_t * ) pMutex;
    BaseType_t lockResult;

    configASSERT( internalMutex != NULL );

    IotLogDebug( "Locking mutex %p.", internalMutex );

    /* Call the correct FreeRTOS mutex take function based on mutex type. */
    if( internalMutex->recursive == pdTRUE )
    {
        lockResult = xSemaphoreTakeRecursive( ( SemaphoreHandle_t ) &internalMutex->xMutex, timeout );
    }
    else
    {
        lockResult = xSemaphoreTake( ( SemaphoreHandle_t ) &internalMutex->xMutex, timeout );
    }

    return( lockResult == pdTRUE );
}

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

void IotMutex_Lock( IotMutex_t * pMutex )
{
    prIotMutexTimedLock( pMutex, portMAX_DELAY );
}

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

bool IotMutex_TryLock( IotMutex_t * pMutex )
{
    return prIotMutexTimedLock( pMutex, 0 );
}

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

void IotMutex_Unlock( IotMutex_t * pMutex )
{
    _IotSystemMutex_t * internalMutex = ( _IotSystemMutex_t * ) pMutex;

    configASSERT( internalMutex != NULL );

    IotLogDebug( "Unlocking mutex %p.", internalMutex );

    /* Call the correct FreeRTOS mutex unlock function based on mutex type. */
    if( internalMutex->recursive == pdTRUE )
    {
        ( void ) xSemaphoreGiveRecursive( ( SemaphoreHandle_t ) &internalMutex->xMutex );
    }
    else
    {
        ( void ) xSemaphoreGive( ( SemaphoreHandle_t ) &internalMutex->xMutex );
    }
}

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

bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore,
                          uint32_t initialValue,
                          uint32_t maxValue )
{
    _IotSystemSemaphore_t * internalSemaphore = ( _IotSystemSemaphore_t * ) pNewSemaphore;

    configASSERT( internalSemaphore != NULL );

    IotLogDebug( "Creating new semaphore %p.", pNewSemaphore );

    ( void ) xSemaphoreCreateCountingStatic( maxValue, initialValue, &internalSemaphore->xSemaphore );

    return true;
}

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

uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore )
{
    _IotSystemSemaphore_t * internalSemaphore = ( _IotSystemSemaphore_t * ) pSemaphore;
    UBaseType_t count = 0;

    configASSERT( internalSemaphore != NULL );

    count = uxSemaphoreGetCount( ( SemaphoreHandle_t ) &internalSemaphore->xSemaphore );

    IotLogDebug( "Semaphore %p has count %d.", pSemaphore, count );

    return ( uint32_t ) count;
}

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

void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore )
{
    _IotSystemSemaphore_t * internalSemaphore = ( _IotSystemSemaphore_t * ) pSemaphore;

    configASSERT( internalSemaphore != NULL );

    IotLogDebug( "Destroying semaphore %p.", internalSemaphore );

    vSemaphoreDelete( ( SemaphoreHandle_t ) &internalSemaphore->xSemaphore );
}

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

void IotSemaphore_Wait( IotSemaphore_t * pSemaphore )
{
    _IotSystemSemaphore_t * internalSemaphore = ( _IotSystemSemaphore_t * ) pSemaphore;

    configASSERT( internalSemaphore != NULL );

    IotLogDebug( "Waiting on semaphore %p.", internalSemaphore );

    /* Take the semaphore using the FreeRTOS API. */
    if( xSemaphoreTake( ( SemaphoreHandle_t ) &internalSemaphore->xSemaphore,
                        portMAX_DELAY ) != pdTRUE )
    {
        IotLogWarn( "Failed to wait on semaphore %p.",
                    pSemaphore );

        /* There is an assert here because during debugging we could falsely
         * believe that we are waiting successfully on a semaphore. */
        configASSERT( false );
    }
}

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

bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore )
{
    _IotSystemSemaphore_t * internalSemaphore = ( _IotSystemSemaphore_t * ) pSemaphore;

    configASSERT( internalSemaphore != NULL );

    IotLogDebug( "Attempting to wait on semaphore %p.", internalSemaphore );

    return IotSemaphore_TimedWait( pSemaphore, 0 );
}

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

bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore,
                             uint32_t timeoutMs )
{
    _IotSystemSemaphore_t * internalSemaphore = ( _IotSystemSemaphore_t * ) pSemaphore;

    configASSERT( internalSemaphore != NULL );

    /* Take the semaphore using the FreeRTOS API. Cast the calculation to 64 bit to avoid overflows. */
    if( xSemaphoreTake( ( SemaphoreHandle_t ) &internalSemaphore->xSemaphore,
                        pdMS_TO_TICKS( timeoutMs ) ) != pdTRUE )
    {
        /* Only warn if timeout > 0. */
        if( timeoutMs > 0 )
        {
            IotLogWarn( "Timeout waiting on semaphore %p.",
                        internalSemaphore );
        }

        return false;
    }

    return true;
}

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

void IotSemaphore_Post( IotSemaphore_t * pSemaphore )
{
    _IotSystemSemaphore_t * internalSemaphore = ( _IotSystemSemaphore_t * ) pSemaphore;

    configASSERT( internalSemaphore != NULL );

    IotLogDebug( "Posting to semaphore %p.", internalSemaphore );
    /* Give the semaphore using the FreeRTOS API. */
    BaseType_t result = xSemaphoreGive( ( SemaphoreHandle_t ) &internalSemaphore->xSemaphore );

    if( result == pdFALSE )
    {
        IotLogDebug( "Unable to give semaphore over maximum", internalSemaphore );
    }
}

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