-- File: TimeChecker.mesa,  Last Edit: HGM  December 4, 1980  6:55 PM
-- Please don't forget to update the herald....

DIRECTORY
  InlineDefs USING [BcplToMesaLongNumber],
  Process USING [Detach, SetTimeout, SecondsToTicks],
  Runtime USING [IsBound],
  Storage USING [FreeString],
  String USING [
    AppendChar, AppendString, AppendDecimal, AppendLongDecimal, EquivalentString],
  Time USING [AppendCurrent, Current],

  CmFile USING [OpenSection, NextItem, Close],
  Put USING [Line],
  Tool USING [Create, MakeSWsProc, MakeMsgSW],
  ToolWindow USING [TransitionProcType],
  Window USING [Handle],

  Indirect USING [GetParmFileName],
  TimeServerDefs USING [PupTimeFormat],
  PupDefs USING [
    PupBuffer, GetFreePupBuffer, ReturnFreePupBuffer, PupAddress, GetPupAddress,
    PupNameTrouble, PupSocket, PupSocketDestroy, PupSocketMake, PupPackageDestroy,
    PupPackageMake, SecondsToTocks, SetPupContentsWords],
  PupTypes USING [fillInSocketID, miscSrvSoc];

TimeChecker: MONITOR
  IMPORTS
    InlineDefs, Process, Runtime, Storage, String, Time, CmFile, Put, Tool,
    Indirect, PupDefs =
  BEGIN OPEN PupDefs;

  target: STRING;
  timesAround: CARDINAL ← 0;
  msg: Window.Handle ← NIL;
  pleaseStop, running: BOOLEAN ← FALSE;

  Init: PROCEDURE =
    BEGIN
    IF ~FindParameters[] THEN RETURN;
    [] ← Tool.Create[
      name: "Time Checker of December 4, 1980"L, makeSWsProc: MakeSWs,
      clientTransition: ClientTransition];
    Process.Detach[FORK ClockWatcher[]];
    END;

  FindParameters: PROCEDURE RETURNS [ok: BOOLEAN] =
    BEGIN
    parmFileName: STRING ← NIL;
    sectionName: STRING = "TimeChecker"L;
    token, arg: STRING ← NIL;
    IF Runtime.IsBound[Indirect.GetParmFileName] THEN
      parmFileName ← Indirect.GetParmFileName[];
    IF parmFileName = NIL THEN parmFileName ← "TimeChecker.txt";
    IF ~CmFile.OpenSection[parmFileName, sectionName] THEN
      BEGIN
      Problem["Can't find [TimeChecker] section in "L, parmFileName];
      RETURN[FALSE];
      END;
    ok ← FALSE;
    DO
      [token, arg] ← CmFile.NextItem[];
      SELECT TRUE FROM
	token = NIL => EXIT;
	(token.length > 0 AND token[0] = ';) => NULL;
	String.EquivalentString[token, "Target"L] =>
	  BEGIN
	  target ← arg;
	  arg ← NIL;
	  ok ← TRUE;
	  Problem["Target is "L, target];
	  END;
	ENDCASE => Problem["Unknown parameter: "L, token];
      Storage.FreeString[token];
      Storage.FreeString[arg];
      ENDLOOP;
    CmFile.Close[parmFileName];
    RETURN[ok];
    END;

  Problem: PROCEDURE [one, two, three: STRING ← NIL] =
    BEGIN
    text: STRING = [100];
    Time.AppendCurrent[text];
    String.AppendString[text, "  TimeChecker: "L];
    String.AppendString[text, one];
    IF two # NIL THEN String.AppendString[text, two];
    IF three # NIL THEN String.AppendString[text, three];
    String.AppendChar[text, '.];
    LogString[text];
    END;

  ClockWatcher: ENTRY PROCEDURE =
    BEGIN
    pause: CONDITION; -- One minute
    who: PupAddress;
    when: LONG CARDINAL;
    running ← TRUE;
    Process.SetTimeout[@pause, Process.SecondsToTicks[60]];
    THROUGH [0..5) DO WAIT pause; ENDLOOP; -- let time get set
    PupPackageMake[];
    who ← FindAddress[];
    when ← Time.Current[];
    UNTIL pleaseStop DO
      Probe[who]; -- save frame space
      when ← when + 3600;
      THROUGH [0..120) UNTIL Time.Current[] > when DO WAIT pause; ENDLOOP;
      timesAround ← timesAround + 1;
      ENDLOOP;
    PupPackageDestroy[];
    running ← FALSE;
    END;

  FindAddress: INTERNAL PROCEDURE RETURNS [who: PupAddress] =
    BEGIN
    pause: CONDITION; -- one hour
    error: BOOLEAN ← FALSE;
    Process.SetTimeout[@pause, Process.SecondsToTicks[60*60]];
    who.socket ← PupTypes.miscSrvSoc;
    BEGIN
    ENABLE
      PupDefs.PupNameTrouble =>
	BEGIN
	text: STRING = [150];
	Time.AppendCurrent[text];
	String.AppendString[
	  text, "  TimeChecker: Troubles finding address for "L];
	String.AppendString[text, target];
	String.AppendString[text, ", "L];
	String.AppendString[text, e];
	String.AppendChar[text, '.];
	LogString[text];
	error ← TRUE;
	RETRY;
	END;
    IF error THEN WAIT pause;
    GetPupAddress[@who, target];
    END;
    END;

  Probe: PROCEDURE [who: PupAddress] =
    BEGIN
    soc: PupSocket ← PupSocketMake[
      PupTypes.fillInSocketID, who, SecondsToTocks[1]];
    b: PupBuffer;
    diff: LONG INTEGER ← 99999;
    hits: CARDINAL ← 0;
    THROUGH [0..10) DO
      b ← GetFreePupBuffer[];
      b.pupType ← dateAltoRequest;
      SetPupContentsWords[b, 0];
      soc.put[b];
      IF (b ← soc.get[]) # NIL THEN -- one sec timeout
	BEGIN
	-- maybe we should include 1/2 of the response time
	IF b.pupType = dateAltoIs THEN
	  BEGIN
	  info: LONG POINTER TO TimeServerDefs.PupTimeFormat;
	  me, him: LONG INTEGER; -- NB: not LONG CARDINAL
	  info ← LOOPHOLE[@b.pupWords];
	  me ← Time.Current[];
	  him ← InlineDefs.BcplToMesaLongNumber[info.time];
	  diff ← MIN[diff, (him - me)];
	  hits ← hits + 1;
	  END;
	ReturnFreePupBuffer[b];
	END;
      ENDLOOP;
    PupSocketDestroy[soc];
    IF hits # 0 THEN
      BEGIN
      text: STRING = [100];
      Time.AppendCurrent[text];
      SELECT TRUE FROM
	(timesAround = 0) =>
	  BEGIN String.AppendString[text, "  At the start of this run, "L]; END;
	(timesAround = 1) =>
	  BEGIN String.AppendString[text, "  After one hour, "L]; END;
	(timesAround = 24) =>
	  BEGIN String.AppendString[text, "  After one day, "L]; END;
	(timesAround = 168) =>
	  BEGIN String.AppendString[text, "  After one week, "L]; END;
	(timesAround MOD 24) = 0 =>
	  BEGIN
	  String.AppendString[text, "  After "L];
	  String.AppendDecimal[text, timesAround/24];
	  String.AppendString[text, " days, "L];
	  END;
	ENDCASE =>
	  BEGIN
	  String.AppendString[text, "  After "L];
	  String.AppendDecimal[text, timesAround];
	  String.AppendString[text, " hours, "L];
	  END;
      String.AppendString[text, target];
      String.AppendString[text, "'s clock is "L];
      String.AppendLongDecimal[text, diff];
      String.AppendString[text, " seconds faster than ours."L];
      LogString[text];
      END;
    END;

  LogString: PROCEDURE [text: STRING] =
    BEGIN IF msg # NIL THEN Put.Line[msg, text]; Put.Line[NIL, text]; END;

  MakeSWs: Tool.MakeSWsProc =
    BEGIN msg ← Tool.MakeMsgSW[window: window, lines: 5]; END;

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN IF new = inactive THEN msg ← NIL; END;

  -- initialization

  Init[];
  END.