/* Source code courtesy of Denys Douchier, and used with his permission.
 * Small modifications by Alan M. Carroll
 */

/* keyboard.c process.c sysdep.c x11term.c xterm.c
 * in keyboard.c signal(SIGALRM,request_echo)
 * in process.c signal(SIGALRM,create_process_1)
 */

/* I want to use the real timer for a dual purpose.
 * On the one hand, emacs expects to be able to call
 * alarm and set a timeout. On the other hand I want
 * the user to be able to call set-timer and get
 * back an event in a specified number of milliseconds.
 *
 * At any time, the real timer is loaded with the
 * smallest request (typically the user's request will
 * be smaller, in the 100 of milliseconds range. emacs
 * requests delays in seconds).
 *
 * Whenever both emacs and the user need the timer at the
 * same time, one of the requests (the longuest) will be
 * queued. When the smaller request terminates, we need
 * to reset the timer with the remainder of the suspended
 * request.
 */

#include <sys/time.h>

static struct itimerval suspended_tv;
static int              executing_tv;

#ifdef POSIX_SIGNAL
extern sigset_t zero_sigmask;		/* sigmask with all 0's */
extern struct sigaction zero_sigaction;	/* sigaction with all 0's */

#ifndef signal
/* Rather than changing all of the signal calls directly, do so with
   the preprocessor.  This macro does not return anything.  */

#define signal(num, func) \
  { \
    struct sigaction sa; \
    sa = zero_sigaction; \
    sa.sa_handler = (void (*)()) func; \
    sigaction (num, &sa, (struct sigaction *)0); \
  }
#endif /* signal */
#endif /* POSIX_SIGNAL */


#define ALARM_FREE		0	/* no request */
#define ALARM_USER		1	/* executing user request */
#define ALARM_EMACS		2	/* executing emacs request */
#define ALARM_SUSPENDED_EMACS	3	/* executing user request, suspended emacs request */
#define ALARM_SUSPENDED_USER	4	/* executing emacs request, suspended user request */

#define TIMER_CLEAR(t) bzero(&t,sizeof(struct itimerval))

#define SET_TIMER(t,m) \
{ t.it_value.tv_sec  = m / 1000; \
  t.it_value.tv_usec = (m % 1000) * 1000; }

#define SUBTRACT_FROM_TIMER(t1,t2) \
{ t1.it_value.tv_sec  -= t2.it_value.tv_sec; \
  t1.it_value.tv_usec -= t2.it_value.tv_usec; \
  if (t1.it_value.tv_usec < 0) { t1.it_value.tv_sec--; t1.it_value.tv_usec += 1000000; }; \
  if (t1.it_value.tv_sec  < 0) t1.it_value.tv_sec = t1.it_value.tv_usec = 0; }

#define ADD_TO_TIMER(t1,t2) \
{ t1.it_value.tv_sec  += t2.it_value.tv_sec; \
  t1.it_value.tv_usec += t2.it_value.tv_usec; \
  if (t1.it_value.tv_usec > 1000000) { t1.it_value.tv_sec++; t1.it_value.tv_usec -= 1000000; }; }

#define ALARMING(t) setitimer(ITIMER_REAL,&t,0)

#define COPY_TIMER(from,to) \
{ to.it_value.tv_sec = from.it_value.tv_sec; \
  to.it_value.tv_usec = from.it_value.tv_usec; }

#define TIMER_LT(t1,t2) timercmp(&t1.it_value,&t2.it_value,<)

void
set_alarm (milliseconds,userp)
     long milliseconds;
     int userp;
{
  struct itimerval timer;
  struct itimerval otimer;

  /* first let us retrieve the time remaining in
   * the real timer and let's clear it, we don't
   * want to be interrupted here.
   */

  TIMER_CLEAR (timer);
  setitimer(ITIMER_REAL,&timer,&otimer);
  
  /* ignore negative requests */
  if (milliseconds < 0) return;

  /* let's deal with the special case where milliseconds == 0,
   * which simply means to disarm the corresponding timer.
   */

  if (! milliseconds)
    switch (executing_tv)
      {
      case ALARM_FREE:
      case ALARM_USER:
      case ALARM_EMACS:
	executing_tv = ALARM_FREE;	/* already disarmed by above call to setitimer */
	return;
      case ALARM_SUSPENDED_EMACS:
	executing_tv = ALARM_EMACS;
	ADD_TO_TIMER (suspended_tv,otimer);
	ALARMING (suspended_tv);	/* restart suspended request */
	return;
      case ALARM_SUSPENDED_USER:
	executing_tv = ALARM_USER;
	ADD_TO_TIMER (suspended_tv,otimer);
	ALARMING (suspended_tv);	/* here too */
	return;
      };

  SET_TIMER (timer,milliseconds);

  switch (executing_tv)
    {
    case ALARM_FREE:
      executing_tv = (userp) ? ALARM_USER : ALARM_EMACS;
      ALARMING (timer);
      return;

    case ALARM_SUSPENDED_EMACS:
      /* add remainder of user request to suspended request to
       * find out what really reamins of the suspended request.
       */
      ADD_TO_TIMER (suspended_tv,otimer);
      if (userp)
	/* we received a user request while another user request was
	 * still executing and an emacs request was suspended. we
	 * should override the old user request.
	 */
	if (TIMER_LT (timer,suspended_tv))
	  {
	    /* new user request is less than suspended emacs request.
	     * leave emacs request suspended.
	     */
	    SUBTRACT_FROM_TIMER (suspended_tv,timer);
	    ALARMING (timer);
	    return;
	  }
	else
	  {
	    /* new user request is longer than suspended emacs request.
	     * suspend user request, restart emacs request.
	     */
	    executing_tv = ALARM_SUSPENDED_USER;
	    COPY_TIMER (suspended_tv,otimer);
	    SUBTRACT_FROM_TIMER (timer,otimer);
	    COPY_TIMER (timer,suspended_tv);
	    ALARMING (otimer);
	    return;
	  }
      else
	/* we received a new emacs request while a user request was
	 * executing and another emacs request was suspended. we should
	 * override the old emacs request.
	 */
	if (TIMER_LT (otimer,timer))
	  {
	    /* the new emacs request is larger than the executing user
	     * request. let the user request run out.
	     */
	    SUBTRACT_FROM_TIMER (timer,otimer);
	    COPY_TIMER (timer,suspended_tv);
	    ALARMING (otimer);
	    return;
	  }
	else
	  {
	    /* the new emacs request is smaller than the executing user
	     * request. suspend user request and start emacs request.
	     */
	    executing_tv = ALARM_SUSPENDED_USER;
	    SUBTRACT_FROM_TIMER (otimer,timer);
	    COPY_TIMER (otimer,suspended_tv);
	    ALARMING (timer);
	    return;
	  }

    case ALARM_SUSPENDED_USER:
      /* add remainder of user request to suspended request to
       * find out what really reamins of the suspended request.
       */
      ADD_TO_TIMER (suspended_tv,otimer);
      if (userp)
	/* we received a user request while another user request was
	 * suspended and an emacs request was executing.
	 */
	if (TIMER_LT (timer,otimer))
	  {
	    /* new user request is less than currently executing
	     * emacs request. suspend emacs request, start user
	     * request.
	     */
	    executing_tv = ALARM_SUSPENDED_EMACS;
	    SUBTRACT_FROM_TIMER (otimer,timer);
	    COPY_TIMER (otimer,suspended_tv);
	    ALARMING (timer);
	    return;
	  }
	else
	  {
	    /* new user request is longer than currently executing
	     * emacs request. suspend user request. let emacs request
	     * run out.
	     */
	    SUBTRACT_FROM_TIMER (timer,otimer);
	    COPY_TIMER (timer,suspended_tv);
	    ALARMING (otimer);
	    return;
	  }
      else
	/* we received an emacs request while another one was running
	 * and a user request was suspended.
	 */
	if (TIMER_LT (timer,suspended_tv))
	  {
	    /* new emacs request is less than suspended user request.
	     * leave user request suspended.
	     */
	    SUBTRACT_FROM_TIMER (suspended_tv,timer);
	    ALARMING (timer);
	    return;
	  }
	else
	  {
	    /* new emacs request is longer than suspended user request.
	     * suspended emacs request. restart user request.
	     */
	    COPY_TIMER (suspended_tv,otimer);
	    SUBTRACT_FROM_TIMER (timer,otimer);
	    COPY_TIMER (timer,suspended_tv);
	    ALARMING (otimer);
	    return;
	  }

    case ALARM_USER:
      if (userp)
	{
	  /* we received a new user request while the old one was
	   * still executing. override.
	   */
	  ALARMING (timer);
	  return;
	}
      else
	/* we received an emacs request while a user request was
	 * executing.
	 */
	if (TIMER_LT (timer,otimer))
	  {
	    /* new emacs request is smaller than current user request.
	     * suspend user request. start emacs request.
	     */
	    executing_tv = ALARM_SUSPENDED_USER;
	    SUBTRACT_FROM_TIMER (otimer,timer);
	    COPY_TIMER (otimer,suspended_tv);
	    ALARMING (timer);
	    return;
	  }
	else
	  {
	    /* new emacs request is longer than current user request.
	     * suspend emacs request. continue user request.
	     */
	    executing_tv = ALARM_SUSPENDED_EMACS;
	    SUBTRACT_FROM_TIMER (timer,otimer);
	    COPY_TIMER (timer,suspended_tv);
	    ALARMING (otimer);
	    return;
	  }

    case ALARM_EMACS:
      if (! userp)
	{
	  /* we received a new emacs request while the old one was
	   * still executing. override.
	   */
	  ALARMING (timer);
	  return;
	}
      else
	/* we received a user request while an emacs request was
	 * executing.
	 */
	if (TIMER_LT (timer,otimer))
	  {
	    /* new user request is less than current emacs request.
	     * suspend emacs request. start user request.
	     */
	    executing_tv = ALARM_SUSPENDED_EMACS;
	    SUBTRACT_FROM_TIMER (otimer,timer);
	    COPY_TIMER (otimer,suspended_tv);
	    ALARMING (timer);
	    return;
	  }
	else
	  {
	    /* new user request is longer than curent emacs request.
	     * suspend user request. let emacs request run out.
	     */
	    executing_tv = ALARM_SUSPENDED_USER;
	    SUBTRACT_FROM_TIMER (timer,otimer);
	    COPY_TIMER (timer,suspended_tv);
	    ALARMING (otimer);
	    return;
	  }
    }
}

/* reset_alarm is called whenever the process receives a SIGALRM
 * signal. it means that the currently executing request has
 * run out and that the suspended request if any should be restarted
 * right away.
 */
void
reset_alarm ()
{
  if (executing_tv == ALARM_SUSPENDED_EMACS)
    {
      executing_tv = ALARM_EMACS;
      ALARMING (suspended_tv);
    }
  else if (executing_tv = ALARM_SUSPENDED_USER)
    {
      executing_tv = ALARM_USER;
      ALARMING (suspended_tv);
    }
  else
    executing_tv = ALARM_FREE;
}

/* this is emacs's interface. As far as I know, emacs is never
 * interested in the traditional return value of alarm; therefore
 * I simply return nothing and declare the function void.
 */

void
alarm (seconds) { set_alarm (seconds*1000,0); }

int timer_went_off;
int (* emacs_alarm_handler)();

#include <signal.h>

void
alarm_handler ()
{
  extern int XXpid;
  switch (executing_tv)
    {
    case ALARM_FREE: break;
    case ALARM_USER:
      timer_went_off++;
      executing_tv = ALARM_FREE;
      kill (XXpid,SIGIO);
      break;
    case ALARM_SUSPENDED_EMACS:
      timer_went_off++;
      executing_tv = ALARM_EMACS;
      ALARMING (suspended_tv);
      kill (XXpid,SIGIO);
      break;
    case ALARM_EMACS:
      executing_tv = ALARM_FREE;
      if (emacs_alarm_handler)
	(* emacs_alarm_handler) ();
      break;
    case ALARM_SUSPENDED_USER:
      executing_tv = ALARM_USER;
      ALARMING (suspended_tv);
      if (* emacs_alarm_handler)
	(* emacs_alarm_handler) ();
      break;
    }
}

void
x_init_timer ()
{
  timer_went_off = executing_tv = (int) emacs_alarm_handler = 0;
  signal (SIGALRM,alarm_handler);
}
