/*
 * Amazon FreeRTOS POSIX V1.1.0
 * Copyright (C) 2018 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 FreeRTOS_POSIX_pthread_mutex.c
 * @brief Implementation of mutex functions in pthread.h
 */

/* C standard library includes. */
#include <stddef.h>
#include <string.h>

/* FreeRTOS+POSIX includes. */
#include "FreeRTOS_POSIX.h"
#include "FreeRTOS_POSIX/errno.h"
#include "FreeRTOS_POSIX/pthread.h"
#include "FreeRTOS_POSIX/utils.h"

/**
 * @brief Initialize a PTHREAD_MUTEX_INITIALIZER mutex.
 *
 * PTHREAD_MUTEX_INITIALIZER sets a flag for a mutex to be initialized later.
 * This function performs the initialization.
 * @param[in] pxMutex The mutex to initialize.
 *
 * @return nothing
 */
static void prvInitializeStaticMutex( pthread_mutex_internal_t * pxMutex );

/**
 * @brief Default pthread_mutexattr_t.
 */
static const pthread_mutexattr_internal_t xDefaultMutexAttributes =
{
    .iType = PTHREAD_MUTEX_DEFAULT,
};

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

static void prvInitializeStaticMutex( pthread_mutex_internal_t * pxMutex )
{
    /* Check if the mutex needs to be initialized. */
    if( pxMutex->xIsInitialized == pdFALSE )
    {
        /* Mutex initialization must be in a critical section to prevent two threads
         * from initializing it at the same time. */
        taskENTER_CRITICAL();

        /* Check again that the mutex is still uninitialized, i.e. it wasn't
         * initialized while this function was waiting to enter the critical
         * section. */
        if( pxMutex->xIsInitialized == pdFALSE )
        {
            /* Set the mutex as the default type. */
            pxMutex->xAttr.iType = PTHREAD_MUTEX_DEFAULT;

            /* Call the correct FreeRTOS mutex initialization function based on
             * the mutex type. */
            #if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_RECURSIVE
                ( void ) xSemaphoreCreateRecursiveMutexStatic( &pxMutex->xMutex );
            #else
                ( void ) xSemaphoreCreateMutexStatic( &pxMutex->xMutex );
            #endif

            pxMutex->xIsInitialized = pdTRUE;
        }

        /* Exit the critical section. */
        taskEXIT_CRITICAL();
    }
}

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

int pthread_mutex_destroy( pthread_mutex_t * mutex )
{
    pthread_mutex_internal_t * pxMutex = ( pthread_mutex_internal_t * ) ( mutex );

    /* Free resources in use by the mutex. */
    if( pxMutex->xTaskOwner == NULL )
    {
        vSemaphoreDelete( ( SemaphoreHandle_t ) &pxMutex->xMutex );
    }

    return 0;
}

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

int pthread_mutex_init( pthread_mutex_t * mutex,
                        const pthread_mutexattr_t * attr )
{
    int iStatus = 0;
    pthread_mutex_internal_t * pxMutex = ( pthread_mutex_internal_t * ) mutex;

    if( pxMutex == NULL )
    {
        /* No memory. */
        iStatus = ENOMEM;
    }

    if( iStatus == 0 )
    {
        *pxMutex = FREERTOS_POSIX_MUTEX_INITIALIZER;

        /* No attributes given, use default attributes. */
        if( attr == NULL )
        {
            pxMutex->xAttr = xDefaultMutexAttributes;
        }
        /* Otherwise, use provided attributes. */
        else
        {
            pxMutex->xAttr = *( ( pthread_mutexattr_internal_t * ) ( attr ) );
        }

        /* Call the correct FreeRTOS mutex creation function based on mutex type. */
        if( pxMutex->xAttr.iType == PTHREAD_MUTEX_RECURSIVE )
        {
            /* Recursive mutex. */
            ( void ) xSemaphoreCreateRecursiveMutexStatic( &pxMutex->xMutex );
        }
        else
        {
            /* All other mutex types. */
            ( void ) xSemaphoreCreateMutexStatic( &pxMutex->xMutex );
        }

        /* Ensure that the FreeRTOS mutex was successfully created. */
        if( ( SemaphoreHandle_t ) &pxMutex->xMutex == NULL )
        {
            /* Failed to create mutex. Set error EAGAIN and free mutex object. */
            iStatus = EAGAIN;
            vPortFree( pxMutex );
        }
        else
        {
            /* Mutex successfully created. */
            pxMutex->xIsInitialized = pdTRUE;
        }
    }

    return iStatus;
}

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

int pthread_mutex_lock( pthread_mutex_t * mutex )
{
    return pthread_mutex_timedlock( mutex, NULL );
}

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

int pthread_mutex_timedlock( pthread_mutex_t * mutex,
                             const struct timespec * abstime )
{
    int iStatus = 0;
    pthread_mutex_internal_t * pxMutex = ( pthread_mutex_internal_t * ) ( mutex );
    TickType_t xDelay = portMAX_DELAY;
    BaseType_t xFreeRTOSMutexTakeStatus = pdFALSE;

    /* If mutex in uninitialized, perform initialization. */
    prvInitializeStaticMutex( pxMutex );

    /* At this point, the mutex should be initialized. */
    configASSERT( pxMutex->xIsInitialized == pdTRUE );

    /* Convert abstime to a delay in TickType_t if provided. */
    if( abstime != NULL )
    {
        struct timespec xCurrentTime = { 0 };

        /* Get current time */
        if( clock_gettime( CLOCK_REALTIME, &xCurrentTime ) != 0 )
        {
            iStatus = EINVAL;
        }
        else
        {
            iStatus = UTILS_AbsoluteTimespecToDeltaTicks( abstime, &xCurrentTime, &xDelay );
        }

        /* If abstime was in the past, still attempt to lock the mutex without
         * blocking, per POSIX spec. */
        if( iStatus == ETIMEDOUT )
        {
            xDelay = 0;
            iStatus = 0;
        }
    }

    /* Check if trying to lock a currently owned mutex. */
    if( ( iStatus == 0 ) &&
        ( pxMutex->xAttr.iType == PTHREAD_MUTEX_ERRORCHECK ) &&  /* Only PTHREAD_MUTEX_ERRORCHECK type detects deadlock. */
        ( pxMutex->xTaskOwner == xTaskGetCurrentTaskHandle() ) ) /* Check if locking a currently owned mutex. */
    {
        iStatus = EDEADLK;
    }

    if( iStatus == 0 )
    {
        /* Call the correct FreeRTOS mutex take function based on mutex type. */
        if( pxMutex->xAttr.iType == PTHREAD_MUTEX_RECURSIVE )
        {
            xFreeRTOSMutexTakeStatus = xSemaphoreTakeRecursive( ( SemaphoreHandle_t ) &pxMutex->xMutex, xDelay );
        }
        else
        {
            xFreeRTOSMutexTakeStatus = xSemaphoreTake( ( SemaphoreHandle_t ) &pxMutex->xMutex, xDelay );
        }

        /* If the mutex was successfully taken, set its owner. */
        if( xFreeRTOSMutexTakeStatus == pdPASS )
        {
            pxMutex->xTaskOwner = xTaskGetCurrentTaskHandle();
        }
        /* Otherwise, the mutex take timed out. */
        else
        {
            iStatus = ETIMEDOUT;
        }
    }

    return iStatus;
}

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

int pthread_mutex_trylock( pthread_mutex_t * mutex )
{
    int iStatus = 0;
    struct timespec xTimeout =
    {
        .tv_sec  = 0,
        .tv_nsec = 0
    };

    /* Attempt to lock with no timeout. */
    iStatus = pthread_mutex_timedlock( mutex, &xTimeout );

    /* POSIX specifies that this function should return EBUSY instead of
     * ETIMEDOUT for attempting to lock a locked mutex. */
    if( iStatus == ETIMEDOUT )
    {
        iStatus = EBUSY;
    }

    return iStatus;
}

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

int pthread_mutex_unlock( pthread_mutex_t * mutex )
{
    int iStatus = 0;
    pthread_mutex_internal_t * pxMutex = ( pthread_mutex_internal_t * ) ( mutex );

    /* If mutex in uninitialized, perform initialization. */
    prvInitializeStaticMutex( pxMutex );

    /* Check if trying to unlock an unowned mutex. */
    if( ( ( pxMutex->xAttr.iType == PTHREAD_MUTEX_ERRORCHECK ) ||
          ( pxMutex->xAttr.iType == PTHREAD_MUTEX_RECURSIVE ) ) &&
        ( pxMutex->xTaskOwner != xTaskGetCurrentTaskHandle() ) )
    {
        iStatus = EPERM;
    }

    if( iStatus == 0 )
    {
        /* Suspend the scheduler so that
         * mutex is unlocked AND owner is updated atomically */
        vTaskSuspendAll();

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

        /* Update the owner of the mutex. A recursive mutex may still have an
         * owner, so it should be updated with xSemaphoreGetMutexHolder. */
        pxMutex->xTaskOwner = xSemaphoreGetMutexHolder( ( SemaphoreHandle_t ) &pxMutex->xMutex );

        /* Resume the scheduler */
        ( void ) xTaskResumeAll();
    }

    return iStatus;
}

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

int pthread_mutexattr_destroy( pthread_mutexattr_t * attr )
{
    ( void ) attr;

    return 0;
}

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

int pthread_mutexattr_gettype( const pthread_mutexattr_t * attr,
                               int * type )
{
    pthread_mutexattr_internal_t * pxAttr = ( pthread_mutexattr_internal_t * ) ( attr );

    *type = pxAttr->iType;

    return 0;
}

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

int pthread_mutexattr_init( pthread_mutexattr_t * attr )
{
    *( ( pthread_mutexattr_internal_t * ) ( attr ) ) = xDefaultMutexAttributes;

    return 0;
}

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

int pthread_mutexattr_settype( pthread_mutexattr_t * attr,
                               int type )
{
    int iStatus = 0;
    pthread_mutexattr_internal_t * pxAttr = ( pthread_mutexattr_internal_t * ) ( attr );

    switch( type )
    {
        case PTHREAD_MUTEX_NORMAL:
        case PTHREAD_MUTEX_RECURSIVE:
        case PTHREAD_MUTEX_ERRORCHECK:
            pxAttr->iType = type;
            break;

        default:
            iStatus = EINVAL;
            break;
    }

    return iStatus;
}

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