/*
 *  Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ 
 *  
 *  Redistribution and use in source and binary forms, with or without 
 *  modification, are permitted provided that the following conditions 
 *  are met:
 *
 *    Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer.
 *
 *    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.
 *
 *    Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 *  "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 COPYRIGHT 
 *  OWNER OR CONTRIBUTORS 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.
 *  
 */

/*
 *  ======== ClockP_nonos.c ========
 */

#include <ti/drivers/ports/ClockP.h>
#include <driverlib/systick.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

bool systick_running = 0;
bool systick_in_use = 0;
unsigned long long systick_count = 0;

typedef struct systick_inst
{
    char *name;
    ClockP_Fxn cb_fxn;
    uintptr_t cb_arg;
    unsigned int timeout_val;
    struct systick_inst *next;  
}sys_elem;

typedef struct 
{
    unsigned short num_inst;
    sys_elem *start;
}sys_ctrl_t;

static sys_ctrl_t       sys_ctrl;

#define SYSTICK_FREQ            1000
#define configCPU_CLOCK_HZ     ( ( unsigned long ) 80000000 )
#define SYSTICK_PERIOD          (configCPU_CLOCK_HZ/SYSTICK_FREQ)

void
SysTickHandler(void)
{
    //increment the systick counter
    systick_count++;

    // if there is no element in the linked list, no need to process further
    if(sys_ctrl.start == NULL)
    {
      return;
    }

    // check if the clock instance has expired
    if((sys_ctrl.start)->timeout_val <= systick_count)
    {
        sys_elem *temp = (sys_ctrl.start)->next;
        /* clock instance expired */
        /* call appropriate callback function */
        ((sys_ctrl.start)->cb_fxn)((sys_ctrl.start)->cb_arg);

        /* update the starting of the list, if not already done in the callback */
        if(temp != sys_ctrl.start)
        {
            (sys_ctrl.start)->next = NULL;
            sys_ctrl.start = temp;
            /* decrement the instances */
            sys_ctrl.num_inst--;
        }
        else
        {
            // the linked list has already been updated in callback, hence no need
            // to update
        }

        // if no active instance is there, stop the systick interrupt and reset the 
        // systick count
        if(sys_ctrl.num_inst == 0)
        {
            SysTickIntDisable();
            systick_count = 0;
            systick_in_use = 0;
        } 
    }
}

/*
 *  ======== ClockP_Params_init ========
 */
void ClockP_Params_init(ClockP_Params *params)
{
    params->name = NULL;
    params->arg = (uintptr_t)0;
}

/*
 *  ======== ClockP_create ========
 */
ClockP_Handle ClockP_create(ClockP_Fxn clockFxn, ClockP_Params *params)
{
    ClockP_Params defaultParams;
  
    if(systick_running == 0)
    {
        systick_running = 1;
        systick_in_use = 1;

        /* initialising the control structure */
        sys_ctrl.num_inst = 0;
        sys_ctrl.start = NULL;
        
        /* SysTick Enabling */
        SysTickIntRegister(SysTickHandler);
        /* setting a period of 1ms */
        SysTickPeriodSet(SYSTICK_PERIOD); 
        SysTickEnable();
    }

    /* initialise the params, if not already*/
    if (params == NULL) {
        params = &defaultParams;
        ClockP_Params_init(&defaultParams);
    }

    /* dynamically allocate space for new element */
    sys_elem *new_obj;
    new_obj = (sys_elem*)malloc(sizeof(sys_elem));

    /* populate the new clock instance */
    new_obj->name = params->name;
    new_obj->cb_fxn = clockFxn;
    new_obj->cb_arg = params->arg;
    new_obj->next = NULL;

    return ((ClockP_Handle)new_obj);

}

/*
 *  ======== ClockP_start ========
 */
ClockP_Status ClockP_start(ClockP_Handle handle, uint32_t timeout)
{
    sys_elem *new_obj = (sys_elem*)handle;
    new_obj->timeout_val = systick_count + timeout;

    /* increment the number of ective elements */
    sys_ctrl.num_inst++;

    // check if the linked list exists
    if(sys_ctrl.start != NULL)
    {
        sys_elem *temp = sys_ctrl.start;

        if(temp->timeout_val > timeout)
        {
            new_obj->next = sys_ctrl.start;
            sys_ctrl.start = new_obj;
        }
        else
        {
            while(temp->next != NULL)
            {
                if((temp->next)->timeout_val > timeout)
                {
                    new_obj->next = temp->next;
                    temp->next = new_obj;
                    break;
                }
                else
                {
                    temp = temp->next; 
                }
                
            }

            if(temp->next == NULL)
            {
                temp->next = new_obj;
                //may not be required, just for addition check
                new_obj->next = NULL;
            }
        }
    }
    else
    {
        sys_ctrl.start = new_obj;
    }

    // enable the systick, if not already    
    if(systick_in_use == 0)
    {
        systick_in_use = 1;

        // set the systick period of 1 millisecond
        SysTickPeriodSet(SYSTICK_PERIOD);

        // Enable the systick interrupt
        SysTickIntEnable();
    }
 
    return (ClockP_OK);
}

/*
 *  ======== ClockP_startFromISR ========
 */
ClockP_Status ClockP_startFromISR(ClockP_Handle handle, uint32_t timeout)
{
    return(ClockP_start(handle, timeout));
}


/*
 *  ======== ClockP_delete ========
 */
ClockP_Status ClockP_delete(ClockP_Handle handle)
{
    sys_elem *obj = (sys_elem*)handle;
    sys_elem *temp = sys_ctrl.start;
    
    if(!temp)
    {
      return (ClockP_FAILURE);
    }
    
    while((temp->next != obj) && (temp->next != NULL))
    {
        temp = temp->next;
    }
    if(temp->next == obj)
    {
        temp->next = obj->next;
    }
    free(obj);

    return (ClockP_OK);
}

/*
 *  ======== ClockP_getCpuFreq ========
 */
void ClockP_getCpuFreq(ClockP_FreqHz *freq)
{
    freq->lo = (uint32_t)configCPU_CLOCK_HZ;
    freq->hi = 0;
}

/*
 *  ======== ClockP_getSystemTickPeriod ========
 */
uint32_t ClockP_getSystemTickPeriod()
{
    return(SYSTICK_FREQ);
}

/*
 *  ======== ClockP_getSystemTicks ========
 *  TODO determine if we ever call this from an ISR
 */
uint32_t ClockP_getSystemTicks()
{
    return(systick_count);
}

/*
 *  ======== ClockP_stop ========
 */
ClockP_Status ClockP_stop(ClockP_Handle handle)
{
    sys_elem *obj = (sys_elem*)handle;
    sys_elem *temp = sys_ctrl.start;

    if(!temp) 
    {
      return (ClockP_OK);  
    }

    if(sys_ctrl.start == obj)
    {
      sys_ctrl.start = sys_ctrl.start->next;
      obj->next = NULL;

      /* decrement the instances */
      sys_ctrl.num_inst--;

      return (ClockP_OK);  
    }
    
    while((temp->next != obj) && (temp->next != NULL))
    {
        temp = temp->next;
    }
    
    if(temp->next == obj)
    {
        temp->next = obj->next;
        obj->next = NULL;
        /* decrement the instances */
        sys_ctrl.num_inst--;
    }
    else
    {
      return (ClockP_FAILURE);
    }
    return (ClockP_OK);
}

/*
 *  ======== ClockP_stopFromISR ========
 */
ClockP_Status ClockP_stopFromISR(ClockP_Handle handle)
{
    return(ClockP_stop(handle));
}
