-- Laurel subsystem - chat program --

-- chat.mesa

-- Mike Schroeder,  3-Mar-81 12:19:07
-- Edited by Brotz, January 16, 1981  11:56 AM

DIRECTORY
   ImageDefs,
   InlineDefs,
   IODefs,
   LaurelExecDefs,
   ProcessDefs,
   PupDefs,
   PupStream,
   PupTypes,
   Stream,
   StreamDefs,
   TimeDefs;

Chat: MONITOR

IMPORTS ImageDefs, InlineDefs, IODefs, LaurelExecDefs, ProcessDefs, PupStream,
   Stream, StreamDefs, TimeDefs =

BEGIN

OPEN IODefs;


ServerToUser: PROCEDURE =

BEGIN

char: CHARACTER;
serverName: STRING ← [32];
addr: PupDefs.PupAddress;
serverStream: Stream.Handle ← NIL;
keyStream: StreamDefs.StreamHandle = GetInputStream[];
displayStream: StreamDefs.StreamHandle = GetOutputStream[];

setPageLength: Stream.SubSequenceType = 3;
timingMark: Stream.SubSequenceType = 5;
timingMarkReply: Stream.SubSequenceType = 6;

  UserToServer: PROCEDURE [login: BOOLEAN] =
  BEGIN
  char: CHARACTER;
    BEGIN ENABLE ABORTED, PupStream.StreamClosing => GOTO return;
    --Stream.SetSST[serverStream, setPageLength]; 
    --Stream.PutByte[serverStream, 255];  kludge to minimize BELLS from Juniper
    IF login THEN
      BEGIN
        credential: STRING ← [40];
        SendStringToServer["Login "];
        LaurelExecDefs.GetUserCredentials[name: credential]; --gets name--
        SendStringToServer[credential];
        SendStringToServer[" "L];
        credential.length ← 0;
        LaurelExecDefs.GetUserCredentials[password: credential]; --gets password--
        SendStringToServer[credential];
        SendStringToServer[" 
"L];
        END;
      DO
        char ← ReadChar[];
        DO
          Stream.PutChar[serverStream, char];
          IF keyStream.endof[keyStream] THEN EXIT;
          char ← ReadChar[];
          ENDLOOP;
        Stream.SendNow[serverStream];
        ENDLOOP;
    EXITS return => NULL;
    END;
  END;


  AcceptFromServer: PROC[login: BOOLEAN, serverStream: Stream.Handle] =
  BEGIN
    userToServer: PROCESS = FORK UserToServer[login];
    DO
      ENABLE
        BEGIN
        ABORTED, PupStream.StreamClosing =>
          { NotifyState[starting]; ProcessDefs.Abort[userToServer] };
        UNWIND => JOIN userToServer-- join outside the Pup pkg monitor! --;
        END;
      buffer: STRING ← [200];
      why: Stream.CompletionCode;
      mySST: Stream.SubSequenceType;
      [buffer.length, why, mySST] ←
        Stream.GetBlock[serverStream, [@buffer.text, 0, buffer.maxlength]];
      WriteString[buffer];
      IF why = sstChange AND mySST = timingMark
        THEN Stream.SetSST[serverStream, timingMarkReply];
      ENDLOOP;
  END;


  SendStringToServer: PROCEDURE[s: STRING] =
  BEGIN
  i: CARDINAL;
    FOR i IN [0 .. s.length) DO
      Stream.PutChar[serverStream, s[i]];
      ENDLOOP;
    Stream.SendNow[serverStream];
  END;


  WriteHostPrompt: PROCEDURE[c: STRING] =
  BEGIN
    WriteChar[CR];
    WriteString[c];
    WriteString[" to host: "L];
  END;


  WriteCRStringHost: PROCEDURE[s: STRING] =
  BEGIN
    WriteChar[CR];
    WriteString[s];
    WriteString[serverName];
  END;


  FinishStringWithErrorMsg: PROCEDURE[errorMsg: STRING] =
  BEGIN
    IF errorMsg # NIL THEN
      BEGIN
        WriteString[": "L];
        WriteString[errorMsg];
      END;
    WriteChar['.];
  END;

  WriteHerald: PROCEDURE =
  BEGIN
    time: STRING ← [25];
    TimeDefs.AppendDayTime[time, TimeDefs.UnpackDT[ImageDefs.BcdVersion[].time]];
    WriteChar[CR];
    WriteString["Laurel Chat of "L];
    WriteLine[time];
    WriteString["(CTRL DEL closes connection and returns to Chat command level.)"L];
  END;  -- of WriteHerald --


LaurelExecDefs.MakeMenuCommandCallable[newMail];
LaurelExecDefs.MakeMenuCommandCallable[user];
LaurelExecDefs.MakeMenuCommandCallable[mailFile];
LaurelExecDefs.MakeMenuCommandCallable[display];
LaurelExecDefs.MakeMenuCommandCallable[delete];
LaurelExecDefs.MakeMenuCommandCallable[undelete];
LaurelExecDefs.MakeMenuCommandCallable[moveTo];
LaurelExecDefs.MakeMenuCommandCallable[copy];
WriteHerald[];
DO
  ENABLE
    BEGIN
      ABORTED =>
        BEGIN
          NotifyState[starting];
          IF serverStream # NIL THEN
            BEGIN
              WriteCRStringHost["Closing connection to "L];
              WriteChar['.];
            END;
          LOOP;
        END;
      PupStream.StreamClosing =>
        BEGIN
          NotifyState[starting];
          IF serverStream # NIL THEN
            BEGIN
              WriteCRStringHost["Connection closed by "L];
              FinishStringWithErrorMsg[text];
            END;
          keyStream.reset[keyStream]; 
          LOOP;
        END;
    END;
    IF serverStream # NIL THEN
      BEGIN
        serverStream.delete[serverStream];
        serverStream ← NIL;
      END;
    DO
      WriteString["
C(onnect to), L(ogin to), or Q(uit)? "L];
      char ← InlineDefs.BITOR[40B, ReadChar[]];
      WriteChar[char];
      SELECT char FROM
        'c => WriteHostPrompt["Connect"L];
        'l => WriteHostPrompt["Login"L];
        'q => {WriteChar[CR]; WriteChar[CR]};
        ENDCASE => LOOP;
      EXIT;
      ENDLOOP;
    IF char = 'q THEN EXIT;
    ReadID[serverName ! 
      Rubout =>
        BEGIN WriteString[" ... XXX"L]; LOOP; END;
      LineOverflow =>
        BEGIN WriteString[" ... name too long!"L]; keyStream.reset[keyStream]; LOOP; END ];
    NotifyState[running];
    WriteString[" ... "L];
    addr.socket ← PupTypes.telnetSoc; -- default value
    PupStream.GetPupAddress[@addr, serverName ! PupStream.PupNameTrouble =>
      BEGIN
        NotifyState[starting];
        WriteString["Can't find host name"L];
        FinishStringWithErrorMsg[e];
        keyStream.reset[keyStream];
        LOOP;
      END];
    serverStream ← PupStream.PupByteStreamCreate
      [addr, PupDefs.veryLongWait ! PupStream.StreamClosing =>
        BEGIN
          NotifyState[starting];
          WriteString["Can't connect to host"L];
          FinishStringWithErrorMsg[text];
          keyStream.reset[keyStream];
          LOOP;
        END];
    Stream.SetInputOptions
      [serverStream, Stream.InputOptions[TRUE,FALSE,FALSE,FALSE,FALSE]];
    WriteLine["open."L];
    AcceptFromServer[char = 'l, serverStream];
    ENDLOOP;
  NotifyState[quitTyped];
  RETURN;
END;

State: TYPE = {starting, running, quitTyped};
serverToUserState: State ← starting;

NotifyState: ENTRY PROCEDURE [s: State] =
BEGIN
  IF s = running AND serverToUserState # running THEN StreamDefs.ResetControlDEL[];
  serverToUserState ← s;
  NOTIFY pause;
END;


WaitForQuit: ENTRY PROCEDURE =
BEGIN
UNTIL serverToUserState = quitTyped DO
  IF StreamDefs.ControlDELtyped[] AND serverToUserState = running THEN
    BEGIN
      StreamDefs.ResetControlDEL[];
      ProcessDefs.Abort[serverToUser];
    END;
  WAIT pause;
  ENDLOOP;
END;


-- main program for chat

pause: CONDITION; 
serverToUser: PROCESS;

ProcessDefs.SetTimeout[@pause, ProcessDefs.MsecToTicks[250]];
serverToUser ← FORK ServerToUser[];
WaitForQuit[];
JOIN serverToUser;

END.