/*
   Copyright 2001 (c) by Lukas Ruf,
   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/TMTimerNG.c,v $
   Author(s):            Lukas Ruf
   Affiliation:          ETH Zuerich, TIK
   Version:              $Revision: 2922 $
   Creation Date:        2001/01/29
   Last Date of Change:  $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $      by: $Author: baderma $
 */

#include "MMHeapMemory.h"
#include "tm-include.h"
#include "Topsy.h"
#include "Threads.h"
#include "Messages.h"
#include "Lock.h"

#include "TMTimerNG.h"

typedef struct tmTQL_s
{
  struct tmTQL_s *next;         /* list of delta */
  struct tmTQL_s *vert;         /* list of events to be processed if expired */
  unsigned long  delta;         /* delta to previous */
  ThreadId       threadId;      /* thread to call when timer expires */
  unsigned long  timerId;       /* id of the timer element */
}
tmTQL_t;

static tmTQL_t *tmTQL = NULL;   /* tm Timer Queue List */
static LockDesc tmLock;
static unsigned long tmTimerId;

void tmTimerInit()
{
  lockInit(&tmLock);
  tmTimerId = 0;
}

/* Error tmTimerInsert(unsigned long deltaTime, ThreadId id,
 *                     unsigned long *timerId);
 * Inserts a thread into the Timer Queue of pending start times
 *
 * Parameters:
 *   deltaTime : Nr. Timer Interrupts to be passed until the threads will be sent
 *               an event.
 *   id        : ThreadId of the Thread to send a message to.
 *   timerId   : will identify the timer element.  Wrap around after 2^32-1 (on 32bit archs)
 */
Error tmTimerInsert(unsigned long deltaTime, ThreadId id,
                    unsigned long *timerId)
{
  tmTQL_t *xxTQL;
  tmTQL_t *ttTQL = (tmTQL_t *)NULL;
  tmTQL_t *yyTQL;

  Error    lError = TIMER_INSERTFAILED;

  if (lockTry(&tmLock))
  {
    xxTQL = tmTQL;
    /* look for element which:
     * - doesn't exist or 
     * - fires later
     */
    while (xxTQL && deltaTime > xxTQL->delta)
    {
      deltaTime -= xxTQL->delta;
      ttTQL      = xxTQL;
      xxTQL      = xxTQL->next;
    }

    if (hmAlloc((Address *)&yyTQL, sizeof(tmTQL_t)) == HM_ALLOCOK)
    {

      yyTQL->delta      = deltaTime;
      yyTQL->vert       = NULL;
      yyTQL->next       = NULL;
      yyTQL->threadId   = id;
      *timerId    = ++tmTimerId;
      yyTQL->timerId    = *timerId;

      if (xxTQL)  /* if the new one doesn't fire later than all the existing ones */
        if (deltaTime < xxTQL->delta)
        { /* if the new one fires earlier */
          xxTQL->delta -= deltaTime;
          yyTQL->next   = xxTQL;
        }
        else if (deltaTime == xxTQL->delta)
        { /* if the new one fires "simultaneously" */
          yyTQL->vert = xxTQL;      /* pre-pend the new one "vertically" */
          yyTQL->next = xxTQL->next;
          xxTQL->next = NULL;
          /* do not panic if the delta is not adjusted in case the inserted element
           * will be the first one.  The firing does not take care of the "vertical"
           * deltas.
           */
        }

      /* adjust the list: set the correct predecessor */
      if (ttTQL)     /* if it is not the first element */
        ttTQL->next = yyTQL;
      else           /* it is the first element */
        tmTQL       = yyTQL;
      lError = TIMER_OK;
    }

    unlock(&tmLock);
  }
  return lError;
}

Error tmTimerFire()
{
  Error    lError = TIMER_EXPIREFAILED;
  tmTQL_t *xxTQL  = tmTQL;
  tmTQL_t *yyTQL;

  Message  expiredMsg;
  if (lockTry(&tmLock))
  {

    if (tmTQL && --tmTQL->delta == 0)
    {
      tmTQL        = tmTQL->next;

      while (xxTQL)
      {
        expiredMsg.id   = TM_TIMEREXPIRED;
        expiredMsg.from = TMTHREADID;
        expiredMsg.msg.tmTimerExpired.timerId = xxTQL->timerId;

        CHECK((lError = kSend(xxTQL->threadId, &expiredMsg)) == TM_OK,
              "tmTimerFire(): kSend failed");

        yyTQL  = xxTQL;
        xxTQL  = xxTQL->vert;
        CHECK((lError = hmFree(yyTQL)) == HM_FREEOK,
              "tmTimerFire(): hmFree failed");
      }
      lError = TIMER_OK;
    }
    unlock(&tmLock);

  }

  return lError;
}

Error tmTimerRemove(ThreadId threadId, unsigned int timerId)
{
  Error    lError = TIMER_CLEARFAILED;
  tmTQL_t *xxTQL  = tmTQL;    /* horizontal */
  tmTQL_t *yyTQL;             /* vertical   */
  tmTQL_t *txTQL  = NULL;     /* horizontal helper */
  tmTQL_t *tyTQL;             /* vertical helper   */

  Boolean found   = FALSE;

  if (lockTry(&tmLock))
  {

    while (xxTQL && !found)
    {
      yyTQL = xxTQL;
      tyTQL = NULL;
      found = yyTQL->timerId == timerId && yyTQL->threadId == threadId;
      while (yyTQL && !found)
      {
        tyTQL = yyTQL;
        found = yyTQL->timerId == timerId && yyTQL->threadId == threadId;
        yyTQL = yyTQL->vert;
      }
      if (!found)
      {
        txTQL = xxTQL;
        xxTQL = xxTQL->next;
      }
    }
    if (found)
    {
      lError = TIMER_OK;

      /* condition: yyTQL points to the element to be removed             */
      /*            xxTQL points to the first element of the column       */
      if (!txTQL && !tyTQL)
      { /* the very first element                   */
        if (yyTQL->vert)
        {    /* if there are vertical appendices         */
          tmTQL->vert->next = tmTQL->next;
          tmTQL             = tmTQL->vert;
        }
        else
          tmTQL             = tmTQL->next;
      }
      else
        if ( txTQL && !tyTQL)
        { /* the first element vertically             */
          if (yyTQL->vert)
          {    /* if there are vertical appendices         */
            txTQL->next       = yyTQL->vert;
            yyTQL->vert->next = yyTQL->next;
          }
          else
            txTQL->next       = yyTQL->next;
        }
        else
          if (!txTQL &&  tyTQL)
          { /* an element somewhere in the first column */
            tyTQL->vert         = yyTQL->vert;
          }
          else
            if ( txTQL &&  tyTQL)
            { /* an element somewhere                     */
              tyTQL->vert         = yyTQL->vert;
            }
      hmFree(yyTQL);  /* this does not free the message buffer!           */
    }

    unlock(&tmLock);

  }

  return lError;
}

