// CrossWorks Tasking Library.
//
// Copyright (c) 2004 Rowley Associates Limited.
//
// This file may be distributed under the terms of the License Agreement
// provided with this software.
//
// THIS FILE IS PROVIDED AS IS WITH NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "ctl_api.h"
#include "ctl_impl.h"

CTL_TASK_t *ctl_task_list;            
CTL_TASK_t *ctl_task_executing;
unsigned char ctl_interrupt_count;
CTL_TIME_t ctl_current_time;
 
static void
ctl_private_add_to_task_list(CTL_TASK_t *t)
{
  if (!ctl_task_list)
    ctl_task_list = t;
  else if (ctl_task_list->priority < t->priority)
    {
      t->next = ctl_task_list;
      ctl_task_list = t;
    }
  else
    {
      CTL_TASK_t *prev = ctl_task_list;
      CTL_TASK_t *r = ctl_task_list->next;
      while (r && (r->priority >= t->priority))
        {
          prev=r;
          r=r->next;
        }
      prev->next = t;
      t->next = r;
    }      
}  
 
static CTL_TASK_t *
ctl_private_leave_task_list(void)
{
  CTL_TASK_t *t;
  if (!ctl_task_list)
    ctl_handle_error(CTL_ERROR_NO_TASKS_TO_RUN);
  if (ctl_task_list->state == CTL_STATE_RUNNABLE)
    {
      t = ctl_task_list;
      ctl_task_list = ctl_task_list->next;
    }
  else
    {
      CTL_TASK_t *prev = ctl_task_list;
      t=ctl_task_list->next;
      while (t && t->state!=CTL_STATE_RUNNABLE)
        {
          prev = t;
          t = t->next;
        }
      prev->next = t->next;
    }
  if (!t)
    ctl_handle_error(CTL_ERROR_NO_TASKS_TO_RUN);
  t->next = 0;
  return t;
}

void 
ctl_task_reschedule(void)
{
  if (ctl_task_executing->state!=CTL_STATE_RUNNABLE || (ctl_task_list && (ctl_task_executing->priority <= ctl_task_list->priority)))
    {
      CTL_TASK_t *old = ctl_task_executing;
      ctl_private_add_to_task_list(old);
      ctl_task_executing = ctl_private_leave_task_list();
      if (ctl_task_executing != old)
        ctl_private_switch_context(old, ctl_task_executing); // This will re-enable interrupts
    }
  ctl_global_interrupts_set(1);
}  

void 
ctl_task_init(CTL_TASK_t *t,              
              unsigned char priority,
              char *name)
{
  t->priority = priority;
  t->state = CTL_STATE_RUNNABLE;
  t->next = 0;
  t->name = name;  
  ctl_task_executing = t;
}  

void 
ctl_task_run(CTL_TASK_t *t,                               
             unsigned char priority,             
             void (*entrypoint)(void *),
             void *parameter,
             char *name,                      
             unsigned stack_size_in_words,
             unsigned *stack,
             unsigned call_size_in_words) 
{
  int enabled;
  t->stack_pointer = stack + stack_size_in_words;
  t->priority = priority;
  t->state = CTL_STATE_RUNNABLE;
  t->next = 0;
  t->name = name;
#ifdef CTL_TWO_STACKS
  ctl_private_init_registers(t, entrypoint, parameter, ctl_task_die, stack+call_size_in_words);
#else
  ctl_private_init_registers(t, entrypoint, parameter, ctl_task_die);
#endif
  enabled=ctl_global_interrupts_disable();
  ctl_private_add_to_task_list(t);
  if (!ctl_interrupt_count)
    ctl_task_reschedule();  
  else
    ctl_global_interrupts_set(enabled);
}   

void
ctl_task_die(void)
{
  CTL_TASK_t *old;

  ctl_global_interrupts_disable();

  // Don't die from an ISR.
  if (ctl_interrupt_count)
    ctl_handle_error(CTL_SUICIDE_IN_ISR);
  old = ctl_task_executing;
  ctl_task_executing = ctl_private_leave_task_list();
  ctl_private_switch_context(old, ctl_task_executing); // This will re-enable interrupts
}

void 
ctl_task_remove(CTL_TASK_t *task)
{
  int enabled = ctl_global_interrupts_disable();
  if (task == ctl_task_executing)
    ctl_task_die();
  else if (ctl_task_list == task)
    ctl_task_list = ctl_task_list->next;
  else
    {
      CTL_TASK_t *prev=ctl_task_list;
      CTL_TASK_t *t=ctl_task_list->next;
      while (t && t!=task)
        {
          prev = t;
          t=t->next;
        }
      if (t)
        prev->next = t->next;      
    }
  if (!ctl_interrupt_count)
    ctl_task_reschedule();
  else
    ctl_global_interrupts_set(enabled);
}

void 
ctl_task_set_priority(CTL_TASK_t *task, 
                      unsigned char priority)
{
  int enabled = ctl_global_interrupts_disable();
  task->priority = priority;
  if (!ctl_interrupt_count)
    ctl_task_reschedule();
  else
    ctl_global_interrupts_set(enabled);
}

void 
ctl_timeout_wait(CTL_TIME_t timeout)
{
  ctl_global_interrupts_disable();
  if (ctl_interrupt_count)
    ctl_handle_error(CTL_WAIT_CALLED_FROM_ISR);
  ctl_task_executing->timeout = timeout;  
  ctl_task_executing->wait_object = 0;
  ctl_task_executing->u.wait_events = 0;
  ctl_task_executing->state = CTL_STATE_TIMER_WAIT;          
  ctl_task_reschedule(); 
}

void 
ctl_exit_isr(void *savedRegisters)
{
  ctl_global_interrupts_disable();
  ctl_interrupt_count--;
  if (ctl_interrupt_count==0)
    {
      CTL_TASK_t *t=0;
      CTL_TASK_t *p=0;
      for (t=ctl_task_list; t && (t->priority > ctl_task_executing->priority); p=t,t=t->next)
        if (t->state == CTL_STATE_RUNNABLE)      
          {
            CTL_TASK_t *old = ctl_task_executing;
            if (t == ctl_task_list)          
              ctl_task_list = ctl_task_list->next;                      
            else          
              p->next = t->next;                      
            ctl_task_executing = t;
            ctl_private_add_to_task_list(old);           
            ctl_private_switch_isr_context(savedRegisters, old, ctl_task_executing);       
          }
    }
  ctl_private_isr_return(savedRegisters);
}

void 
ctl_increment_tick_from_isr(void)
{
  CTL_TASK_t *t;
  ctl_current_time++;
  for (t=ctl_task_list;t;t=t->next)
    if ((t->state & CTL_STATE_TIMER_WAIT) && (t->timeout < ctl_current_time))
      {
        t->state = CTL_STATE_RUNNABLE;
        t->timeout = 0;
      }
}

CTL_TIME_t
ctl_get_current_time(void)
{
  CTL_TIME_t t;
  int enabled = ctl_global_interrupts_disable();
  t = ctl_current_time;
  ctl_global_interrupts_set(enabled);
  return t;
}
