-- File: FindGateways.mesa,  Last Edit: HGM  March 24, 1981  1:10 PM
-- Please don't forget to update the herald....

DIRECTORY
  InlineDefs USING [BcplLongNumber, BcplToMesaLongNumber],
  Process USING [Detach, Yield],
  Storage USING [Node, String, Free, FreeString],
  String USING [AppendChar, AppendLongNumber, AppendNumber, AppendString],
  Time USING [AppendCurrent, Current],

  Event USING [Item, Reason, AddNotifier],
  Format USING [], -- Needed by Put.Number and Put.Date
  FormSW USING [
    AllocateItemDescriptor, ClientItemsProcType, CommandItem,
    ItemHandle, newLine, NumberItem, ProcType, StringItem],
  MsgSW USING [Post],
  Put USING [Char, CR, Decimal, Text, Line, Number, LongDecimal, LongNumber],
  Tool USING [
    Create, MakeSWsProc, UnusedLogName, MakeMsgSW, MakeFormSW, MakeFileSW,
    AddThisSW],
  ToolWindow USING [TransitionProcType, DisplayProcType, CreateSubwindow],
  Window USING [Handle, Box, DisplayData, DisplayInvert, DisplayWhite],

  GateControlDefs USING [
    gateControlStatsSend, gateControlStatsAck, GateControlStatsEntry],
  PupDefs USING [
    PupPackageMake, PupPackageDestroy, GetFreePupBuffer,
    ReturnFreePupBuffer, PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake,
    defaultNumberOfNetworks, GetHopsToNetwork, SecondsToTocks,
    SetPupContentsWords, AppendPupAddress, AppendHostName, AppendErrorPup,
    GetPupAddress, PupNameTrouble],
  PupTypes USING [PupAddress, fillInSocketID];

FindGateways: PROGRAM
  IMPORTS
    InlineDefs, Process, Storage, String, Time,
    Event, FormSW, MsgSW, Put, Tool, ToolWindow, Window,
    PupDefs =
  BEGIN OPEN PupDefs, PupTypes;

  herald: STRING = "Find Gateways of March 19, 1981";

  msg, form, boxes, log: Window.Handle;
  eventItem: Event.Item ← [eventMask: 177777B, eventProc: Broom];

  defaultMaxHops: CARDINAL = 3;
  pleaseStop: BOOLEAN ← FALSE;
  running: BOOLEAN ← FALSE;
  indicator: {left, right, off} ← off;
  first: Gateway ← NIL;
  maxHops: CARDINAL ← defaultMaxHops;
  where: PupAddress ← [[0], [0], [31415, 9265]];
  target: STRING ← NIL;

  Gateway: TYPE = POINTER TO GatewayObject;
  GatewayObject: TYPE = RECORD [
    next: Gateway,
    where: PupTypes.PupAddress,
    text: STRING];

  ScanCircle: FormSW.ProcType =
    BEGIN
    IF running THEN
      BEGIN MsgSW.Post[msg, "Somebody is already running..."L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    running ← TRUE;
    Process.Detach[FORK ScanSeveral[]];
    END;

  ScanTarget: FormSW.ProcType =
    BEGIN
    IF running THEN
      BEGIN MsgSW.Post[msg, "Somebody is already running..."L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Finding Gateways on "L];
    IF ~FindPath[] THEN RETURN;
    running ← TRUE;
    Process.Detach[FORK ScanOne[]];
    END;

  Stop: FormSW.ProcType = BEGIN Off[]; END;

  Off: PROCEDURE =
    BEGIN
    IF ~running THEN RETURN;
    pleaseStop ← TRUE;
    WHILE running DO Process.Yield[]; ENDLOOP;
    pleaseStop ← FALSE;
    END;

  FindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    WriteString[target];
    WriteChar['=];
    GetPupAddress[
      @where, target !
      PupNameTrouble =>
	BEGIN MsgSW.Post[msg, e]; WriteLine[e]; GOTO Trouble; END];
    PrintPupAddress[where];
    WriteLine["."L];
    RETURN[TRUE];
    EXITS Trouble => RETURN[FALSE];
    END;

  ScanSeveral: PROCEDURE =
    BEGIN
    first: BOOLEAN ← TRUE;
    SetupBoxes[];
    FOR net: CARDINAL IN [1..PupDefs.defaultNumberOfNetworks) UNTIL pleaseStop DO
      IF GetHopsToNetwork[[net]] > maxHops THEN LOOP;
      where ← [[net], [0], [31415, 9265]];
      IF first THEN Put.Text[log, "  Searching network "]
      ELSE Put.Text[log, ", "];
      Put.Number[log, net, [8, FALSE, TRUE, 0]];
      Put.Char[log, '(];
      Put.Decimal[log, GetHopsToNetwork[[net]]];
      Put.Char[log, ')];
      ScanSingle[];
      first ← FALSE;
      ENDLOOP;
    Put.CR[log];
    SetDownBoxes[];
    PrintGatewayList[];
    DeleteList[];
    running ← FALSE;
    END;

  ScanOne: PROCEDURE =
    BEGIN
    SetupBoxes[];
    ScanSingle[];
    SetDownBoxes[];
    PrintGatewayList[];
    DeleteList[];
    running ← FALSE;
    END;

  ScanSingle: PROCEDURE =
    BEGIN
    sequenceNumber: CARDINAL ← GetNextSequenceNumber[];
    soc: PupSocket ← PupSocketMake[fillInSocketID, where, SecondsToTocks[5]];
    FOR i: CARDINAL IN [0..5) DO
      b: PupBuffer ← GetFreePupBuffer[];
      b.pupID.a ← 27182;
      b.pupID.b ← sequenceNumber;
      b.pupType ← GateControlDefs.gateControlStatsSend;
      SetPupContentsWords[b, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
	SELECT TRUE FROM
	  ((b.pupType = GateControlDefs.gateControlStatsAck)
          AND (b.pupID.b = sequenceNumber)) =>
	    BEGIN
	    gse: LONG POINTER TO GateControlDefs.GateControlStatsEntry;
            info: Gateway ← FindEntry[b.source];
	    gse ← LOOPHOLE[@b.pupBody];
	    FOR i: CARDINAL IN [0..gse.versionText.length) DO
	      String.AppendChar[info.text, gse.versionText.char[i]]; ENDLOOP;
	    AppendUpTime[info.text, gse.startTime];
	    FlipBoxes[];
	    END;
	  ENDCASE =>
	    BEGIN
	    temp: STRING = [100];
	    PupDefs.AppendErrorPup[temp, b];
	    MsgSW.Post[msg, temp];
	    END;
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF b # NIL THEN ReturnFreePupBuffer[b];
      ENDLOOP;
    PupSocketDestroy[soc];
    END;

  AppendUpTime: PROCEDURE [s: STRING, startTime: InlineDefs.BcplLongNumber] =
    BEGIN
    now, then: LONG INTEGER;
    sec: LONG INTEGER;
    min: LONG INTEGER;
    hours: LONG INTEGER;
    String.AppendString[s, " up "L];
    then ← InlineDefs.BcplToMesaLongNumber[startTime];
    now ← Time.Current[];
    sec ← now - then;
    IF sec<0 THEN
      BEGIN  -- Startup glitch
      sec ← -sec;
      String.AppendChar[s, '-];
      END;
    hours ← sec/3600;
    sec ← sec - hours*3600;
    min ← sec/60;
    sec ← sec - min*60;
    String.AppendLongNumber[s, hours, 10];
    String.AppendChar[s, ':];
    IF min < 10 THEN String.AppendChar[s, '0];
    String.AppendLongNumber[s, min, 10];
    String.AppendChar[s, ':];
    IF sec < 10 THEN String.AppendChar[s, '0];
    String.AppendLongNumber[s, sec, 10];
    END;

  FindEntry: PROCEDURE [where: PupTypes.PupAddress] RETURNS [new: Gateway] =
    BEGIN
    finger: Gateway ← NIL;
    FOR new ← first, new.next UNTIL new = NIL DO
      IF where = new.where THEN
	BEGIN
        new.text.length ← 0;
	RETURN;
	END;
      IF LessPupAddress[new.where, where] THEN finger ← new;
      ENDLOOP;
    new ← Storage.Node[SIZE[GatewayObject]];
    new↑ ← [next: NIL, where: where, text: Storage.String[100]];
    SELECT TRUE FROM
      first = NIL => first ← new; -- first
      finger = NIL =>
        BEGIN new.next ← first; first ← new; END;  -- insert at front of list
      ENDCASE =>
        BEGIN new.next ← finger.next; finger.next ← new; END;  -- middle or end
    END;

  LessPupAddress: PROCEDURE [a, b: PupAddress] RETURNS [BOOLEAN] =
    BEGIN
    IF a.net < b.net THEN RETURN[TRUE];
    IF a.net > b.net THEN RETURN[FALSE];
    IF a.host < b.host THEN RETURN[TRUE];
    IF a.host > b.host THEN RETURN[FALSE];
    IF a.socket.a < b.socket.a THEN RETURN[TRUE];
    IF a.socket.a > b.socket.a THEN RETURN[FALSE];
    IF a.socket.b < b.socket.b THEN RETURN[TRUE];
    IF a.socket.b > b.socket.b THEN RETURN[FALSE];
    RETURN[FALSE];
    END;

  DeleteList: PROCEDURE =
    BEGIN
    gate: Gateway ← first;
    UNTIL gate = NIL DO
      next: Gateway ← gate.next;
      Storage.FreeString[gate.text];
      Storage.Free[gate];
      gate ← next;
      ENDLOOP;
    first ← NIL;
    END;

  PrintGatewayList: PROCEDURE =
    BEGIN
    gate: Gateway ← first;
    Put.CR[log];
    Put.Line[log, "  Name and Address              Text"L];
    FOR gate: Gateway ← first, gate.next UNTIL gate = NIL DO
      where: PupAddress ← [[gate.where.net], [gate.where.host], [0,0]];
      temp: STRING = [50];
      PupDefs.AppendHostName[temp, where];
      String.AppendString[temp, " = "L];
      AppendPupAddress[temp, where];
      String.AppendString[temp, "  "L];
      Put.Text[log, temp];
      FOR i: CARDINAL IN [temp.length..30) DO Put.Char[log, ' ]; ENDLOOP;
      Put.Text[log, gate.text];
      Put.CR[log];
      ENDLOOP;
    END;

  nextSequenceNumber: CARDINAL ← 123;
  GetNextSequenceNumber: PROCEDURE RETURNS [CARDINAL] =
    BEGIN RETURN[nextSequenceNumber ← nextSequenceNumber + 1]; END;


  -- IO things

  WriteChar: PROCEDURE [c: CHARACTER] = BEGIN Put.Char[log, c]; END;

  WriteCR: PROCEDURE = BEGIN Put.CR[log]; END;

  WriteString: PROCEDURE [s: STRING] = BEGIN Put.Text[log, s]; END;

  WriteLine: PROCEDURE [s: STRING] = BEGIN Put.Line[log, s]; END;

  WriteLongDecimal: PROCEDURE [n: LONG CARDINAL] =
    BEGIN Put.LongDecimal[log, n]; END;

  WriteDecimal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 10, 0]; END;

  WriteOctal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 8, 0]; END;

  WriteNumber: PROCEDURE [n, radix, width: CARDINAL] = INLINE
    BEGIN
    temp: STRING = [25];
    String.AppendNumber[temp, n, radix];
    THROUGH [temp.length..width) DO WriteChar[' ]; ENDLOOP;
    WriteString[temp];
    END;

  D8: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 8]; END;

  O3: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END;

  O6: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END;

  O9: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 9]; END;

  WriteCurrentDateAndTime: PROCEDURE =
    BEGIN time: STRING = [20]; Time.AppendCurrent[time]; WriteString[time]; END;

  PrintPupAddress: PROCEDURE [a: PupAddress] =
    BEGIN temp: STRING = [40]; AppendPupAddress[temp, a]; WriteString[temp]; END;

  indicatorBox: Window.Box = [[25, 10], [16, 16]];
  DisplayBoxes: ToolWindow.DisplayProcType =
    BEGIN
    pattern: ARRAY [0..1] OF ARRAY [0..8) OF WORD;
    left: WORD = 177400B;
    right: WORD = 000377B;
    SELECT indicator FROM
      left => pattern ← [ALL[left], ALL[right]];
      right => pattern ← [ALL[right], ALL[left]];
      off => pattern ← [ALL[0], ALL[0]];
      ENDCASE;
    Window.DisplayData[window, indicatorBox, @pattern, 1]
    END;

  SetupBoxes: PROCEDURE = BEGIN indicator ← left; DisplayBoxes[boxes]; END;

  FlipBoxes: PROCEDURE =
    BEGIN
    SELECT indicator FROM
      left => indicator ← right;
      off, right => indicator ← left;
      ENDCASE;
    Window.DisplayInvert[boxes, indicatorBox];
    END;

  SetDownBoxes: PROCEDURE =
    BEGIN indicator ← off; Window.DisplayWhite[boxes, indicatorBox]; END;

  MakeBoxesSW: PROCEDURE [window: Window.Handle] =
    BEGIN
    boxes ← ToolWindow.CreateSubwindow[parent: window, display: DisplayBoxes];
    boxes.box.dims.h ← 36;
    Tool.AddThisSW[window: window, sw: boxes, swType: LOOPHOLE[0]];
    END;

  MakeSWs: Tool.MakeSWsProc =
    BEGIN
    logFileName: STRING = [40];
    msg ← Tool.MakeMsgSW[window: window, lines: 5];
    form ← Tool.MakeFormSW[window: window, formProc: MakeForm];
    MakeBoxesSW[window];
    Tool.UnusedLogName[logFileName, "FindGateways.log$"L];
    log ← Tool.MakeFileSW[window: window, name: logFileName];
    END;


  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 5;
    items ← FormSW.AllocateItemDescriptor[nParams];
    items[0] ← FormSW.CommandItem[
      tag: "Stop"L, proc: Stop, place: FormSW.newLine];
    items[1] ← FormSW.CommandItem[
      tag: "ScanCircle"L, proc: ScanCircle, place: FormSW.newLine];
    items[2] ← FormSW.NumberItem[
      tag: "MaxHops"L, value: @maxHops, default: defaultMaxHops];
    items[3] ← FormSW.CommandItem[
      tag: "ScanTarget"L, proc: ScanTarget, place: FormSW.newLine];
    items[4] ← FormSW.StringItem[tag: "Target"L, string: @target];
    RETURN[items, TRUE];
    END;

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN
    SELECT TRUE FROM
      old = inactive =>
	BEGIN
	target ← Storage.String[20];
	String.AppendString[target, "ME"L];
	PupDefs.PupPackageMake[];
	END;
      new = inactive =>
	BEGIN
	IF running THEN Off[];
	PupDefs.PupPackageDestroy[];
	Storage.FreeString[target];
	END;
      ENDCASE;
    END;

  Broom: PROCEDURE [why: Event.Reason] =
    BEGIN
    SELECT why FROM
      makeImage, makeCheck =>
	BEGIN IF running THEN Off[]; PupDefs.PupPackageDestroy[]; END;
      startImage, restartCheck, continueCheck =>
	BEGIN PupDefs.PupPackageMake[]; END;
      ENDCASE => NULL;
    END;

  -- Main Body

  [] ← Tool.Create[
    name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition];
  Event.AddNotifier[@eventItem];
  END.