/* Copyright 1996 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Title:   alarm.c
 * Purpose: alarm facilities for wimp programs, using non-busy waiting
 *          for idle events
 * Author:  IDJ
 * Status:  under development
 * History:   20-Mar-89 IDJ created
 *            21-Mar-89 IDJ modified kludge to alarm_timedifference
 *            21-Mar-89 IDJ added alarm__laterthan macro
 *            08-May-91 ECN #ifndefed out unused ROM functions
 *            09-May-91 ECN use swi names instead of nos.
 *            14-Jun-91 IDJ timedifference put back in
 */

#include <stdlib.h>
#include <limits.h>
#include "alarm.h"
#include "werr.h"
#include "os.h"
#include "msgs.h"
#include "swis.h"
#include "h.verintern.messages"

#define alarm__laterthan(t1,t2) (t1 > t2)


typedef struct alarm__str
        {
           struct alarm__str *next;
           int at;
           alarm_handler proc;
           void *handle;
        } ALARM;

/* the list of pending alarms */
static ALARM *alarm__pending_list = 0;


void alarm_init(void)
{
  ALARM *p, *save_p;

  if (alarm__pending_list != 0)
  {
    p = alarm__pending_list;
    while (p != 0)
    {
      save_p = p;
      p = p->next;
      free(save_p);
    }
    alarm__pending_list = 0;
  }
}



int alarm_timenow(void)
{
  os_regset r;
  os_error *e;

  if ((e = os_swix(OS_ReadMonotonicTime, &r)) != 0)
  {
       werr (TRUE, msgs_lookup(MSGS_alarm1));
       return 0;   /* compiler likes this !*/
  }
  else
       return (r.r[0]);
}


int alarm_timedifference(int t1, int t2)
{
  /*
   * wrap-round of timer is not really a problem
   * we could just do a subtraction (since large_-ve - large_+ve == small+ve
   * but this kludge makes doubly sure (I think)
   */

   if (t1>0 && t2<0)
   {
     t1 |= 0x80000000;   /* ie. neg distance from 0 */
     t2 &= ~0x80000000;  /* ie. pos distance from 0 */
   }
   return(t2 -t1);
}


void alarm_set(int at, alarm_handler proc, void *handle)
{
  ALARM *p, *save_p, *new;

  p = alarm__pending_list;
  save_p = p;

  /* find where to put the alarm (in time) */
  while (p!=0 && alarm__laterthan(at, p->at))
  {
#ifdef TRACE
  tracef1("at = %d\n", (int)(p->handle));
#endif
    save_p = p;
    p = p->next;
  }

  /* insert new pending alarm */
  if((new = malloc(sizeof(ALARM)))==0)
    werr(TRUE, msgs_lookup(MSGS_alarm2));
  new->next = p;
  new->proc = proc;
  new->handle = handle;
  new->at = at;
  if(save_p == p)
    alarm__pending_list = new;
  else
    save_p->next = new;
}



BOOL alarm_next(int *result)
{
  if (alarm__pending_list != 0)
  {
    /* if there's a pending alarm say when it's for */
    *result = alarm__pending_list->at;
    return TRUE;
  }
  else
    return FALSE;
}



void alarm_callnext(void)
{
  ALARM *next_alarm;
  alarm_handler proc_to_call;
  int called_at;
  void *handle_to_pass;

  if (alarm__pending_list != 0)
  {
    /* save details of next alarm */
    proc_to_call = alarm__pending_list->proc;
    called_at = alarm__pending_list->at;
    handle_to_pass = alarm__pending_list->handle;

    /* farewell and adieu to the next pending alarm */
    next_alarm = alarm__pending_list;
    alarm__pending_list = alarm__pending_list->next;
    free(next_alarm);

    /* call supplied routine last (in case it goes bang!) */
    proc_to_call(called_at, handle_to_pass);
  }
}

#ifndef UROM
void alarm_remove(int at, void *handle)
{
  ALARM *p, *save_p;

  p = alarm__pending_list;
  save_p = p;

  while(p != 0)
  {
    if(p->at == at && p->handle == handle)
      break;
    else
    {
      save_p = p;
      p = p->next;
    }
  }

  if(p != 0)
  {
    if(save_p == p)
      alarm__pending_list = p->next;
    else
      save_p->next = p->next;
    free(p);
  }
}
#endif

void alarm_removeall(void *handle)
{
  ALARM *p, *save_p;

  p = alarm__pending_list;
  save_p = p;

  while(p != 0)
    if(p->handle == handle)
    {
      if(save_p == p)
      {
        alarm__pending_list = p->next;
        p = alarm__pending_list;
        free(save_p);
        save_p = p;
      }
      else
      {
        save_p->next = p->next;
        free(p);
        p = save_p->next;
      }
    }
    else
    {
      save_p = p;
      p = p->next;
    }
}

BOOL alarm_anypending(void *handle)
{
  ALARM *p = alarm__pending_list;

  while(p != 0)
  {
    if(p->handle == handle) return TRUE;
    p = p->next;
  }

  return FALSE;
}