/*
 * Marko Kiiskila carnil@cs.tut.fi 
 * 
 * Copyright (c) 1996
 * Tampere University of Technology - Telecommunications Laboratory
 * All rights reserved.
 *
 * Permission to use, copy, modify and distribute this
 * software and its documentation is hereby granted,
 * provided that both the copyright notice and this
 * permission notice appear in all copies of the software,
 * derivative works or modified versions, and any portions
 * thereof, that both notices appear in supporting
 * documentation, and that the use of this software is
 * acknowledged in any publications resulting from using
 * the software.
 * 
 * TUT ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION AND DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS
 * SOFTWARE.
 * 
 */
/*
 *
 * Timer creation, setting and allocation. Protypes defined in
 * utl_os.h
 *
 * $Id: timers.c,v 1.9 1996/08/06 14:14:11 carnil Exp carnil $
 *
 */

/* Global protypes */
#include <sys/time.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>

/* Digital prototypes */
#include "g_types.h"
#include "utl_os.h"
#include "codes.h"
#include "g_event.h"

/* Own prototypes */
#include "timers.h"

#define EMOD MOD_UTL_OS
#define EINST "timers.c"
#include "emask.h"

typedef struct _Timer_t_ {
  void (*callback) (HANDLE context); /* Pointer to the routine to callback 
					when the timer expires. */
  HANDLE context; /* Value to pass to the callback routine. */
  struct timeval *alarm_time; /* Time to expire timer */
  struct _Timer_t_ *previous;
  struct _Timer_t_ *next;
} Timer_t;

static Timer_t *timerlist = NULL;

static struct timeval g_timer;

STATUS 
os_timer_alloc (OS_TIMER_CALLBACK callback, 
		HANDLE context, HANDLE *p_timer_handle)
{
  Timer_t *new;

  new = (Timer_t *)mem_alloc(EINST,sizeof(Timer_t));
  if (!new)
    return STATUS_K_RESOURCES;
  EVENT(EM_TIMER, ("Os_timer_alloc timer:%p callback:%p\n",
		   new, callback));
  new->callback = callback;
  new->context = context;
  new->alarm_time = NULL;
  new->previous = NULL;
  new->next = timerlist;
  if (timerlist)
    timerlist->previous = new;
  timerlist = new;
  *p_timer_handle = (HANDLE)new;
  return STATUS_K_SUCCESS;
}

void 
os_timer_dealloc (HANDLE timer_handle)
{
  Timer_t *old;

  assert(timer_handle);

  EVENT(EM_TIMER,("Os_timer_dealloc timer: %p\n",timer_handle));
  old = (Timer_t *)timer_handle;

  if (old->alarm_time)
    mem_free(EINST,old->alarm_time);
  if (old->previous)
    (old->previous)->next = old->next;
  if (old->next)
    (old->next)->previous = old->previous;
  if (timerlist == old)
    timerlist = old->next;
  mem_free(EINST,old);
}

STATUS 
os_timer_set (HANDLE timer_handle, UINT32 timeset)
{
  Timer_t *to_set;

  assert(timer_handle);
  EVENT(EM_TIMER,("Os_timer_set timer:%p %ld ms\n",
		  timer_handle, timeset));
  to_set = (Timer_t *)timer_handle;

  if (!(to_set->alarm_time)) {
    to_set->alarm_time = (struct timeval *)mem_alloc(EINST,
						     sizeof(struct timeval));
    if (!(to_set->alarm_time))
      return STATUS_K_RESOURCES;
  }
  gettimeofday(to_set->alarm_time, NULL);
  (to_set->alarm_time)->tv_usec = 
    (to_set->alarm_time)->tv_usec+(long)(timeset%1000)*1000;
  if ((to_set->alarm_time)->tv_usec>=1000000) {
    (to_set->alarm_time)->tv_usec-=1000000;
    (to_set->alarm_time)->tv_sec++;
  }
  (to_set->alarm_time)->tv_sec = 
    (to_set->alarm_time)->tv_sec + (long)(timeset/999);

  return STATUS_K_SUCCESS;
}

STATUS 
os_timer_cancel (HANDLE timer_handle)
{
  Timer_t *to_cancel;

  assert(timer_handle);
  EVENT(EM_TIMER,("Os_timer_cancel %p\n",timer_handle));

  to_cancel = (Timer_t *)timer_handle;

  if (to_cancel->alarm_time) {
    mem_free(EINST,to_cancel->alarm_time);
    to_cancel->alarm_time = NULL;
  }
  return STATUS_K_SUCCESS;
}

/* Returns Timer_t_ with soonest expiration time, can be
   NULL if there are no timers set */
HANDLE
timer_find_soonest(void)
{
  Timer_t *current, *soonest;

  soonest = current = timerlist;
  EVENT(EM_TIMER,("Timer_find_soonest\n"));

  while(current) {
    if (current->alarm_time) {
      if (!soonest->alarm_time ||
	  ((current->alarm_time->tv_sec <
	    soonest->alarm_time->tv_sec) ||
	   (current->alarm_time->tv_sec ==
	    soonest->alarm_time->tv_sec &&
	    current->alarm_time->tv_usec <
	    soonest->alarm_time->tv_usec))) {
	soonest = current;
      }
    }
    current = current->next;
  }
  return soonest;
}

/* Returns timers expiration time from current moment 
   (fit for select()'s time parameter), allocates memory
   for timeval, in case timer is set. If timer should already 
   have expired, structure's fields are set to zero.
   If HANDLE is NULL, returns NULL. */
struct timeval *
timer_get_expiration(HANDLE timer_handle)
{
  Timer_t *to_get;
  struct timezone tz;

  EVENT(EM_TIMER,("Timer_get_expiration\n"));
  if (!timer_handle)
    return NULL;

  to_get = (Timer_t *)timer_handle;

  if (to_get->alarm_time) {
    gettimeofday(&g_timer, &tz);
    EVENT(EM_TIMER,("G_timer:%d sec %d usec\n",
		    g_timer.tv_sec,g_timer.tv_usec));
    EVENT(EM_TIMER,("Timer  :%d sec %d usec\n",
		    to_get->alarm_time->tv_sec,
		    to_get->alarm_time->tv_usec));
    if (g_timer.tv_sec > (to_get->alarm_time)->tv_sec || 
	(g_timer.tv_sec == (to_get->alarm_time)->tv_sec &&
	 g_timer.tv_usec >= (to_get->alarm_time)->tv_usec)) {
      g_timer.tv_sec = g_timer.tv_usec = 0;
    } else {
      g_timer.tv_sec = (to_get->alarm_time)->tv_sec - g_timer.tv_sec;
      if (g_timer.tv_usec > (to_get->alarm_time)->tv_usec) {
	g_timer.tv_sec--;
	g_timer.tv_usec = 
	  1000000 + (to_get->alarm_time)->tv_usec - g_timer.tv_usec;
      } else
	g_timer.tv_usec = 
	  (to_get->alarm_time)->tv_usec - g_timer.tv_usec;
    }
    EVENT(EM_TIMER,("Return:%d sec %d usec\n",g_timer.tv_sec,g_timer.tv_usec));
    return &g_timer;
  } else
    return NULL;
}

/* Calls timer's callback function */
void
timer_call_callback(HANDLE timer_handle)
{
  Timer_t *to_call;

  EVENT(EM_TIMER,("Timer_call_callback timer:%lx\n",(long)timer_handle));
  assert(timer_handle);

  to_call = (Timer_t *)timer_handle;

  mem_free(EINST, to_call->alarm_time);
  to_call->alarm_time = NULL;

  if (to_call->callback)
    to_call->callback(to_call->context);
}

void 
timer_usage(void)
{
  Timer_t *tmp;

  printf("Timer usage:\n");
  for(tmp=timerlist;tmp;tmp=tmp->next)
    printf("\tCallback %p Context:%p %d sec %d usec\n",
	   tmp->callback, tmp->context, 
	   tmp->alarm_time?tmp->alarm_time->tv_sec:0,
	   tmp->alarm_time?tmp->alarm_time->tv_usec:0);
}

/*
 *
 * $Log: timers.c,v $
 * Revision 1.9  1996/08/06 14:14:11  carnil
 * Cleaning up
 *
 * Revision 1.8  1996/07/07 11:51:47  carnil
 * Global_msgmask
 *
 * Revision 1.7  1996/04/25 19:42:13  carnil
 * Copyright notice
 *
 * Revision 1.6  1996/02/29 11:24:12  carnil
 * *** empty log message ***
 *
 * Revision 1.5  1996/02/16 06:16:20  carnil
 * oshandle,cmhandle removed
 *
 * Revision 1.4  1996/02/06  11:23:41  carnil
 * callback calling, select time
 *
 * Revision 1.3  1996/01/29  09:30:43  carnil
 * mem_alloc, mem_free
 *
 * Revision 1.2  1996/01/23  10:02:12  carnil
 * Debug info added
 *
 * Revision 1.1  1996/01/19  06:21:43  carnil
 * Initial revision
 *
 *
 */
