-- Copyright (C) 1983  by Xerox Corporation. All rights reserved. 
-- CornPopper.mesa, HGM, 26-Nov-83  1:23:02

DIRECTORY
  Ascii USING [ControlA, CR, LF, SP],
  Process USING [Pause, SecondsToTicks],
  Stream USING [Delete, GetChar, Handle, PutChar, PutString, SendNow, TimeOut],
  String USING [AppendString, AppendChar],
  System USING [
    AdjustGreenwichMeanTime, GetClockPulses, GetGreenwichMeanTime,
    gmtEpoch, GreenwichMeanTime, Pulses, PulsesToMicroseconds],
  Time USING [Pack, Unpack, Unpacked],
  
  PopCorn USING [ErrorType],
  PupStream USING [PupByteStreamCreate, SecondsToTocks, StreamClosing],
  PupTypes USING [PupAddress];
  
  CornPopper: PROGRAM
  IMPORTS Process, Stream, String, System, Time, PupStream
  EXPORTS PopCorn =
  BEGIN
  
  Error: PUBLIC ERROR [why: PopCorn.ErrorType, text: LONG STRING] = CODE;
  
  GetClockOffset: PUBLIC PROCEDURE [where: PupTypes.PupAddress, tries: CARDINAL]
    RETURNS [msDiff: LONG INTEGER, msDelay: LONG CARDINAL] =
    BEGIN
    sh: Stream.Handle ← NIL;
    IF System.GetGreenwichMeanTime[] = System.gmtEpoch THEN
      ERROR Error[spare1, "Clock not set"L];
    BEGIN ENABLE
      BEGIN
      PupStream.StreamClosing =>
        BEGIN
        SELECT why FROM
          transmissionTimeout => ERROR Error[timeout, "Timeout during transmission"L];
          remoteReject => ERROR Error[rejecting, text];
          ENDCASE => ERROR Error[pupConfusion, text];
        END;
      Stream.TimeOut => ERROR Error[timeout, "Stream Timeout"L];
      UNWIND => IF sh # NIL THEN Stream.Delete[sh];
      END;
    sh ← PupStream.PupByteStreamCreate[where, PupStream.SecondsToTocks[5]];
    IF sh # NIL THEN
      BEGIN
      hits: CARDINAL ← 0;
      err: PopCorn.ErrorType;
      string: STRING = [200];
      Stream.PutString[sh, "B2400"L];  -- DLS: Set Baud Rate
      Stream.PutChar[sh, Ascii.CR];
      Stream.PutChar[sh, 'C];  -- DLS: Connect
      Stream.PutChar[sh, 'R];  -- Box: Reset everything (send every second)
      Stream.SendNow[sh];
      Process.Pause[Process.SecondsToTicks[2]];
      Stream.PutChar[sh, 'T];  -- Box: Send current time (once)
      Stream.SendNow[sh];
      [] ← FlushThePipe[sh];  -- Skip over DLS header and partial lines
      Stream.PutString[sh, "F000:00:00:00.000Q"L];  -- Box: Set Format
      msDelay ← LAST[LONG CARDINAL];
      FOR i: CARDINAL IN [0..20) UNTIL hits = tries DO
        thisDelta, thisDelay: LONG CARDINAL;
        [thisDelta, thisDelay] ← PokeBox[sh ! Error =>
	  BEGIN
	  err ← why;
	  string.length ← 0;
	  String.AppendString[string, text];
	  LOOP;
	  END ];
	hits ← hits + 1;
	IF thisDelay < msDelay THEN
	  BEGIN
	  msDelay ← thisDelay;
	  msDiff ← thisDelta;
	  END;
	ENDLOOP;
      IF hits = 0 THEN ERROR Error[err, string];
      Stream.PutChar[sh, 'R];  -- Box: Reset everything
      Stream.SendNow[sh];
      Stream.Delete[sh];
      sh ← NIL;
      END;
    END;
    END;
  
  FlushThePipe: PROCEDURE [sh: Stream.Handle] RETURNS [hit: BOOLEAN] =
    BEGIN
    n: CARDINAL ← 0;
    DO
      c: CHARACTER;
      c ← Stream.GetChar[sh ! Stream.TimeOut => EXIT];
      n ← n + 1;
      IF n > 1000 THEN ERROR Error [cantParse, "Box stuck talking"L];
      ENDLOOP;
    RETURN[n > 0];
    END;
    
  PokeBox: PROCEDURE [sh: Stream.Handle] 
    RETURNS [msDiff: LONG INTEGER, msDelay: LONG CARDINAL] =
    BEGIN
    start, stop: System.Pulses;
    local, remote, first: System.GreenwichMeanTime;
    diff: LONG INTEGER;
    string: STRING = [40];
    days, hours, minutes, seconds, miliseconds: CARDINAL;
    slop: LONG CARDINAL;
    IF FlushThePipe[sh] THEN ERROR Error[cantParse, "Unexpected characters in pipeline"L];
    start ← System.GetClockPulses[];
    first ← System.GetGreenwichMeanTime[];
    DO  -- Wait until clock ticks
      temp: System.Pulses ← System.GetClockPulses[];
      local ← System.GetGreenwichMeanTime[];
      stop ← System.GetClockPulses[];
      IF first # local THEN EXIT;
      start ← temp;
      ENDLOOP;
    slop ← System.PulsesToMicroseconds[[stop-start]]/1000 + 1;
    -- This sometimes hangs in WaitToSend.  Maybe probeCounter is confusing things
    Stream.PutChar[sh, 'T];  -- Box: Send current time (once more)
    Stream.SendNow[sh];
    DO
      c: CHARACTER ← Stream.GetChar[sh !
        Stream.TimeOut => ERROR Error[cantParse, "Timeout while collecting answer"L]];
      IF c = Ascii.CR THEN LOOP;
      IF c = Ascii.LF THEN EXIT;
      IF string.length = string.maxlength THEN ERROR Error[cantParse, "Too many characters"L];
      String.AppendChar[string, c];
      ENDLOOP;
    stop ← System.GetClockPulses[];
    msDelay ← System.PulsesToMicroseconds[[stop-start]]/1000 + 1 + slop;
    -- Format should be ↑Addd:hh:mm:ss.sssx  (x might be ?)
    IF string.length < 18 THEN ERROR Error[cantParse, "18 characters expected"L];
    IF string[0] # Ascii.ControlA THEN ERROR Error[cantParse, "↑A expected"L];
    IF string[17] # Ascii.SP THEN
      BEGIN
      temp: STRING = [50];
      String.AppendString[temp, "Not Locked ("L];
      String.AppendString[temp, string];
      SELECT string[17] FROM
        '? => String.AppendString[temp, " => +/- 500ms"L];
        '# => String.AppendString[temp, " => +/- 50ms"L];
        '* => String.AppendString[temp, " => +/- 5ms"L];
        '. => String.AppendString[temp, " => +/- 1ms"L];
        ENDCASE => String.AppendString[temp, " = unknown quality indication"L];
      String.AppendChar[temp, ')];
      ERROR Error[notLocked, temp];
      END;
    days ← ExtractDecimal[string, 1, 3];
    hours ← ExtractDecimal[string, 5, 2];
    minutes ← ExtractDecimal[string, 8, 2];
    seconds ← ExtractDecimal[string, 11, 2];
    miliseconds ← ExtractDecimal[string, 14, 3];
    remote ← [
      StartOfThisYear[local, days] +
      (days -1) * 86400 +  -- Box thinks days are like Fortran
      hours * LONG[3600] +
      minutes * 60 +
      seconds ];
    diff ← remote - local;
    IF ABS[diff] > 2*3600 THEN ERROR Error[localClockWayOff, "Local clock way off"L];
    msDiff ← diff * 1000 + miliseconds;
    BEGIN  -- Correct for transmission time of RS232 characters
    msPerCharacter: CARDINAL = 4;  -- 2400 baud
    msDiff ← msDiff - msPerCharacter;  -- Box strobes time at end of "T"
    msDelay ← msDelay - (1 + 20) * msPerCharacter;
    END;
    END;
    
  ExtractDecimal: PROCEDURE [s: STRING, offset, digits: CARDINAL] RETURNS [n: CARDINAL] =
    BEGIN
    n ← 0;
    FOR i: CARDINAL IN [offset..offset + digits) DO
      c: CHARACTER = s[i];
      SELECT c FROM
        IN ['0..'9] => n ← n*10 + c-'0;
	ENDCASE => ERROR Error[cantParse, "Decimal digit expected"L];
      ENDLOOP;
    END;
  
  StartOfThisYear: PROCEDURE [
    local: System.GreenwichMeanTime, days: CARDINAL] RETURNS [System.GreenwichMeanTime] =
    BEGIN
    temp: Time.Unpacked;
    fudge: LONG INTEGER = 90*86400; 
    SELECT days FROM
      IN [0..90] => local ← System.AdjustGreenwichMeanTime[local, fudge];
      IN [365-90..366] => local ← System.AdjustGreenwichMeanTime[local, -fudge];
      ENDCASE;
    temp ← Time.Unpack[local];
    temp.month ← 0;
    temp.day ← 1;
    temp.hour ← 0;
    temp.minute ← 0;
    temp.second ← 0;
    temp.dst ← FALSE;
    temp.zone ← [west, 0, 0, 366, 366];
    RETURN[Time.Pack[temp, FALSE]];
    END;
    
  END.