(* Ulm's Oberon Library
   Copyright (C) 1989-1994 by University of Ulm, SAI, D-89069 Ulm, Germany
   ----------------------------------------------------------------------------
   Ulm's Oberon 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.

   Ulm's Oberon 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.
   ----------------------------------------------------------------------------
   E-mail contact: oberon@mathematik.uni-ulm.de
   ----------------------------------------------------------------------------
   $Id: Clocks.om,v 1.3 2004/02/19 15:21:17 borchert Exp $
   ----------------------------------------------------------------------------
   $Log: Clocks.om,v $
   Revision 1.3  2004/02/19 15:21:17  borchert
   Passed added including passed capability

   Revision 1.2  1996/01/04 16:50:25  borchert
   clocks are now an extension of Services.Object

   Revision 1.1  1994/02/22  20:06:13  borchert
   Initial revision

   ----------------------------------------------------------------------------
   AFB 1/92
   ----------------------------------------------------------------------------
*)

MODULE ulmClocks;

   IMPORT Disciplines := ulmDisciplines, Events := ulmEvents, Objects := ulmObjects, Op := ulmOperations, Priorities := ulmPriorities,
      RelatedEvents := ulmRelatedEvents, Services := ulmServices, Times := ulmTimes;

   TYPE
      Clock* = POINTER TO ClockRec;

   CONST
      settime* = 0; timer* = 1; passed* = 2;
   TYPE
      CapabilitySet* = SET; (* OF [settime..passed] *)
   TYPE
      GetTimeProc* = PROCEDURE (clock: Clock; VAR time: Times.Time);
      SetTimeProc* = PROCEDURE (clock: Clock; time: Times.Time);
      PassedProc* = PROCEDURE (clock: Clock; time: Times.Time) : BOOLEAN;
      TimerOnProc* = PROCEDURE (clock: Clock; time: Times.Time;
			       event: Events.Event);
      TimerOffProc* = PROCEDURE (clock: Clock);
      GetPriorityProc* = PROCEDURE (clock: Clock;
				   VAR priority: Priorities.Priority);
      Interface* = POINTER TO InterfaceRec;
      InterfaceRec* =
	 RECORD
	    (Objects.ObjectRec)
	    gettime*: GetTimeProc;
	    settime*: SetTimeProc;
	    passed*: PassedProc;
	    timeron*: TimerOnProc;
	    timeroff*: TimerOffProc;
	    getpriority*: GetPriorityProc;
	 END;

   TYPE
      ClockRec* =
	 RECORD
	    (Services.ObjectRec)
	    if: Interface;
	    caps: CapabilitySet;
	 END;
   VAR
      clockType: Services.Type;

   TYPE
      StaticClock = POINTER TO StaticClockRec;
      StaticClockRec =
	 RECORD
	    (ClockRec)
	    time: Times.Time;
	    timerOn: BOOLEAN;
	    timer: Times.Time;
	    event: Events.Event;
	 END;
   VAR
      staticClockType: Services.Type;

   VAR
      system*: Clock; (* the clock of the operating system *)

   CONST
      cannotSetTime* = 0; (* SetTime not implemented *)
      cannotSetTimer* = 1; (* timer not implemented *)
      errorcodes* = 2;
   TYPE
      ErrorEvent* = POINTER TO ErrorEventRec;
      ErrorEventRec* =
	 RECORD
	    (Events.EventRec)
	    errorcode*: SHORTINT;
	 END;
   VAR
      errormsg*: ARRAY errorcodes OF Events.Message;
      error*: Events.EventType;

   PROCEDURE Error(clock: Clock; code: SHORTINT);
      VAR
	 event: ErrorEvent;
   BEGIN
      NEW(event);
      event.type := error;
      event.message := errormsg[code];
      event.errorcode := code;
      RelatedEvents.Raise(clock, event);
   END Error;

   PROCEDURE InitErrorHandling;
   BEGIN
      errormsg[cannotSetTime] := "SetTime not implemented for this clock";
      errormsg[cannotSetTimer] := "timer not implemented for this clock";
      Events.Define(error);
      Events.SetPriority(error, Priorities.liberrors);
   END InitErrorHandling;

   PROCEDURE Init*(clock: Clock; if: Interface; caps: CapabilitySet);
      VAR
	 type: Services.Type;
   BEGIN
      Services.GetType(clock, type);
      ASSERT(type # NIL);
      ASSERT(if.gettime # NIL);
      ASSERT(~(passed IN caps) OR (if.passed # NIL));
      ASSERT(~(settime IN caps) OR (if.settime # NIL));
      IF timer IN caps THEN
	 ASSERT((if.timeron # NIL) & (if.timeroff # NIL) &
	    (if.getpriority # NIL));
      END;
      clock.if := if;
      clock.caps := caps;
      RelatedEvents.QueueEvents(clock);
   END Init;

   PROCEDURE Capabilities*(clock: Clock) : CapabilitySet;
   BEGIN
      RETURN clock.caps
   END Capabilities;

   PROCEDURE GetTime*(clock: Clock; VAR time: Times.Time);
   BEGIN
      clock.if.gettime(clock, time);
   END GetTime;

   PROCEDURE SetTime*(clock: Clock; time: Times.Time);
   BEGIN
      IF settime IN clock.caps THEN
	 clock.if.settime(clock, time);
      ELSE
	 Error(clock, cannotSetTime);
      END;
   END SetTime;

   PROCEDURE Passed*(clock: Clock; time: Times.Time) : BOOLEAN;
      VAR
	 currentTime: Times.Time;
   BEGIN
      IF passed IN clock.caps THEN
	 RETURN clock.if.passed(clock, time)
      ELSE
	 GetTime(clock, currentTime);
	 RETURN Op.Compare(currentTime, time) >= 0
      END;
   END Passed;

   PROCEDURE TimerOn*(clock: Clock; time: Times.Time; event: Events.Event);
   BEGIN
      IF timer IN clock.caps THEN
	 clock.if.timeron(clock, time, event);
      ELSE
	 Error(clock, cannotSetTimer);
      END;
   END TimerOn;

   PROCEDURE TimerOff*(clock: Clock);
   BEGIN
      IF timer IN clock.caps THEN
	 clock.if.timeroff(clock);
      ELSE
	 Error(clock, cannotSetTimer);
      END;
   END TimerOff;

   PROCEDURE GetPriority*(clock: Clock; VAR priority: Priorities.Priority);
      (* return Priorities.base in case of static clocks *)
   BEGIN
      IF timer IN clock.caps THEN
	 clock.if.getpriority(clock, priority);
      ELSE
	 Error(clock, cannotSetTimer);
      END;
   END GetPriority;

   (* ========= implementation of static clocks ========== *)

   PROCEDURE StaticGetTime(clock: Clock; VAR time: Times.Time);
   BEGIN
      time := clock(StaticClock).time;
   END StaticGetTime;

   PROCEDURE StaticSetTime(clock: Clock; time: Times.Time);
   BEGIN
      WITH clock: StaticClock DO
	 clock.time := time;
	 IF clock.timerOn & (Op.Compare(clock.time, clock.timer) >= 0) THEN
	    clock.timerOn := FALSE;
	    Events.Raise(clock.event);
	 END;
      END;
   END StaticSetTime;

   PROCEDURE StaticTimerOn(clock: Clock; time: Times.Time; event: Events.Event);
   BEGIN
      WITH clock: StaticClock DO
	 IF Op.Compare(time, clock.time) < 0 THEN
	    Events.Raise(event);
	 ELSE
	    clock.timerOn := TRUE;
	    clock.timer := time;
	    clock.event := event;
	 END;
      END;
   END StaticTimerOn;

   PROCEDURE StaticTimerOff(clock: Clock);
   BEGIN
      WITH clock: StaticClock DO
	 clock.timerOn := FALSE;
      END;
   END StaticTimerOff;

   PROCEDURE StaticGetPriority(clock: Clock; VAR priority: Priorities.Priority);
   BEGIN
      priority := Priorities.base;
   END StaticGetPriority;

   PROCEDURE CreateStaticClock*(VAR clock: Clock);
      VAR
	 if: Interface;
	 staticClock: StaticClock;
   BEGIN
      NEW(staticClock);
      Services.Init(staticClock, staticClockType);
      Times.Create(staticClock.time, Times.absolute);
      staticClock.timerOn := FALSE;
      NEW(if);
      if.gettime := StaticGetTime;
      if.settime := StaticSetTime;
      if.timeron := StaticTimerOn;
      if.timeroff := StaticTimerOff;
      if.getpriority := StaticGetPriority;
      Init(staticClock, if, {settime, timer});
      clock := staticClock;
   END CreateStaticClock;

BEGIN
   InitErrorHandling;
   Services.CreateType(clockType, "Clocks.Clock", "");
   Services.CreateType(staticClockType, "Clocks.StaticClock", "Clocks.Clock");
   (* system is hopefully re-initialized by another module which interfaces
      the real system clock
   *)
   CreateStaticClock(system);
END ulmClocks.
