/**
 * @file sys_arch.c
 * @brief LwIP system architecture implementation for FreeRTOS
 */

#include "lwip/opt.h"
#include "lwip/mem.h"
#include "lwip/sys.h"
#include "lwip/stats.h"
#include "arch/sys_arch.h"

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

#include <string.h>

int errno;

/* Timeout for infinite wait */
#define LWIP_ARCH_TICK_PER_MS   (1000 / configTICK_RATE_HZ)

/*---------------------------------------------------------------------------
 * System Initialization
 *---------------------------------------------------------------------------*/

/**
 * @brief Initialize the system architecture layer
 */
void sys_init(void)
{
    /* Nothing to do here for FreeRTOS */
}

/**
 * @brief Get current time in milliseconds
 * @return Current time in milliseconds
 */
u32_t sys_now(void)
{
    return xTaskGetTickCount() * portTICK_PERIOD_MS;
}

/*---------------------------------------------------------------------------
 * Semaphore Functions
 *---------------------------------------------------------------------------*/

/**
 * @brief Create a new semaphore
 * @param sem Pointer to the semaphore to create
 * @param count Initial count of the semaphore
 * @return ERR_OK if successful, ERR_MEM if out of memory
 */
err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
    *sem = xSemaphoreCreateCounting(0xFF, count);
    if (*sem == NULL)
    {
        SYS_STATS_INC(sem.err);
        return ERR_MEM;
    }
    
    SYS_STATS_INC_USED(sem);
    return ERR_OK;
}

/**
 * @brief Free a semaphore
 * @param sem Pointer to the semaphore to free
 */
void sys_sem_free(sys_sem_t *sem)
{
    if (*sem != SYS_SEM_NULL)
    {
        vSemaphoreDelete(*sem);
        *sem = SYS_SEM_NULL;
        SYS_STATS_DEC(sem.used);
    }
}

/**
 * @brief Signal a semaphore
 * @param sem Pointer to the semaphore to signal
 */
void sys_sem_signal(sys_sem_t *sem)
{
    xSemaphoreGive(*sem);
}

/**
 * @brief Wait for a semaphore
 * @param sem Pointer to the semaphore to wait on
 * @param timeout Timeout in milliseconds (0 = wait forever)
 * @return Time waited in milliseconds, or SYS_ARCH_TIMEOUT on timeout
 */
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
    TickType_t start_time = xTaskGetTickCount();
    TickType_t wait_ticks;
    
    if (timeout == 0)
    {
        wait_ticks = portMAX_DELAY;
    }
    else
    {
        wait_ticks = pdMS_TO_TICKS(timeout);
    }
    
    if (xSemaphoreTake(*sem, wait_ticks) == pdTRUE)
    {
        u32_t elapsed = (xTaskGetTickCount() - start_time) * portTICK_PERIOD_MS;
        return elapsed;
    }
    
    return SYS_ARCH_TIMEOUT;
}

/*---------------------------------------------------------------------------
 * Mutex Functions
 *---------------------------------------------------------------------------*/

/**
 * @brief Create a new mutex
 * @param mutex Pointer to the mutex to create
 * @return ERR_OK if successful, ERR_MEM if out of memory
 */
err_t sys_mutex_new(sys_mutex_t *mutex)
{
    *mutex = xSemaphoreCreateMutex();
    if (*mutex == NULL)
    {
        SYS_STATS_INC(mutex.err);
        return ERR_MEM;
    }
    
    SYS_STATS_INC_USED(mutex);
    return ERR_OK;
}

/**
 * @brief Free a mutex
 * @param mutex Pointer to the mutex to free
 */
void sys_mutex_free(sys_mutex_t *mutex)
{
    if (*mutex != SYS_MUTEX_NULL)
    {
        vSemaphoreDelete(*mutex);
        *mutex = SYS_MUTEX_NULL;
        SYS_STATS_DEC(mutex.used);
    }
}

/**
 * @brief Lock a mutex
 * @param mutex Pointer to the mutex to lock
 */
void sys_mutex_lock(sys_mutex_t *mutex)
{
    xSemaphoreTake(*mutex, portMAX_DELAY);
}

/**
 * @brief Unlock a mutex
 * @param mutex Pointer to the mutex to unlock
 */
void sys_mutex_unlock(sys_mutex_t *mutex)
{
    xSemaphoreGive(*mutex);
}

/*---------------------------------------------------------------------------
 * Mailbox Functions
 *---------------------------------------------------------------------------*/

/**
 * @brief Create a new mailbox
 * @param mbox Pointer to the mailbox to create
 * @param size Size of the mailbox
 * @return ERR_OK if successful, ERR_MEM if out of memory
 */
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
    *mbox = xQueueCreate(size, sizeof(void *));
    if (*mbox == NULL)
    {
        SYS_STATS_INC(mbox.err);
        return ERR_MEM;
    }
    
    SYS_STATS_INC_USED(mbox);
    return ERR_OK;
}

/**
 * @brief Free a mailbox
 * @param mbox Pointer to the mailbox to free
 */
void sys_mbox_free(sys_mbox_t *mbox)
{
    if (*mbox != SYS_MBOX_NULL)
    {
        /* Wait for mailbox to be empty */
        while (uxQueueMessagesWaiting(*mbox) != 0)
        {
            vTaskDelay(1);
        }
        vQueueDelete(*mbox);
        *mbox = SYS_MBOX_NULL;
        SYS_STATS_DEC(mbox.used);
    }
}

/**
 * @brief Post a message to a mailbox
 * @param mbox Pointer to the mailbox
 * @param msg Message to post
 */
void sys_mbox_post(sys_mbox_t *mbox, void *msg)
{
    while (xQueueSendToBack(*mbox, &msg, portMAX_DELAY) != pdTRUE);
}

/**
 * @brief Try to post a message to a mailbox
 * @param mbox Pointer to the mailbox
 * @param msg Message to post
 * @return ERR_OK if successful, ERR_MEM if mailbox is full
 */
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
    if (xQueueSendToBack(*mbox, &msg, 0) == pdTRUE)
    {
        return ERR_OK;
    }
    
    SYS_STATS_INC(mbox.err);
    return ERR_MEM;
}

/**
 * @brief Try to post a message to front of mailbox
 * @param mbox Pointer to the mailbox
 * @param msg Message to post
 * @return ERR_OK if successful, ERR_MEM if mailbox is full
 */
err_t sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if (xQueueSendToBackFromISR(*mbox, &msg, &xHigherPriorityTaskWoken) == pdTRUE)
    {
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        return ERR_OK;
    }
    
    return ERR_MEM;
}

/**
 * @brief Fetch a message from a mailbox
 * @param mbox Pointer to the mailbox
 * @param msg Pointer to store the received message
 * @param timeout Timeout in milliseconds (0 = wait forever)
 * @return Time waited in milliseconds, or SYS_ARCH_TIMEOUT on timeout
 */
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
    TickType_t start_time = xTaskGetTickCount();
    TickType_t wait_ticks;
    void *received_msg;
    
    if (timeout == 0)
    {
        wait_ticks = portMAX_DELAY;
    }
    else
    {
        wait_ticks = pdMS_TO_TICKS(timeout);
    }
    
    if (xQueueReceive(*mbox, &received_msg, wait_ticks) == pdTRUE)
    {
        if (msg != NULL)
        {
            *msg = received_msg;
        }
        u32_t elapsed = (xTaskGetTickCount() - start_time) * portTICK_PERIOD_MS;
        return elapsed;
    }
    
    if (msg != NULL)
    {
        *msg = NULL;
    }
    return SYS_ARCH_TIMEOUT;
}

/**
 * @brief Try to fetch a message from a mailbox
 * @param mbox Pointer to the mailbox
 * @param msg Pointer to store the received message
 * @return 0 if successful, SYS_MBOX_EMPTY if mailbox is empty
 */
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
    void *received_msg;
    
    if (xQueueReceive(*mbox, &received_msg, 0) == pdTRUE)
    {
        if (msg != NULL)
        {
            *msg = received_msg;
        }
        return 0;
    }
    
    return SYS_MBOX_EMPTY;
}

/*---------------------------------------------------------------------------
 * Thread Functions
 *---------------------------------------------------------------------------*/

/**
 * @brief Create a new thread
 * @param name Thread name
 * @param thread Thread function
 * @param arg Argument to pass to thread function
 * @param stacksize Stack size in words
 * @param prio Thread priority
 * @return Thread handle
 */
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, 
                            void *arg, int stacksize, int prio)
{
    TaskHandle_t task_handle = NULL;
    
    BaseType_t result = xTaskCreate(
        (TaskFunction_t)thread,
        name,
        stacksize,
        arg,
        prio,
        &task_handle
    );
    
    if (result != pdPASS)
    {
        return NULL;
    }
    
    return task_handle;
}

/*---------------------------------------------------------------------------
 * Critical Section Protection
 *---------------------------------------------------------------------------*/

/**
 * @brief Enter critical section
 * @return Previous interrupt state
 */
sys_prot_t sys_arch_protect(void)
{
    taskENTER_CRITICAL();
    return 0;
}

/**
 * @brief Leave critical section
 * @param pval Previous interrupt state (unused in FreeRTOS)
 */
void sys_arch_unprotect(sys_prot_t pval)
{
    (void)pval;
    taskEXIT_CRITICAL();
}
