/*********************************************************************
 *
 *         EZWGL, the EZ Widget and Graphics Library
 *
 *             Copyright (C) 1996, 1997  Maorong Zou
 *  
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **********************************************************************/
/*
 *  June 1996.  Beta Release.
 *  Sept 1996.  Release Version 1.0
 *  Dec 1996.  Release Version 1.1 Beta
 *  April 1997.  Release Version 1.2
 *  November 1997.  Release Version 1.3
 */
/*******************************************************************
 *
 * Timers
 */
#define _EZ_TIMER_C_

#include "EZ_Widget.h"
#include <sys/time.h>
#include <unistd.h>
#include <ctype.h>

/*******************************************************************
 *
 * Functions implemented in this file:
 */
void        EZ_InitializeTimers MY_ANSIARGS((void));
EZ_Timer    *EZ_CreateTimer MY_ANSIARGS((long sec, long usec,int repeat,
					 EZ_CallBack callback, void *pdata, int idata));
EZ_Timer    *EZ_ResetTimer MY_ANSIARGS((EZ_Timer *timer, long sec, long usec,int repeat,
					EZ_CallBack callback, void *pdata, int idata));

int         EZ_CheckRealTimers MY_ANSIARGS((long sec, long usec));
void        EZ_GetTimerState MY_ANSIARGS((EZ_Timer *timer, long *sec, long *usec,int *repeat));
void        EZ_CancelTimer MY_ANSIARGS((EZ_Timer *timer));
void        EZ_DestroyAllTimers  MY_ANSIARGS((void));
void        EZ_RestartTimer  MY_ANSIARGS((EZ_Timer *timer));
void        EZ_SetTimerPtrData  MY_ANSIARGS((EZ_Timer *timer, void *ptr));
void        EZ_SetTimerIntData  MY_ANSIARGS((EZ_Timer *timer,int data));
void        EZ_SetTimerClientData  MY_ANSIARGS((EZ_Timer *timer, void *ptr, int idata));      
void        EZ_SetTimerCallBack  MY_ANSIARGS((EZ_Timer *timer, EZ_CallBack callback, void *data));
void        *EZ_GetTimerPtrData      MY_ANSIARGS((EZ_Timer *timer));
int         EZ_GetTimerIntData MY_ANSIARGS((EZ_Timer *timer));
void        EZ_GetTimerClientData  MY_ANSIARGS((EZ_Timer *timer, void **ptr, int *idata));      
/*******************************************************************
 *
 * Local variables
 */
static EZ_Timer    RepeatingTimerList;
static EZ_Timer    UsualTimerList;
/*******************************************************************
 *
 * Local functions
 */
static EZ_Timer *find_the_timer  MY_ANSIARGS((EZ_Timer *timer));      
static void insert_timer  MY_ANSIARGS((EZ_Timer *timer, int repeat, long sec, long usec));      
/*******************************************************************
 *
 *  Initialize Timers
 */
void  EZ_InitializeTimers()
{
  EZ_Timer *tmp;

  tmp = &RepeatingTimerList;
  tmp->next = tmp;
  tmp->prev = tmp;

  tmp = &UsualTimerList;
  tmp->next = tmp;
  tmp->prev = tmp;
}
/*******************************************************************
 * 
 * destroy all timers. called only by EZ_Shutdown.
 */
void  EZ_DestroyAllTimers()
{
  EZ_Timer *head, *tmp, *chase;

  head = &RepeatingTimerList;
  tmp = head->next;
  while(tmp != head)
    {
      chase = tmp;
      tmp = tmp->next;
      (void)my_free( (char *)chase);  
    }
  head->next = head; head->prev = head; /* just incase */

  head = &UsualTimerList;
  tmp = head->next;
  while(tmp != head)
    {
      chase = tmp;
      tmp = tmp->next;
      (void)my_free( (char *)chase);  
    }
  head->next = head; head->prev = head; /* just incase */
}
/*******************************************************************
 *
 *  Create a Timer.
 */
extern void EZ_GetCurrentTime();

EZ_Timer  *EZ_CreateTimer(sec,usec,repeat,callback, pdata, idata)
     EZ_CallBack callback;
     long sec, usec;
     void      *pdata;
     int       repeat, idata;
{
  EZ_Timer *tmp;
  struct timeval ctv, *tv;
  long tsec, tusec;

  tv = &ctv;
  EZ_GetCurrentTime(tv);
  if(sec == 0 && usec == 0) return(NULL);
  
  /* allocate and initialize timer */
  if((tmp = (EZ_Timer *)my_malloc(sizeof(EZ_Timer),_TIMER_)) == NULL)
    EZ_OutOfMemory("EZ_CreateTimer");
  tsec = tv->tv_sec + sec;
  tusec = tv->tv_usec + usec;
  while(tusec >= 1000000) 
    {
      tusec -= 1000000;
      tsec  += 1;
    }
  tmp->TheTime.tv_sec = tsec;
  tmp->TheTime.tv_usec =tusec;
  tmp->sec = sec;
  tmp->usec = usec;
  (tmp->callback).callback = callback;
  (tmp->callback).data = pdata;
  (tmp->callback).internal = idata;
  tmp->repeat = repeat;
  tmp->inAction = 0;

  insert_timer(tmp, repeat, tsec, tusec);
  return(tmp);
}

static void insert_timer(tmp, repeat, tsec, tusec)
     EZ_Timer *tmp; int repeat;
     long tsec, tusec;
{
  /* insert tmp into a sorted link list */
 
  if(repeat)  /* repeating timers, put it in the end */
    {
      EZ_Timer *head, *tail;

      head = &RepeatingTimerList;
      tail = head->prev;

      tmp->next = head;
      tmp->prev = tail;
      tail->next = tmp;
      head->prev = tmp;
    }
  else  /* non-repeating timers, insert it into a sorted list */
    {
      EZ_Timer *head, *front, *chase;

      head = &UsualTimerList; 
      chase = head->next;
      while(chase != head)
	{
	  long s = chase->TheTime.tv_sec;
	  long us= chase->TheTime.tv_usec;
	  
	  if(s > tsec || (s = tsec && us >= tusec)) break;
	  chase = chase->next;
	}
      /* insert at the front of chase */
      front = chase->prev;
      
      tmp->next = chase;
      tmp->prev = front;
      front->next = tmp;
      chase->prev = tmp;
    }
}
/**********************************************************************
 *  
 *  Reset a timer if exists. Otherwise create a new timer.
 */
EZ_Timer  *EZ_ResetTimer(timer, sec,usec,repeat,callback, pdata, idata)
     EZ_Timer *timer;
     EZ_CallBack callback;
     long sec, usec;
     void      *pdata;
     int       repeat, idata;     
{
  if(find_the_timer(timer) == (EZ_Timer *)NULL)
    {
      return(EZ_CreateTimer(sec,usec,repeat,callback, pdata, idata));
    }
  else
    {
      EZ_Timer *front, *back;      
      struct timeval ctv, *tv;
      long tsec, tusec;
  
      if(sec == 0 && usec == 0)
	{
	  EZ_CancelTimer(timer);
	  return(NULL);
	}
      tv = &ctv;
      EZ_GetCurrentTime(tv);
      tsec = tv->tv_sec + sec;
      tusec = tv->tv_usec + usec;
      while(tusec >= 1000000) 
	{
	  tusec -= 1000000;
	  tsec  += 1;
	}
      /* remove timer from the timer list */
      front = timer->prev;
      back = timer->next;
      front->next = back;
      back->prev = front;

      /* reset timer */
      timer->TheTime.tv_sec = tsec;
      timer->TheTime.tv_usec =tusec;
      timer->sec = sec;
      timer->usec = usec;
      (timer->callback).callback = callback;
      (timer->callback).data = pdata;
      (timer->callback).internal = idata;
      timer->repeat = repeat;

      insert_timer(timer, repeat, tsec, tusec);
      return(timer);
    }
}
/**********************************************************************
 *  
 *  Restart a timer.
 */
void EZ_RestartTimer(timer)
     EZ_Timer *timer;
{
  if(find_the_timer(timer) == (EZ_Timer *)NULL)
    return;
  else
    {
      EZ_Timer *front, *back;      
      struct timeval ctv, *tv;
      long tsec, tusec;
      tv = &ctv;
      EZ_GetCurrentTime(tv);
      tsec = tv->tv_sec + timer->sec;
      tusec = tv->tv_usec + timer->usec;
      while(tusec >= 1000000) 
	{
	  tusec -= 1000000;
	  tsec  += 1;
	}
      /* remove timer from the timer list */
      front = timer->prev;
      back = timer->next;
      front->next = back;
      back->prev = front;

      /* reset timer */
      timer->TheTime.tv_sec = tsec;
      timer->TheTime.tv_usec =tusec;

      insert_timer(timer, timer->repeat, tsec, tusec);
    }
}
/**************************************************************
 *
 * Algorithm:
 * for repeating timers, check the list and fire the first timer
 * that has been expired. Move it to the end of the list. The order
 * is in general incorrect! 
 *
 * nonrepeating timers are sorted. Just find the first expried 
 * timer and fire it.
 */
int  EZ_CheckRealTimers(ts, tus)
     long ts,tus;
{
  EZ_Timer *chase, *head, *tail, *front, *back;
  int find = 0;

  /* repeating timers */
  head = &RepeatingTimerList;
  chase = head->next;
  while(chase != head)
    {
      long s = chase->TheTime.tv_sec;
      long us= chase->TheTime.tv_usec;

      if(s == 0 && us == 0)
	{
	  /* this timer has been cancelled */
	  front = chase->prev;
	  back = chase->next;
	  front->next = back;
	  back->prev = front;
	  (void)my_free( (char *)chase);
	  chase = back;
	  continue;
	}
      else if(s < ts || (s==ts && us <= tus)) /* fire it */
	{
	  if(chase->inAction == 0)
	    {
	      /* reset timer */
	      s  = ts + chase->sec;
	      us = tus + chase->usec;
	      while(us >= 1000000) 
		{
		  us -= 1000000;
		  s  += 1;
		}
	      chase->TheTime.tv_sec = s;
	      chase->TheTime.tv_usec = us;
	      
	      find++;

	      /* Need to block this timer from being
	       * invoked from its callback. The
	       * callback may invoke event dispatchers.
	       */
	      chase->inAction = 1;
	      if(chase->repeat > 0) chase->repeat--; 	  
	      /* fire the timer */
	      {
		EZ_CallBackStruct *f = &(chase->callback);
		if(f->callback)  (f->callback)(chase, f->data);
	      }
	      chase->inAction = 0; /* unblock */

	      /* now reset. 1. remove it from the queue */
	      front = chase->prev;
	      back = chase->next;
	      front->next = back;
	      back->prev = front;
	  
	      if(chase->repeat == 0) (void)my_free((char *)chase);
	      else /* if(chase->repeat) */
		{
		  /* 2. move it to the end of the list */
		  tail = head->prev;
		  tail->next = chase;
		  head->prev = chase;
		  chase->prev = tail;
		  chase->next = head;
		}
	    }
	  break;
	}
      chase = chase->next;
    }
  /* non-repeating timers */
  head = &UsualTimerList;
  chase = head->next;
  if(chase != head)
    {
      long s = chase->TheTime.tv_sec;
      long us= chase->TheTime.tv_usec;

      if(s == 0 && us == 0)
	{
	  /* this timer has been cancelled */
	  front = chase->prev;
	  back = chase->next;
	  front->next = back;
	  back->prev = front;
	  (void)my_free( (char *)chase);
	}
    }
  chase = head->next;
  if(chase != head)
    {
      long s = chase->TheTime.tv_sec;
      long us= chase->TheTime.tv_usec;

      if(s < ts || (s==ts && us <= tus)) /* fire it */
	{
	  if(chase->inAction == 0)
	    {
	      /* block recursive invokation */
	      chase->inAction = 1;

	      find++;
	      /* fire it */
	      {
		EZ_CallBackStruct *f = &(chase->callback);
		if(f->callback)  (f->callback)(chase, f->data);
	      }
	      /* reset: remove it to from the timer list */
	      front = chase->prev;
	      back = chase->next;
	      front->next = back;
	      back->prev = front;

	      (void)my_free((char *)chase);
	    }
	}
    }
  return(find);
}
/**************************************************************/  
void  EZ_GetTimerState(timer, sec, usec, repeat)
     EZ_Timer *timer;
     long     *sec, *usec;
     int      *repeat;
{
  if(find_the_timer(timer))
    {
      *sec = timer->sec;
      *usec = timer->usec;
      *repeat = timer->repeat;
      return;
    }
  *sec = 0; *usec = 0; *repeat = 0;
}
/**************************************************************/  
static EZ_Timer *find_the_timer(timer)
     EZ_Timer *timer;
{
  EZ_Timer *head, *tmp;

  head = &RepeatingTimerList;
  tmp = head->next;
  while(tmp != head)
    {
      if(tmp == timer) return(tmp);
      tmp = tmp->next;
    }
  head = &UsualTimerList;  
  tmp = head->next;
  while(tmp != head)
    {
      if(tmp == timer) return(timer);
      tmp = tmp->next;
    }
  return(NULL);
}
/**************************************************************/  
void EZ_CancelTimer(timer)
     EZ_Timer *timer;
{
  if(find_the_timer(timer))  
    {
      /* mark the timer that it has been cancelled */
      timer->TheTime.tv_sec = 0;
      timer->TheTime.tv_usec = 0;
    }
}
/**************************************************************/  
void  EZ_SetTimerPtrData(timer, ptr)
     EZ_Timer *timer; void *ptr;
{
  if(find_the_timer(timer) == timer)
    (timer->callback).data = ptr;
}
void EZ_SetTimerIntData(timer, idata)
      EZ_Timer *timer; int idata;
{
  if(find_the_timer(timer) == timer)
    (timer->callback).internal = idata;
}    
void EZ_SetTimerCallBack(timer, callback, data)
     EZ_Timer *timer; EZ_CallBack callback; void *data;
{
  if(find_the_timer(timer) == timer)
    {
      (timer->callback).callback = callback;
      (timer->callback).data = data;
    }
}
void *EZ_GetTimerPtrData(timer)      
     EZ_Timer *timer;
{
  if(find_the_timer(timer) == timer)
    return((timer->callback).data);
  return(NULL);
}
int EZ_GetTimerIntData(timer)
     EZ_Timer *timer;
{
  if(find_the_timer(timer) == timer)
    return((timer->callback).internal);
  return(0);
}
void EZ_GetTimerClientData(timer, p, i)      
     EZ_Timer *timer;
     void **p; int *i;
{
  if(find_the_timer(timer) == timer)
    {
      if(p) *p = (timer->callback).data;
      if(i) *i = (timer->callback).internal;
    }
  else
    {
      if(p) *p = NULL;
      if(i) *i = 0;
    }
}
void EZ_SetTimerClientData(timer, p, i)      
     EZ_Timer *timer;
     void *p; int i;
{
  if(find_the_timer(timer) == timer)
    {
      (timer->callback).data = p;
      (timer->callback).internal = i;
    }
}
/**************************************************************/
#ifdef EZ_DEBUG
void EZ_DumpTimers()
{
  EZ_Timer *head, *tmp;

  head = &RepeatingTimerList;
  printf("=========Repeadint Timers==========\n");
  tmp = head->next;
  while(head != tmp)
    {
      printf("\t %lx [%ld  %ld] repeat=%d\n", (unsigned long)tmp, tmp->sec,tmp->usec,tmp->repeat);
      tmp = tmp->next;
    }
  head = &UsualTimerList;
  printf("========NonRepeadint Timers========\n");
  tmp = head->next;
  while(head != tmp)
    {
      printf("\t %lx [%ld  %ld] repeat=%d\n", (unsigned long)tmp, tmp->sec,tmp->usec,tmp->repeat);
      tmp = tmp->next;
    }
}
#endif
/**************************************************************/  

#undef _EZ_TIMER_C_
