#include "mutex.h"

#include "check.h"
#include "core_defines.h"
#include "log.h"

#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#else
#include "FreeRTOS.h"
#include "semphr.h"
#endif

typedef struct {
    SemaphoreHandle_t handle;
    MutexType type;
} MutexData;

#define MUTEX_DEBUGGING false

#if MUTEX_DEBUGGING
#define TAG "mutex"
void tt_mutex_info(Mutex mutex, const char* label) {
    MutexData* data = (MutexData*)mutex;
    if (data == NULL) {
        TT_LOG_I(TAG, "mutex %s: is NULL", label);
    } else {
        TT_LOG_I(TAG, "mutex %s: handle=%0X type=%d owner=%0x", label, data->handle, data->type, tt_mutex_get_owner(mutex));
    }
}
#else
#define tt_mutex_info(mutex, text)
#endif

Mutex tt_mutex_alloc(MutexType type) {
    tt_assert(!TT_IS_IRQ_MODE());
    MutexData* data = malloc(sizeof(MutexData));

    data->type = type;

    switch (type) {
        case MutexTypeNormal:
            data->handle = xSemaphoreCreateMutex();
            break;
        case MutexTypeRecursive:
            data->handle = xSemaphoreCreateRecursiveMutex();
            break;
        default:
            tt_crash("mutex type unknown/corrupted");
    }

    tt_check(data->handle != NULL);
    tt_mutex_info(data, "alloc  ");
    return (Mutex)data;
}

void tt_mutex_free(Mutex mutex) {
    tt_assert(!TT_IS_IRQ_MODE());
    tt_assert(mutex);

    MutexData* data = (MutexData*)mutex;
    vSemaphoreDelete(data->handle);
    data->handle = NULL; // If the mutex is used after release, this might help debugging
    data->type = 0xBAADF00D; // Set to an invalid value
    free(data);
}

TtStatus tt_mutex_acquire(Mutex mutex, uint32_t timeout) {
    tt_assert(mutex);
    tt_assert(!TT_IS_IRQ_MODE());
    MutexData* data = (MutexData*)mutex;
    tt_assert(data->handle);
    TtStatus status = TtStatusOk;

    tt_mutex_info(mutex, "acquire");

    switch (data->type) {
        case MutexTypeNormal:
            if (xSemaphoreTake(data->handle, timeout) != pdPASS) {
                if (timeout != 0U) {
                    status = TtStatusErrorTimeout;
                } else {
                    status = TtStatusErrorResource;
                }
            }
            break;
        case MutexTypeRecursive:
            if (xSemaphoreTakeRecursive(data->handle, timeout) != pdPASS) {
                if (timeout != 0U) {
                    status = TtStatusErrorTimeout;
                } else {
                    status = TtStatusErrorResource;
                }
            }
            break;
        default:
            tt_crash("mutex type unknown/corrupted");
    }

    return status;
}

TtStatus tt_mutex_release(Mutex mutex) {
    tt_assert(mutex);
    assert(!TT_IS_IRQ_MODE());
    MutexData* data = (MutexData*)mutex;
    tt_assert(data->handle);
    TtStatus status = TtStatusOk;

    tt_mutex_info(mutex, "release");

    switch (data->type) {
        case MutexTypeNormal: {
            if (xSemaphoreGive(data->handle) != pdPASS) {
                status = TtStatusErrorResource;
            }
            break;
        }
        case MutexTypeRecursive:
            if (xSemaphoreGiveRecursive(data->handle) != pdPASS) {
                status = TtStatusErrorResource;
            }
            break;
        default:
            tt_crash("mutex type unknown/corrupted %d");
    }

    return status;
}

ThreadId tt_mutex_get_owner(Mutex mutex) {
    tt_assert(mutex != NULL);
    tt_assert(!TT_IS_IRQ_MODE());
    MutexData* data = (MutexData*)mutex;
    tt_assert(data->handle);
    return (ThreadId)xSemaphoreGetMutexHolder(data->handle);
}
