/*
 * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 * Author: Adam Dunkels <adam@sics.se>
 */

/* ------------------------ System includes ------------------------------- */
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include "lwipopts.h"
/* ------------------------ FreeRTOS includes ----------------------------- */
#if (NO_SYS == 0)
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "portmacro.h"

/* ------------------------ lwIP includes --------------------------------- */
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/sys.h"
#include "lwip/mem.h"
#include "lwip/sio.h"
#include "lwip/stats.h"

#include "lwip/err.h"

/* ------------------------ Project includes ------------------------------ */

/* ------------------------ Defines --------------------------------------- */
/* This is the number of threads that can be started with sys_thead_new() */


#define SYS_MBOX_SIZE               ( 32 )
#define MS_TO_TICKS( ms )           \
    ( portTickType )( ( portTickType ) ( ms ) / 10/*portTICK_RATE_MS*/ )
#define TICKS_TO_MS( ticks )        \
    ( unsigned long )( ( portTickType ) ( ticks ) * 10 /*portTICK_RATE_MS*/ )
#define THREAD_STACK_SIZE           ( 1024 )
#define THREAD_NAME                 "lwIP"

#define THREAD_INIT( tcb ) \
    do { \
        tcb->next = NULL; \
        tcb->pid = ( xTaskHandle )0; \
        tcb->timeouts.next = NULL; \
    } while( 0 )

/* ------------------------ Type definitions ------------------------------ */
typedef struct sys_tcb
{
    struct sys_tcb *next;
    struct sys_timeouts timeouts;
    xTaskHandle     pid;
} sys_tcb_t;

/* ------------------------ Prototypes ------------------------------------ */

/* ------------------------ Static functions ------------------------------ */
sys_tcb_t      *sys_thread_current( void );

sys_thread_t
sys_arch_thread_new( char *name, void ( *thread ) ( void *arg ), void *arg, int prio, size_t ssize );

/* ------------------------ Static variables ------------------------------ */
static sys_tcb_t *tasks = NULL;

/* ------------------------ Start implementation -------------------------- */
void
sys_init( void )
{
    LWIP_ASSERT( "sys_init: not called first", tasks == NULL );
    tasks = NULL;
}

/*
 * This optional function does a "fast" critical region protection and returns
 * the previous protection level. This function is only called during very short
 * critical regions. An embedded system which supports ISR-based drivers might
 * want to implement this function by disabling interrupts. Task-based systems
 * might want to implement this by using a mutex or disabling tasking. This
 * function should support recursive calls from the same task or interrupt. In
 * other words, sys_arch_protect() could be called while already protected. In
 * that case the return value indicates that it is already protected.
 *
 * sys_arch_protect() is only required if your port is supporting an operating
 * system.
 */
sys_prot_t
sys_arch_protect( void )
{
    vPortEnterCritical(  );
    return 1;
}

/*
 * This optional function does a "fast" set of critical region protection to the
 * value specified by pval. See the documentation for sys_arch_protect() for
 * more information. This function is only required if your port is supporting
 * an operating system.
 */
void
sys_arch_unprotect( sys_prot_t pval )
{
    ( void )pval;
    vPortExitCritical(  );
}

/*
 * Prints an assertion messages and aborts execution.
 */
void
sys_assert( const char *msg )
{
	fputs( msg, stderr );
	fputs( "\n\r", stderr );
    vPortEnterCritical(  );
    for( ;; );
}

void
sys_debug( const char *const fmt, ... )
{
    va_list         ap;

    va_start( ap, fmt );
    ( void )vprintf( fmt, ap );
    ( void )putchar( '\r' );
    va_end( ap );
}

/* ------------------------ Start implementation ( Threads ) -------------- */

sys_thread_t
sys_thread_new(char *name, void (* thread)(void *arg), void *arg, int stacksize, int prio)
//sys_thread_new( void ( *thread ) ( void *arg ), void *arg, int prio )
{
    return sys_arch_thread_new( name, thread, arg, prio, THREAD_STACK_SIZE );
}

/*
 * Starts a new thread with priority "prio" that will begin its execution in the
 * function "thread()". The "arg" argument will be passed as an argument to the
 * thread() function. The argument "ssize" is the requested stack size for the
 * new thread. The id of the new thread is returned. Both the id and the
  * priority are system dependent.
 */
sys_thread_t
sys_arch_thread_new( char *name, void ( *thread ) ( void *arg ), void *arg, int prio, size_t ssize )
{
    sys_thread_t    thread_hdl = NULL;
    int             i;
    sys_tcb_t      *p;
    //char            thread_name[ configMAX_TASK_NAME_LEN ];

    /* We disable the FreeRTOS scheduler because it might be the case that the new
     * tasks gets scheduled inside the xTaskCreate function. To prevent this we
     * disable the scheduling. Note that this can happen although we have interrupts
     * disabled because xTaskCreate contains a call to taskYIELD( ).
     */
    vPortEnterCritical(  );

    p = tasks;
    i = 0;
    /* We are called the first time. Initialize it. */
    if( p == NULL )
    {
        p = pvPortMalloc( sizeof( sys_tcb_t ) );
        if( p != NULL )
        {
            tasks = p;
        }
    }
    else
    {
        /* First task already counter. */
        i++;
        /* Cycle to the end of the list. */
        while( p->next != NULL )
        {
            i++;
            p = p->next;
        }
        p->next = pvPortMalloc( sizeof( sys_tcb_t ) );
        p = p->next;
    }

    if( p != NULL )
    {
        /* Memory allocated. Initialize the data structure. */
        THREAD_INIT( p );
        //( void )snprintf( thread_name, configMAX_TASK_NAME_LEN, "lwIP%d", i );

        /* Now q points to a free element in the list. */
        if( xTaskCreate( thread, name, ssize, arg, prio, &p->pid ) == pdPASS )
        {
            thread_hdl = p;
        }
        else
        {
            vPortFree( p );
        }
    }

    vPortExitCritical(  );
    return thread_hdl;
}

void
sys_arch_thread_remove( sys_thread_t hdl )
{
    sys_tcb_t      *current = tasks, *prev;
    sys_tcb_t      *toremove = hdl;
    xTaskHandle     pid = ( xTaskHandle ) 0;

    LWIP_ASSERT( "sys_arch_thread_remove: assertion hdl != NULL failed!", hdl != NULL );

    /* If we have to remove the first task we must update the global "tasks"
     * variable. */
    vPortEnterCritical(  );
    if( hdl != NULL )
    {
        prev = NULL;
        while( ( current != NULL ) && ( current != toremove ) )
        {
            prev = current;
            current = current->next;
        }
        /* Found it. */
        if( current == toremove )
        {
            /* Not the first entry in the list. */
            if( prev != NULL )
            {
                prev->next = toremove->next;
            }
            else
            {
                tasks = toremove->next;
            }
            LWIP_ASSERT( "sys_arch_thread_remove: can't remove thread with timeouts!",
                         toremove->timeouts.next == NULL );
            pid = toremove->pid;
            THREAD_INIT( toremove );
            vPortFree( toremove );
        }
    }
    /* We are done with accessing the shared datastructure. Release the
     * resources.
     */
    vPortExitCritical(  );
    if( pid != ( xTaskHandle ) 0 )
    {
        vTaskDelete( pid );
        /* not reached. */
    }
}

/*
 * Returns the thread control block for the currently active task. In case
 * of an error the functions returns NULL.
 */
sys_thread_t
sys_arch_thread_current( void )
{
    sys_tcb_t      *p = tasks;
    xTaskHandle     pid;

    pid= xTaskGetCurrentTaskHandle( );

    vPortEnterCritical(  );
    while( ( p != NULL ) && ( p->pid != pid ) )
    {
        p = p->next;
    }
    vPortExitCritical(  );
    return p;
}

/*
 * Returns a pointer to the per-thread sys_timeouts structure. In lwIP,
 * each thread has a list of timeouts which is represented as a linked
 * list of sys_timeout structures. The sys_timeouts structure holds a
 * pointer to a linked list of timeouts. This function is called by
 * the lwIP timeout scheduler and must not return a NULL value.
 *
 * In a single threaded sys_arch implementation, this function will
 * simply return a pointer to a global sys_timeouts variable stored in
 * the sys_arch module.
 */
struct sys_timeouts *
sys_arch_timeouts( void )
{
    sys_tcb_t      *ptask;

    ptask = sys_arch_thread_current(  );
    LWIP_ASSERT( "sys_arch_timeouts: ptask != NULL", ptask != NULL );
    return ptask != NULL ? &( ptask->timeouts ) : NULL;
}

/* ------------------------ Start implementation ( Semaphores ) ----------- */

/* Creates and returns a new semaphore. The "count" argument specifies
 * the initial state of the semaphore.
 */
sys_sem_t
sys_sem_new( u8_t count )
{
    xSemaphoreHandle xSemaphore;

    vSemaphoreCreateBinary( xSemaphore );
    if( xSemaphore != SYS_SEM_NULL )
    {
        if( count == 0 )
        {
            xSemaphoreTake( xSemaphore, 1 );
        }
#if 0
        #ifdef SYS_STATS
        vPortEnterCritical(  );
//        lwip_stats.sys.sem.used++;
        if( lwip_stats.sys.sem.used > lwip_stats.sys.sem.max )
        {
            lwip_stats.sys.sem.max = lwip_stats.sys.sem.used;
        }
        vPortExitCritical(  );
#endif
#endif
    }
    else
    {
        LWIP_ASSERT( "sys_sem_new: xSemaphore == SYS_SEM_NULL", xSemaphore != SYS_SEM_NULL );
    }

    return xSemaphore;
}

/* Deallocates a semaphore */
void
sys_sem_free( sys_sem_t sem )
{
    LWIP_ASSERT( "sys_sem_free: sem != SYS_SEM_NULL", sem != SYS_SEM_NULL );
    if( sem != SYS_SEM_NULL )
    {
#ifdef SYS_STATS
        vPortEnterCritical(  );
//        lwip_stats.sys.sem.used--;
        vPortExitCritical(  );
#endif
        vQueueDelete( sem );
    }
}

/* Signals a semaphore */
void
sys_sem_signal( sys_sem_t sem )
{
    LWIP_ASSERT( "sys_sem_signal: sem != SYS_SEM_NULL", sem != SYS_SEM_NULL );
    xSemaphoreGive( sem );
}

/*
 * Blocks the thread while waiting for the semaphore to be
 * signaled. If the "timeout" argument is non-zero, the thread should
 * only be blocked for the specified time (measured in
 * milliseconds).
 *
 * If the timeout argument is non-zero, the return value is the number of
 * milliseconds spent waiting for the semaphore to be signaled. If the
 * semaphore wasn't signaled within the specified time, the return value is
 * SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore
 * (i.e., it was already signaled), the function may return zero.
 *
 * Notice that lwIP implements a function with a similar name,
 * sys_sem_wait(), that uses the sys_arch_sem_wait() function.
 */
u32_t
sys_arch_sem_wait( sys_sem_t sem, u32_t timeout )
{
    portBASE_TYPE   xStatus;
    portTickType    xTicksStart, xTicksEnd, xTicksElapsed;
    u32_t           timespent;

    LWIP_ASSERT( "sys_arch_sem_wait: sem != SYS_SEM_NULL", sem != SYS_SEM_NULL );
    xTicksStart = xTaskGetTickCount(  );
    if( timeout == 0 )
    {
        do
        {
            xStatus = xSemaphoreTake( sem, MS_TO_TICKS( 100 ) );
        }
        while( xStatus != pdTRUE );
    }
    else
    {
        xStatus = xSemaphoreTake( sem, MS_TO_TICKS( timeout ) );
    }

    /* Semaphore was signaled. */
    if( xStatus == pdTRUE )
    {
        xTicksEnd = xTaskGetTickCount(  );
        xTicksElapsed = xTicksEnd - xTicksStart;
        timespent = TICKS_TO_MS( xTicksElapsed );
    }
    else
    {
        timespent = SYS_ARCH_TIMEOUT;
    }
    return timespent;
}


/* ------------------------ Start implementation ( Mailboxes ) ------------ */

/* Creates an empty mailbox. */

sys_mbox_t
sys_mbox_new( int size )
{
    xQueueHandle    mbox;

    mbox = xQueueCreate( SYS_MBOX_SIZE, /*sizeof( void * )*/size );
    if( mbox != SYS_MBOX_NULL )
    {
#if 0
    	#ifdef SYS_STATS
        vPortEnterCritical(  );
//        lwip_stats.sys.mbox.used++;
        if( lwip_stats.sys.mbox.used > lwip_stats.sys.mbox.max )
        {
            lwip_stats.sys.mbox.max = lwip_stats.sys.mbox.used;
        }
        vPortExitCritical(  );
#endif
#endif
    }
    return mbox;
}

/*
  Deallocates a mailbox. If there are messages still present in the
  mailbox when the mailbox is deallocated, it is an indication of a
  programming error in lwIP and the developer should be notified.
*/
void
sys_mbox_free( sys_mbox_t mbox )
{
    void           *msg;

    LWIP_ASSERT( "sys_mbox_free: mbox != SYS_MBOX_NULL", mbox != SYS_MBOX_NULL );
    if( mbox != SYS_MBOX_NULL )
    {
        while( uxQueueMessagesWaiting( mbox ) != 0 )
        {
            if( sys_arch_mbox_fetch( mbox, &msg, 1 ) != SYS_ARCH_TIMEOUT )
            {
                LWIP_ASSERT( "sys_mbox_free: memory leak (msg != NULL)", msg == NULL );
            }
        }
        vQueueDelete( mbox );
#if 0
        #ifdef SYS_STATS
        vPortEnterCritical(  );
//        lwip_stats.sys.mbox.used--;
        vPortExitCritical(  );
#endif
#endif
    }
}

/*
 * This function sends a message to a mailbox. It is unusual in that no error
 * return is made. This is because the caller is responsible for ensuring that
 * the mailbox queue will not fail. The caller does this by limiting the number
 * of msg structures which exist for a given mailbox.
 */
int sys_mbox_trypost(sys_mbox_t mbox, void *data)
{
    portBASE_TYPE   xQueueSent;

    /* Queue must not be full - Otherwise it is an error. */
    xQueueSent = xQueueSend( mbox, &data, 0 );
    LWIP_ASSERT( "sys_mbox_post: xQueueSent == pdPASS", xQueueSent == pdPASS );
    if (xQueueSent == pdPASS)
    {
    	return 0;
    }
    else
    {
    	return 1;
    }

}
int
sys_mbox_post( sys_mbox_t mbox, void *data )
{
    portBASE_TYPE   xQueueSent;

    /* Queue must not be full - Otherwise it is an error. */
    xQueueSent = xQueueSend( mbox, &data, 0 );
    LWIP_ASSERT( "sys_mbox_post: xQueueSent == pdPASS", xQueueSent == pdPASS );
    if (xQueueSent == pdPASS)
    {
    	return 0;
    }
    else
    {
    	return 1;
    }
}

/*
 * Blocks the thread until a message arrives in the mailbox, but does
 * not block the thread longer than "timeout" milliseconds (similar to
 * the sys_arch_sem_wait() function). The "msg" argument is a result
 * parameter that is set by the function (i.e., by doing "*msg =
 * ptr"). The "msg" parameter maybe NULL to indicate that the message
 * should be dropped.
 *
 * Note that a function with a similar name, sys_mbox_fetch(), is
 * implemented by lwIP.
 */
u32_t
sys_arch_mbox_fetch( sys_mbox_t mbox, void **msg, u32_t timeout )
{
    void           *ret_msg;
    portBASE_TYPE   xStatus;
    portTickType    xTicksStart, xTicksEnd, xTicksElapsed;
    u32_t           timespent;

    LWIP_ASSERT( "sys_arch_mbox_fetch: mbox != SYS_MBOX_NULL", mbox != SYS_MBOX_NULL );
    xTicksStart = xTaskGetTickCount(  );
    if( timeout == 0 )
    {
        do
        {
            xStatus = xQueueReceive( mbox, &ret_msg, MS_TO_TICKS( 100 ) );
        }
        while( xStatus != pdTRUE );
    }
    else
    {
        xStatus = xQueueReceive( mbox, &ret_msg, MS_TO_TICKS( timeout ) );
    }

    if( xStatus == pdTRUE )
    {
        if( msg )
        {
            *msg = ret_msg;
        }
        xTicksEnd = xTaskGetTickCount(  );
        xTicksElapsed = xTicksEnd - xTicksStart;
        timespent = TICKS_TO_MS( xTicksElapsed );
    }
    else
    {
        if( msg )
        {
            *msg = NULL;
        }
        timespent = SYS_ARCH_TIMEOUT;
    }
    return timespent;
}

u32_t
sys_jiffies( void )
{
    portTickType    xTicks = xTaskGetTickCount(  );

    return ( u32_t )TICKS_TO_MS( xTicks );
}
#endif