/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1997-2011. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 */
/* timeout.c 
 * 
 * todo: use posix timers (timer_create etc) instead of setitimer.
 * 
 */
#if !defined(__WIN32__) && !defined(VXWORKS)

/* FIXME: well, at least I can compile now... */

#include "eidef.h"

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <signal.h>

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include "erl_interface.h"
#include "erl_timeout.h"

typedef struct jmp_s {
  jmp_buf jmpbuf;
  struct itimerval timerinfo;
  void *siginfo;
  struct jmp_s *next;
} *jmp_t;

static jmp_t push(jmp_t j);
static jmp_t pop(void);
static void timeout_handler(int dummy);

jmp_buf *timeout_setup(int ms)
{
  struct itimerval t;
  jmp_t j;
  void *s;

#ifdef DEBUG
  fprintf(stderr,"timeout setup\n");
#endif
  s=signal(SIGALRM,timeout_handler);

  /* set the timer */
  t.it_interval.tv_sec = 0;
  t.it_interval.tv_usec = 0;
  t.it_value.tv_sec = ms / 1000;
  t.it_value.tv_usec = (ms % 1000) * 1000;

  /* get a jump buffer and save it */
  j = erl_malloc(sizeof(*j));
  j->siginfo = s;
  push(j);

  setitimer(ITIMER_REAL,&t,&(j->timerinfo));

  return &(j->jmpbuf);
}


int timeout_cancel(void)
{
  jmp_t j;

#ifdef DEBUG
  fprintf(stderr,"timeout cancel\n");
#endif
  /* retrieve the jump buffer */
  j=pop();
  /* restore the timer and signal disposition */
  setitimer(ITIMER_REAL,&(j->timerinfo),NULL);
  signal(SIGALRM,j->siginfo);

  free(j);

  return 0;
}

void timeout_handler(int dummy)
{
  jmp_t j;

#ifdef DEBUG
  fprintf(stderr,"timeout handler\n");
#endif

  /* retrieve the jump buffer */
  j=pop();

  /* restore the timer and signal disposition */
  setitimer(ITIMER_REAL,&(j->timerinfo),NULL);
  signal(SIGALRM,j->siginfo);

  free(j);
  longjmp(j->jmpbuf,JMPVAL);
  return; /* not reached */
}


/* a simple stack for saving the jump buffer allows us to pass a
 * variable between functions that don't call each other, in a way
 * that will survive the longjmp(). 
 */

/* FIXME problem for threaded ? */
static jmp_t jmp_head=NULL;
#ifdef DEBUG
static int depth = 0;
static int maxdepth = 0;
#endif

static jmp_t push(jmp_t j)
{
  j->next = jmp_head;
  jmp_head = j;

#ifdef DEBUG
  depth++;
  if (depth > maxdepth) maxdepth = depth;
#endif

  return j;
}

static jmp_t pop(void)
{
  jmp_t j = jmp_head;
  if (j) jmp_head = j->next;
#ifdef DEBUG
  depth--;
#endif
  return j;
}

#endif /* platform */
