/*
 * 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_clock_freertos.c
 * @brief Implementation of the platform specific functions in iot_clock.h for
 * FreeRTOS.
 */

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

/* Standard includes. */
#include <stdio.h>

/* Platform clock include. */
#include "platform/iot_platform_types_freertos.h"
#include "platform/iot_clock.h"
#include "task.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    ( "CLOCK" )
#include "iot_logging_setup.h"

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

/*
 * Time conversion constants.
 */
#define _MILLISECONDS_PER_SECOND    ( 1000 )                                          /**< @brief Milliseconds per second. */
#define _MILLISECONDS_PER_TICK      ( _MILLISECONDS_PER_SECOND / configTICK_RATE_HZ ) /**< Milliseconds per FreeRTOS tick. */

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

/*  Private Callback function for timer expiry, delegate work to a Task to free
 *  up the timer task for managing other timers */
static void prvTimerCallback( TimerHandle_t xTimerHandle )
{
    _IotSystemTimer_t * pxTimer = ( _IotSystemTimer_t * ) pvTimerGetTimerID( xTimerHandle );

    /* The value of the timer ID, set in timer_create, should not be NULL. */
    configASSERT( pxTimer != NULL );

    /* Restart the timer if it is periodic. */
    if( pxTimer->xTimerPeriod > 0 )
    {
        xTimerChangePeriod( xTimerHandle, pxTimer->xTimerPeriod, 0 );
    }

    /* Call timer Callback from this task */
    pxTimer->threadRoutine( ( void * ) pxTimer->pArgument );
}

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

bool IotClock_GetTimestring( char * pBuffer,
                             size_t bufferSize,
                             size_t * pTimestringLength )
{
    uint64_t milliSeconds = IotClock_GetTimeMs();
    int timestringLength = 0;

    configASSERT( pBuffer != NULL );
    configASSERT( pTimestringLength != NULL );

    /* Convert the localTime struct to a string. */
    timestringLength = snprintf( pBuffer, bufferSize, "%llu", milliSeconds );

    /* Check for error from no string */
    if( timestringLength == 0 )
    {
        return false;
    }

    /* Set the output parameter. */
    *pTimestringLength = timestringLength;

    return true;
}

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

uint64_t IotClock_GetTimeMs( void )
{
    TimeOut_t xCurrentTime = { 0 };

    /* This must be unsigned because the behavior of signed integer overflow is undefined. */
    uint64_t ullTickCount = 0ULL;

    /* Get the current tick count and overflow count. vTaskSetTimeOutState()
     * is used to get these values because they are both static in tasks.c. */
    vTaskSetTimeOutState( &xCurrentTime );

    /* Adjust the tick count for the number of times a TickType_t has overflowed. */
    ullTickCount = ( uint64_t ) ( xCurrentTime.xOverflowCount ) << ( sizeof( TickType_t ) * 8 );

    /* Add the current tick count. */
    ullTickCount += xCurrentTime.xTimeOnEntering;

    /* Return the ticks converted to Milliseconds */
    return ullTickCount * _MILLISECONDS_PER_TICK;
}
/*-----------------------------------------------------------*/

void IotClock_SleepMs( uint32_t sleepTimeMs )
{
    vTaskDelay( pdMS_TO_TICKS( sleepTimeMs ) );
}

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

bool IotClock_TimerCreate( IotTimer_t * pNewTimer,
                           IotThreadRoutine_t expirationRoutine,
                           void * pArgument )
{
    _IotSystemTimer_t * pxTimer = ( _IotSystemTimer_t * ) pNewTimer;

    configASSERT( pNewTimer != NULL );
    configASSERT( expirationRoutine != NULL );

    IotLogDebug( "Creating new timer %p.", pNewTimer );

    /* Set the timer expiration routine, argument and period */
    pxTimer->threadRoutine = expirationRoutine;
    pxTimer->pArgument = pArgument;
    pxTimer->xTimerPeriod = 0;

    /* Create a new FreeRTOS timer. This call will not fail because the
     * memory for it has already been allocated, so the output parameter is
     * also set. */
    pxTimer->timer = ( TimerHandle_t ) xTimerCreateStatic( "timer",                  /* Timer name. */
                                                           portMAX_DELAY,            /* Initial timer period. Timers are created disarmed. */
                                                           pdFALSE,                  /* Don't auto-reload timer. */
                                                           ( void * ) pxTimer,       /* Timer id. */
                                                           prvTimerCallback,         /* Timer expiration callback. */
                                                           &pxTimer->xTimerBuffer ); /* Pre-allocated memory for timer. */

    return true;
}

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

void IotClock_TimerDestroy( IotTimer_t * pTimer )
{
    _IotSystemTimer_t * pTimerInfo = ( _IotSystemTimer_t * ) pTimer;

    configASSERT( pTimerInfo != NULL );
    configASSERT( pTimerInfo->timer != NULL );

    IotLogDebug( "Destroying timer %p.", pTimer );

    if( xTimerIsTimerActive( pTimerInfo->timer ) == pdTRUE )
    {
        /* Stop the FreeRTOS timer. Because the timer is statically allocated, no call
         * to xTimerDelete is necessary. The timer is stopped so that it's not referenced
         * anywhere. xTimerStop will not fail when it has unlimited block time. */
        ( void ) xTimerStop( pTimerInfo->timer, portMAX_DELAY );

        /* Wait until the timer stop command is processed. */
        while( xTimerIsTimerActive( pTimerInfo->timer ) == pdTRUE )
        {
            vTaskDelay( 1 );
        }
    }
}

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

bool IotClock_TimerArm( IotTimer_t * pTimer,
                        uint32_t relativeTimeoutMs,
                        uint32_t periodMs )
{
    _IotSystemTimer_t * pTimerInfo = ( _IotSystemTimer_t * ) pTimer;

    configASSERT( pTimerInfo != NULL );

    TimerHandle_t xTimerHandle = pTimerInfo->timer;

    IotLogDebug( "Arming timer %p with timeout %llu and period %llu.",
                 pTimer,
                 relativeTimeoutMs,
                 periodMs );

    /* Set the timer period in ticks */
    pTimerInfo->xTimerPeriod = pdMS_TO_TICKS( periodMs );

    /* Set the timer to expire after relativeTimeoutMs, and restart it. */
    ( void ) xTimerChangePeriod( xTimerHandle, pdMS_TO_TICKS( relativeTimeoutMs ), portMAX_DELAY );

    return true;
}

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