/*
    Copyright 2000 (c) by Reto Gaehler, George Fankhauser
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Threads/TMTimer.c,v $
    Author(s):             
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2922 $
    Creation Date:         
    Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $      by: $Author: baderma $
 
*/
#include "TMTimer.h"
#include "Lock.h"

/*****************************************************************************
	TMTimer globals
*****************************************************************************/

static long int			gn32TQTimer;
static LockDesc			gLock;
static tqentptr			gpTQHead;
static tqentptr			gpFreeEvents;
static long int			gnNumOfFreeEvents;
static tqent			gTimerEvents[TIMER_MAX_EVENTS];

/*****************************************************************************
	tmTimerInit
*****************************************************************************/
void tmTimerInit()
{
  gpTQHead=0L;
  gpFreeEvents=0L;

  for (gnNumOfFreeEvents=0;gnNumOfFreeEvents<TIMER_MAX_EVENTS;gnNumOfFreeEvents++)
  {
    gTimerEvents[gnNumOfFreeEvents].tq_next=gpFreeEvents;
    gpFreeEvents=&gTimerEvents[gnNumOfFreeEvents];
  }

  gnNumOfFreeEvents=TIMER_MAX_EVENTS;
  gn32TQTimer=0L;

  lockInit(&gLock);
}

/*****************************************************************************
	tmGetTickCount
 *****************************************************************************/
long int tmGetTickCount()
{
  return gn32TQTimer;
}

/*****************************************************************************
	tmInsertElement
 *****************************************************************************/
void	tmInsertElement(tqentptr newtq)
{
  tqentptr	tq,ptq;

  if (gpTQHead == NULL)
  {
    gpTQHead = newtq;
    return;
  }
  for (ptq=0, tq=gpTQHead; tq; tq=tq->tq_next)
  {
    if (newtq->tq_timeleft < tq->tq_timeleft)
      break;
    newtq->tq_timeleft -= tq->tq_timeleft;
    ptq = tq;
  }
  newtq->tq_next = tq;
  if (ptq)
    ptq->tq_next = newtq;
  else
    gpTQHead = newtq;
  if (tq)
    tq->tq_timeleft -= newtq->tq_timeleft;
}


/*****************************************************************************
	tmTimerInterrupt
	timer interrupt service routine (called every 10ms)
 *****************************************************************************/
void tmTimerInterrupt()
{
  tqentptr	tq;

  gn32TQTimer++;

  //timer interrupt, reject if somebody is working in the queue?
  if (gLock.lockVariable==FALSE)
  {
    if (gpTQHead)
    {
      gpTQHead->tq_timeleft--;
    }
    while (gpTQHead != 0  &&  gpTQHead->tq_timeleft <= 0)
    {
      tq = gpTQHead;
      gpTQHead = gpTQHead->tq_next;
      tq->tq_fire(tq->tq_pParam,tq->tq_n32Param,tq->tq_pMsg1,tq->tq_pMsg2,tq->tq_u32Msg);
      if (tq->tq_interval>0)
      {
        tq->tq_next = NULL;
        tq->tq_entrytime = gn32TQTimer;
        tq->tq_timeleft = tq->tq_interval;

        tmInsertElement(tq);
      }
      else
      {
        //insert in free list
        tq->tq_next=gpFreeEvents;
        gpFreeEvents=tq;
        gnNumOfFreeEvents++;
      }
    }
  }
  else
  {
    //reject interrupt
  }
}


/*****************************************************************************
	tmLeftTimer
	returns the remaining time of a timer queue element
 *****************************************************************************/
int tmLeftTimer(tmCallBackProc fire,void *pParam, long n32Param,
                void *pMsg1,void *pMsg2,unsigned long u32Msg)
{
  tqentptr	tq;
  int			timeleft = 0;

  if (gpTQHead == NULL)
    return 0;
  lock(&gLock);
  for (tq = gpTQHead; tq != NULL; tq = tq->tq_next)
  {
    timeleft += tq->tq_timeleft;
    if (tq->tq_fire==fire && tq->tq_pParam==pParam && tq->tq_n32Param==n32Param
        && tq->tq_pMsg1==pMsg1 && tq->tq_pMsg2==pMsg2 && tq->tq_u32Msg==u32Msg)
    {
      unlock(&gLock);
      return timeleft;
    }
  }
  unlock(&gLock);
  return 0;
}


/*****************************************************************************
	tmClearTimer
	removes a timer queue element
 *****************************************************************************/
int tmClearTimer(	tmCallBackProc fire,void *pParam, long n32Param,
                  void *pMsg1,void *pMsg2,unsigned long u32Msg)
{
  tqentptr	prev, ptq;
  int			timespent;

  lock(&gLock);
  prev = 0;
  for (ptq = gpTQHead; ptq != NULL; ptq = ptq->tq_next)
  {
    if (ptq->tq_fire==fire && ptq->tq_pParam==pParam && ptq->tq_n32Param==n32Param
        && ptq->tq_pMsg1==pMsg1 && ptq->tq_pMsg2==pMsg2 && ptq->tq_u32Msg==u32Msg)
    {
      timespent = gn32TQTimer - ptq->tq_entrytime;
      if (prev)
        prev->tq_next = ptq->tq_next;
      else
        gpTQHead = ptq->tq_next;
      if (ptq->tq_next)
        ptq->tq_next->tq_timeleft += ptq->tq_timeleft;

      //insert in free list
      ptq->tq_next=gpFreeEvents;
      gpFreeEvents=ptq;
      gnNumOfFreeEvents++;

      unlock(&gLock);
      return timespent;
    }
    prev = ptq;
  }
  unlock(&gLock);
  return TIMER_FAILED;
}
/*****************************************************************************
	tmSetTimer
	inserts a timer queue element
 *****************************************************************************/
int tmSetTimer(	tmCallBackProc fire, void *pParam, long n32Param,
                void *pMsg1,void *pMsg2,unsigned long u32Msg,int time,Boolean bRepeat)
{
  tqentptr	newtq;

  //are any events in free list?
  if (gnNumOfFreeEvents>0)
  {
    newtq=gpFreeEvents;
    gpFreeEvents=newtq->tq_next;
    gnNumOfFreeEvents--;
  }
  else
  {
    return TIMER_FAILED;
  }
  newtq->tq_fire=fire;
  newtq->tq_timeleft=time;
  newtq->tq_pParam=pParam;
  newtq->tq_n32Param=n32Param;
  newtq->tq_pMsg1=pMsg1;
  newtq->tq_pMsg2=pMsg2;
  newtq->tq_u32Msg=u32Msg;
  newtq->tq_next = NULL;
  newtq->tq_entrytime = gn32TQTimer;
  if (bRepeat)
  {
    newtq->tq_interval=time;
  }
  else
  {
    newtq->tq_interval=0;
  }

  /* clear duplicates */
  tmClearTimer(fire,pParam,n32Param,pMsg1,pMsg2,u32Msg);

  lock(&gLock);

  newtq->tq_next = NULL;
  tmInsertElement(newtq);

  unlock(&gLock);
  return TIMER_OK;
}

