IMPLEMENTATION MODULE Logger;

	(********************************************************)
	(*							*)
	(*		Logging data to disk file		*)
	(*							*)
	(*  Programmer:		P. Moylan			*)
	(*  Last edited:	17 August 1994			*)
	(*  Status:		Working				*)
	(*							*)
	(********************************************************)

(* Depending on what device you are using for logging, you may need	*)
(* to import only one of the following two modules.			*)

IMPORT Floppy;
(*IMPORT HardDisk;*)

FROM Files IMPORT
    (* type *)	File,
    (* proc *)	OpenFile, CloseFile, WriteByte;

FROM IOErrorCodes IMPORT
    (* type *)	ErrorCode,
    (* proc *)	TranslateErrorCode;

FROM Keyboard IMPORT
    (* proc *)	InKey, PutBack;

FROM MaintenancePages IMPORT
    (* type *)	MaintenancePage,
    (* proc *)	CreateMaintenancePage, Associate;

FROM Windows IMPORT
    (* type *)	Window, Colour, FrameType, DividerType,
    (* proc *)	OpenWindow, CloseWindow, WriteString, WriteLn, EditString,
		SetCursor, PressAnyKey, WriteChar;

FROM NumericIO IMPORT
    (* proc *)	ReadBufferedCardinal, WriteRJCard;

FROM Semaphores IMPORT
    (* type *)	Semaphore,
    (* proc *)	CreateSemaphore, Wait, Signal;

FROM LossyQueues IMPORT
    (* type *)	LossyQueue,
    (* proc *)	CreateQueue, PutQueue, GetQueue;

FROM TaskControl IMPORT
    (* proc *)	CreateTask;

FROM Timer IMPORT
    (* proc *)	Sleep;

FROM TerminationControl IMPORT
    (* proc *)	SetTerminationProcedure;

FROM Mouse IMPORT
    (* proc *)	MouseAvailable;

FROM UserInterface IMPORT
    (* type *)	UIWindow, Capability, CapabilitySet,
    (* proc *)	AllowMouseControl;

(************************************************************************)

CONST testing = TRUE;
VAR Mpage: MaintenancePage;
    heading, debug: Window;

CONST
    ElementCapacity = 4;

TYPE

    (* For a lossy queue element, there are two special cases:	*)
    (*		count = -1	means open the log file		*)
    (*		count = 0	means close the log file	*)

    LossyQueueElement = RECORD
			    count: INTEGER;
			    datum: ARRAY [0..ElementCapacity-1] OF INTEGER;
			END (*RECORD*);

VAR

    (* The file to which data are being written.	*)

    log: File;

    (* The name of the log file.	*)

    filename: ARRAY [0..31] OF CHAR;

    (* The queue holding the data to be sent to the output file.	*)

    Lossy: LossyQueue;

    (* A screen window which is displayed while logging is in progress.	*)

    LogWindow: Window;

    (* Semaphore to control access to the log window.	*)

    LogWindowAccess: Semaphore;

    (* Second log window for testing purposes.  *)

    Log2Window: Window;

    (* Semaphore to synchronise file opening.	*)

    FileOpenHandled: Semaphore;

    (* Logging=TRUE while data logging is in progress.  AbortRequested	*)
    (* can be set to TRUE to request that logging be terminated.	*)

    Logging, AbortRequested: BOOLEAN;

    (* A count of the number of data values yet to be collected.	*)

    LogCount: CARDINAL;

    (* Count of data which could not be sent to disk because the output	*)
    (* queue overflowed.						*)

    DataLost: CARDINAL;

    (* Count of output errors.  We abort logging if it gets too large.	*)

    ErrorCount: CARDINAL;

    (* Flags involved in shutting down the module.	*)

    ShutDownDesired: BOOLEAN;
    ShutDownCompleted: Semaphore;

(************************************************************************)
(*			    ERROR MESSAGE HANDLER			*)
(************************************************************************)

PROCEDURE ReportError (code: ErrorCode);

    (* Puts out a screen message about the I/O error, stops logging if	*)
    (* error count gets too large.					*)

    CONST ErrorLimit = 20;

    VAR ErrorWindow: Window;  dummy: UIWindow;
	messbuffer: ARRAY [0..39] OF CHAR;

    BEGIN
	OpenWindow (ErrorWindow, white, blue, 14, 17, 22, 58,
					doubleframe, nodivider);
	IF MouseAvailable() THEN
	    dummy := AllowMouseControl (ErrorWindow, "Disk error message",
				CapabilitySet {wshow, wmove, whide});
	END (*IF*);
	WriteString (ErrorWindow, "Disk error ");
	TranslateErrorCode (code, messbuffer);
	WriteString (ErrorWindow, messbuffer);
	INC (ErrorCount);
	IF ErrorCount > ErrorLimit THEN
	    WriteLn (ErrorWindow);
	    WriteString (ErrorWindow, "Too many errors, aborting logging.");
	    ErrorCount := 0;  AbortRequested := TRUE;
	END (*IF*);
	PressAnyKey (ErrorWindow);  CloseWindow (ErrorWindow);
	IF testing THEN
	    WriteLn (debug);
	    WriteString (debug, "Return from ReportError");
	END (*IF*);
    END ReportError;

(************************************************************************)
(*				FILE OUTPUT				*)
(************************************************************************)

PROCEDURE PutChar (ch: CHAR);

    (* Sends one character to the log file.	*)

    VAR errorcode: ErrorCode;

    BEGIN
	IF testing THEN
	    IF ch = CHR(13) THEN
		WriteLn (Log2Window);
	    ELSIF ch <> CHR(10) THEN
		WriteChar (Log2Window, ch);
	    END (*IF*);
	END (*IF*);
	errorcode := WriteByte (log, ch);
	IF errorcode <> OK THEN
	    ReportError (errorcode);
	END (*IF*);
    END PutChar;

(************************************************************************)

PROCEDURE PutInteger (value: INTEGER);

    (* Converts value to ASCII, sends it to the log file.	*)

    BEGIN
	IF value < 0 THEN
	    PutChar ("-");  value := -value;
	END (*IF*);
	IF value > 9 THEN
	    PutInteger (value DIV 10);
	    value := value MOD 10;
	END (*IF*);
	PutChar (CHR(ORD("0")+ORD(value)));
    END PutInteger;

(************************************************************************)
(*		     SENDING QUEUED DATA TO THE LOG FILE		*)
(************************************************************************)

PROCEDURE LogTask;

    (* Takes data from the lossy queue, converts it to ASCII and sends	*)
    (* it to the log file.  Data received while the log file is closed	*)
    (* (this is possible, if logging is aborted) are discarded.		*)

    CONST CR = CHR(13);  LF = CHR(10);

    VAR j: CARDINAL;  status: ErrorCode;
	buffer: LossyQueueElement;
	LogFileOpen: BOOLEAN;
	w: Window;  dummy: UIWindow;

    BEGIN
	IF testing THEN
	    OpenWindow (Log2Window,white,red,15,24,0,47,simpleframe,nodivider);
	    Associate (Log2Window, Mpage);
	    WriteString (Log2Window, "Log2Window opened.");
	    WriteLn (Log2Window);
	    OpenWindow (w,white,black, 11,14,0,79,simpleframe,nodivider);
	    Associate (w, Mpage);
	    IF MouseAvailable() THEN
		dummy := AllowMouseControl (Log2Window, "Log task testing #2",
				CapabilitySet {wshow, wmove, whide});
		dummy := AllowMouseControl (w, "Log task progress",
				CapabilitySet {wshow, wmove, whide});
	    END (*IF*);
	    WriteString (w, "LogTask started.");  WriteLn (w);
	END (*IF*);
	LogFileOpen := FALSE;
	LOOP	(* until file closed and ShutDownDesired *)
	    GetQueue (Lossy, buffer);
	    IF testing THEN
		WriteLn(w);
		WriteString(w, "Return from GetQueue.");
	    END (*IF*);
	    WITH buffer DO
		IF (count = -1) THEN
		    IF testing THEN
			WriteString(w, ".. count is -1");
		    END (*IF*);
		    ErrorCount := 0;
		    IF NOT LogFileOpen THEN
			IF testing THEN
			    WriteLn (w);
			    WriteString(w, "About to open log file");
			END (*IF*);
			status := OpenFile (log, filename, TRUE);
			IF testing THEN
			    WriteLn (w);
			    WriteString(w, "Have opened log file");
			END (*IF*);
			IF status = OK THEN
			    LogFileOpen := TRUE;
			ELSE
			    ReportError (status);  AbortRequested := TRUE;
			END (*IF*);
		    END (*IF*);
		    Signal (FileOpenHandled);
		ELSIF count = 0 THEN
		    IF testing THEN
			WriteString(w, ".. count is zero");
		    END (*IF*);
		    IF LogFileOpen THEN
			IF testing THEN
			    WriteLn(w);
			    WriteString(w, "Closing log file.");
			END (*IF*);
			CloseFile (log);  LogFileOpen := FALSE;
		    END (*IF*);
		    IF ShutDownDesired THEN
			IF testing THEN
			    WriteLn(w);
			    WriteString(w, "Stopping LogTask.");
			END (*IF*);
			EXIT (*LOOP*);
		    END (*IF*);
		ELSIF LogFileOpen THEN
		    FOR j := 0 TO CARDINAL(count)-1 DO
			PutInteger (datum[j]);
			PutChar (" ");
		    END (*FOR*);
		    PutChar (CR);  PutChar (LF);
		ELSE
		    AbortRequested := TRUE;
		END (*IF*);
	    END (*WITH*);
	END (*LOOP*);
	IF testing THEN
	    WriteLn(w);
	    WriteString(w, "Left LogTask main loop.");
	END (*IF*);
	IF testing THEN
	    WriteLn(w);
	    WriteString(w, "LogTask is terminating.");
	    CloseWindow (w);
	END (*IF*);
	Signal (ShutDownCompleted);
    END LogTask;

(************************************************************************)

PROCEDURE PutSpecialBuffer (code: INTEGER);

    (* Queues a command to open or close the log file.  This procedure	*)
    (* is called only by user tasks and initialisation code.		*)

    VAR buffer: LossyQueueElement;

    BEGIN
	buffer.count := code;
	LOOP
(*
	    IF testing THEN
		WriteLn (debug);
		WriteString (debug, "Putting special buffer element.");
	    END (*IF*);
*)
	    IF PutQueue (Lossy, buffer) THEN
		EXIT (*LOOP*);
	    END (*IF*);
(*
	    IF testing THEN
		WriteString (debug, " - Put was unsuccessful.");
	    END (*IF*);
*)
	    Sleep (1);
	END (*LOOP*);
(*
	IF testing THEN
	    WriteLn (debug);
	    WriteString (debug, "Leaving procedure PutSpecialBuffer.");
	END (*IF*);
*)
    END PutSpecialBuffer;

(************************************************************************)
(*			THE USER-CALLABLE PROCEDURES			*)
(************************************************************************)

PROCEDURE StartLogging(): BOOLEAN;

    (* Opens the log file.  Returns FALSE if open failed.		*)

    CONST Return = CHR(13);

    VAR ch: CHAR;  dummy: UIWindow;

    BEGIN
	IF Logging THEN
	    Wait (LogWindowAccess);
	    SetCursor (LogWindow, 1, 1);
	    WriteString (LogWindow, "Logging is already in progress.");
	    Signal (LogWindowAccess);
	ELSE
	    AbortRequested := FALSE;
	    Wait (LogWindowAccess);
	    OpenWindow (LogWindow,white,red,0,5,0,47,simpleframe,nodivider);
	    IF MouseAvailable() THEN
		dummy := AllowMouseControl (LogWindow, "Logger status",
				CapabilitySet {wshow, wmove, whide});
	    END (*IF*);
	    WriteString (LogWindow, "Enter the name of the log file:");
	    WriteLn (LogWindow);
	    EditString (LogWindow, filename, HIGH(filename));
	    ch := InKey();
	    IF ch <> Return THEN PutBack(ch) END(*IF*);
	    WriteLn (LogWindow);
	    Signal (LogWindowAccess);
	    PutSpecialBuffer (-1);
	    Wait (FileOpenHandled);
	    IF AbortRequested THEN
		LogCount := 0
	    ELSE
		Wait (LogWindowAccess);
		WriteString (LogWindow, "Maximum number of readings to log?");
		LogCount := ReadBufferedCardinal (LogWindow, 6);
		Signal (LogWindowAccess);
		ch := InKey();
		IF ch <> Return THEN PutBack(ch) END(*IF*);
	    END (*IF*);
	    Logging := TRUE;
	    IF LogCount = 0 THEN
		StopLogging;
	    ELSE
		DataLost := 0;
		Wait (LogWindowAccess);
		SetCursor (LogWindow, 4, 1);
		WriteString (LogWindow, "Data lost:      0");
		Signal (LogWindowAccess);
	    END (*IF*);
	END (*IF*);
	RETURN Logging;
    END StartLogging;

(************************************************************************)

PROCEDURE Log (VAR (*IN*) data: ARRAY OF INTEGER);

    (* Sends the data to the log file (assumed to be already open).	*)
    (* This is done via a lossy queue.					*)

    CONST CR = CHR(13);  LF = CHR(10);

    VAR j: CARDINAL;  success: BOOLEAN;
	buffer: LossyQueueElement;

    BEGIN
	IF testing THEN
	    WriteLn (debug);
	    WriteString (debug, "Entering procedure Log");
	END (*IF*);
	IF AbortRequested THEN
	    StopLogging;
	END (*IF*);
	IF NOT Logging THEN RETURN END (*IF*);
	WITH buffer DO
	    count := HIGH(data)+1;
	    IF count > ElementCapacity THEN
		count := ElementCapacity;
	    END (*IF*);
	    FOR j := 0 TO CARDINAL(count-1) DO
		datum[j] := data[j];
	    END (*FOR*);
	END (*WITH*);
	IF testing THEN
	    WriteLn (debug);
	    WriteString (debug, "Log: calling PutQueue");
	END (*IF*);
	success := PutQueue (Lossy, buffer);
	IF testing THEN
	    WriteLn (debug);
	    WriteString (debug, "Log: return from PutQueue");
	END (*IF*);
	IF NOT success THEN
	    INC (DataLost);
	    Wait (LogWindowAccess);
	    SetCursor (LogWindow, 4, 12);
	    WriteRJCard (LogWindow, DataLost, 6);
	    Signal (LogWindowAccess);
	    RETURN;
	END (*IF*);
	DEC (LogCount);
	Wait (LogWindowAccess);
	SetCursor (LogWindow, 3, 35);  WriteRJCard (LogWindow, LogCount, 6);
	Signal (LogWindowAccess);
	IF LogCount = 0 THEN
	    StopLogging;
	END (*IF*);
	IF testing THEN
	    WriteLn (debug);
	    WriteString (debug, "Leaving procedure Log");
	END (*IF*);
    END Log;

(************************************************************************)

PROCEDURE StopLogging;

    (* Closes the log file, after writing out any pending data.		*)

    BEGIN
	IF Logging THEN
	    PutSpecialBuffer (0);
	    Wait (LogWindowAccess);
	    CloseWindow (LogWindow);
	    Signal (LogWindowAccess);
	    Logging := FALSE;  AbortRequested := FALSE;
	END (*IF*);
    END StopLogging;

(************************************************************************)
(*			     PROGRAM CLOSEDOWN				*)
(************************************************************************)

PROCEDURE CloseDown;

    BEGIN
	IF testing THEN
	    WriteLn (debug);
	    WriteString (debug, "Starting closedown of logger.");
	END (*IF*);
	StopLogging;
	ShutDownDesired := TRUE;
	PutSpecialBuffer(0);
	IF testing THEN
	    WriteLn (debug);
	    WriteString (debug, "Waiting for logger shutdown to complete.");
	END (*IF*);
	Wait (ShutDownCompleted);
	IF testing THEN
	    WriteLn (debug);
	    WriteString (debug, "Logger has shut down.");
	END (*IF*);
    END CloseDown;

(************************************************************************)
(*			   MODULE INITIALISATION			*)
(************************************************************************)

VAR dummy: UIWindow;

BEGIN
    IF testing THEN
	CreateMaintenancePage (Mpage);
	OpenWindow (heading, black, white, 0,2, 0,79, simpleframe, nodivider);
	OpenWindow (debug, black, white, 18,23, 0,79, simpleframe, nodivider);
	Associate (heading, Mpage);
	Associate (debug, Mpage);
	IF MouseAvailable() THEN
	    dummy := AllowMouseControl (debug, "Logger debugging",
				CapabilitySet {wshow, wmove, whide});
	END (*IF*);
	SetCursor (heading, 1, 22);
	WriteString (heading,"Diagnostic output from Logger module");
	WriteString (debug, "Logger: initialisation code executing");
    END (*IF*);
    Logging := FALSE;  ShutDownDesired := FALSE;  AbortRequested := FALSE;
    SetTerminationProcedure (CloseDown);
    CreateSemaphore (FileOpenHandled, 0);
    CreateSemaphore (ShutDownCompleted, 0);
    CreateSemaphore (LogWindowAccess, 1);
    filename := "A:LOG.DAT";
    CreateQueue (Lossy, 100, SIZE(LossyQueueElement));
    CreateTask (LogTask, 2, "Log task");
END Logger.