-- File: GateWatcherTool.mesa,  Last Edit: HGM  January 30, 1981  3:19 PM
-- Please don't forget to update the herald....

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

  BitBlt USING [], -- Needed by DisplaySubstring
  Event USING [Item, Reason, AddNotifier],
  FormSW USING [
    AllocateItemDescriptor, ClientItemsProcType, CommandItem, Display, FindItem,
    newLine, ProcType, StringItem],
  MsgSW USING [Post],
  Tool USING [Create, MakeSWsProc, MakeMsgSW, MakeFormSW, AddThisSW],
  ToolWindow USING [TransitionProcType, DisplayProcType, CreateSubwindow],
  Window USING [Box, DisplaySubstring, DisplayWhite, Handle, Place],
  WindowFont USING [FontHeight],

  BootServerDefs USING [bootStatsRequest, bootStatsReply, BootStatsEntry],
  EchoServerDefs USING [echoStatsRequest, echoStatsReply, EchoStatsEntry],
  ForwarderDefs USING [
    forwarderStatsRequest, forwarderStatsReply, ForwardStatsEntry,
    TransitMatrixEntry],
  GateControlDefs USING [
    gateControlStatsSend, gateControlStatsAck, GateControlStatsEntry],
  NameServerDefs USING [nameStatsRequest, nameStatsReply, NameStatsEntry],
  TimeServerDefs USING [timeStatsRequest, timeStatsReply, TimeStatsEntry],
  PupDefs USING [
    PupPackageMake, PupPackageDestroy, AppendErrorPup, GetFreePupBuffer,
    ReturnFreePupBuffer, PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake,
    PupSocketKick, SecondsToTocks, SetPupContentsWords, GetPupContentsBytes,
    AppendPupAddress, GetPupAddress, PupNameTrouble],
  PupTypes USING [
    PupAddress, fillInSocketID, fillInPupAddress, gatewaySoc, miscSrvSoc,
    echoSoc];

GateWatcherTool: MONITOR
  IMPORTS
    InlineDefs, Process, Storage, String, Time, Event, FormSW, MsgSW, Tool,
    ToolWindow, Window, WindowFont, PupDefs =
  BEGIN OPEN PupDefs, PupTypes;

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

  -- Be sure to initialize it when it is allocated!!!!
  data: POINTER TO Data ← NIL;

  maxLines: CARDINAL = 25;
  Data: TYPE = RECORD [
    pleaseStop: BOOLEAN ← FALSE,
    alighTheSillyFink: WORD ← 0,
    running: BOOLEAN ← FALSE,
    him: PupAddress ← PupTypes.fillInPupAddress,
    target: STRING ← NIL,

    fastBoot, slowBoot, newBoot, echo, name, dirsSent, route, time: LONG
      CARDINAL ← 0,

    bootSoc, echoSoc, gcSoc, nameSoc, routeSoc, timeSoc: PupSocket ← NIL,

    activeProcesses: CARDINAL ← 0,
    height: CARDINAL ← 20,
    line: CARDINAL ← 0,
    lines: ARRAY [0..maxLines) OF STRING ← ALL[NIL]];

  Init: PROCEDURE =
    BEGIN
    [] ← Tool.Create[
      name: "Gate Watcher of November 13, 1980"L, makeSWsProc: MakeSWs,
      clientTransition: ClientTransition];
    Event.AddNotifier[@eventItem];
    END;

  GateWatcherOn: PROCEDURE =
    BEGIN
    IF data.running THEN RETURN;
    ZapCounters[];
    ClearThings[];
    data.pleaseStop ← FALSE;
    IF ~FindPath[] THEN RETURN;
    data.activeProcesses ← 6;
    Process.Detach[FORK Boot[]];
    Process.Detach[FORK Echo[]];
    Process.Detach[FORK Name[]];
    Process.Detach[FORK Route[]];
    Process.Detach[FORK Timer[]];
    Process.Detach[FORK GateControl[]];
    data.running ← TRUE;
    UpdatePicture[];
    END;

  GateWatcherOff: PROCEDURE =
    BEGIN
    IF data = NIL THEN RETURN;
    IF ~data.running THEN RETURN;
    WaitUntilStopped[];
    data.running ← FALSE;
    UpdatePicture[];
    END;

  UpdatePicture: PROCEDURE =
    BEGIN
    IF form = NIL THEN RETURN;
    FormSW.FindItem[form, startIX].flags.invisible ← data.running;
    FormSW.FindItem[form, stopIX].flags.invisible ← ~data.running;
    FormSW.Display[form];
    END;

  WaitUntilStopped: PROCEDURE =
    BEGIN
    IF data = NIL THEN RETURN;
    data.pleaseStop ← TRUE;
    PupSocketKick[data.bootSoc];
    PupSocketKick[data.echoSoc];
    PupSocketKick[data.gcSoc];
    PupSocketKick[data.nameSoc];
    PupSocketKick[data.routeSoc];
    PupSocketKick[data.timeSoc];
    WHILE data.activeProcesses # 0 DO Process.Yield[]; ENDLOOP;
    END;

  Boot: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.miscSrvSoc;
    bootSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← GetFreePupBuffer[];
      b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1);
      b.pupType ← BootServerDefs.bootStatsRequest;
      SetPupContentsWords[b, 0];
      bootSoc.put[b];
      UNTIL (b ← bootSoc.get[]) = NIL DO
	-- Until timeout, or we find the expected one
	SELECT TRUE FROM
	  (b.pupType = error) => ShowErrorPup[b];
	  (b.pupType = BootServerDefs.bootStatsReply) AND
	    (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) =>
	    BEGIN
	    bse: LONG POINTER TO BootServerDefs.BootStatsEntry =
	      LOOPHOLE[@b.pupBody];
	    data.fastBoot ← InlineDefs.BcplToMesaLongNumber[bse.fastSends];
	    data.slowBoot ← InlineDefs.BcplToMesaLongNumber[bse.slowSends];
	    data.newBoot ← InlineDefs.BcplToMesaLongNumber[bse.filesRecv];
	    END;
	  ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END;
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF b # NIL THEN ReturnFreePupBuffer[b]
      ENDLOOP;
    PupSocketDestroy[bootSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  Echo: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.echoSoc;
    echoSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← GetFreePupBuffer[];
      b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1);
      b.pupType ← EchoServerDefs.echoStatsRequest;
      SetPupContentsWords[b, 0];
      echoSoc.put[b];
      UNTIL (b ← echoSoc.get[]) = NIL DO
	-- Until timeout, or we find the expected one
	SELECT TRUE FROM
	  (b.pupType = error) => ShowErrorPup[b];
	  (b.pupType = EchoServerDefs.echoStatsReply) AND
	    (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) =>
	    BEGIN
	    ese: LONG POINTER TO EchoServerDefs.EchoStatsEntry =
	      LOOPHOLE[@b.pupBody];
	    data.echo ← InlineDefs.BcplToMesaLongNumber[ese.pupsEchoed];
	    END;
	  ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END;
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF b # NIL THEN ReturnFreePupBuffer[b]
      ENDLOOP;
    PupSocketDestroy[echoSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  Name: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.miscSrvSoc;
    nameSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← GetFreePupBuffer[];
      b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1);
      b.pupType ← NameServerDefs.nameStatsRequest;
      SetPupContentsWords[b, 0];
      nameSoc.put[b];
      UNTIL (b ← nameSoc.get[]) = NIL DO
	-- Until nameout, or we find the expected one
	SELECT TRUE FROM
	  (b.pupType = error) => ShowErrorPup[b];
	  (b.pupType = NameServerDefs.nameStatsReply) AND
	    (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) =>
	    BEGIN
	    nse: LONG POINTER TO NameServerDefs.NameStatsEntry =
	      LOOPHOLE[@b.pupBody];
	    data.name ← InlineDefs.BcplToMesaLongNumber[nse.nameRequests];
	    data.dirsSent ← InlineDefs.BcplToMesaLongNumber[nse.directoriesSend];
	    END;
	  ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END;
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF b # NIL THEN ReturnFreePupBuffer[b]
      ENDLOOP;
    PupSocketDestroy[nameSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  Timer: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.miscSrvSoc;
    timeSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← GetFreePupBuffer[];
      b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1);
      b.pupType ← TimeServerDefs.timeStatsRequest;
      SetPupContentsWords[b, 0];
      timeSoc.put[b];
      UNTIL (b ← timeSoc.get[]) = NIL DO
	-- Until timeout, or we find the expected one
	SELECT TRUE FROM
	  (b.pupType = error) => ShowErrorPup[b];
	  (b.pupType = TimeServerDefs.timeStatsReply) AND
	    (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) =>
	    BEGIN
	    tse: LONG POINTER TO TimeServerDefs.TimeStatsEntry =
	      LOOPHOLE[@b.pupBody];
	    data.time ←
	      InlineDefs.BcplToMesaLongNumber[tse.tenexRequests] +
		InlineDefs.BcplToMesaLongNumber[tse.stringRequests] +
		InlineDefs.BcplToMesaLongNumber[tse.altoRequests];
	    END;
	  ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END;
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF b # NIL THEN ReturnFreePupBuffer[b]
      ENDLOOP;
    PupSocketDestroy[timeSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  Route: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    where.socket ← PupTypes.gatewaySoc;
    routeSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[5]];
    UNTIL pleaseStop DO
      b ← GetFreePupBuffer[];
      b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1);
      b.pupType ← ForwarderDefs.forwarderStatsRequest;
      SetPupContentsWords[b, 0];
      routeSoc.put[b];
      UNTIL (b ← routeSoc.get[]) = NIL DO
	-- Until timeout, or we find the expected one
	SELECT TRUE FROM
	  (b.pupType = error) => ShowErrorPup[b];
	  (b.pupType = ForwarderDefs.forwarderStatsReply) AND
	    (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) =>
	    BEGIN OPEN ForwarderDefs;
	    fse: LONG POINTER TO ForwardStatsEntry = LOOPHOLE[@b.pupBody];
	    l: CARDINAL ← PupDefs.GetPupContentsBytes[b]/2;
	    n: CARDINAL ← fse.numberOfNetworks;
	    p: CARDINAL ←
	      (l - n - SIZE[ForwardStatsEntry])/SIZE[ForwardStatsEntry];
	    data.route ← InlineDefs.BcplToMesaLongNumber[fse.routingInfoRequests];
	    PrintForwardingStats[
	      nets: DESCRIPTOR[@b.pupBody + SIZE[ForwardStatsEntry], n],
	      pupStatsTable: DESCRIPTOR[
	      @b.pupBody + SIZE[ForwardStatsEntry] + n, p]];
	    END;
	  ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END;
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF b # NIL THEN ReturnFreePupBuffer[b]
      ENDLOOP;
    PupSocketDestroy[routeSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  PrintForwardingStats: ENTRY PROCEDURE [
    nets: LONG DESCRIPTOR FOR ARRAY OF CARDINAL,
    pupStatsTable: LONG DESCRIPTOR FOR ARRAY OF
      ForwarderDefs.TransitMatrixEntry] =
    BEGIN
    FindFirst: INTERNAL PROCEDURE [from, to: CARDINAL] =
      BEGIN
      tme: LONG POINTER TO ForwarderDefs.TransitMatrixEntry;
      FOR i: CARDINAL IN [0..LENGTH[pupStatsTable]) DO
	tme ← @pupStatsTable[i];
	IF from # tme.sourceNet OR to # tme.destNet THEN LOOP;
	LD10Dash[InlineDefs.BcplToMesaLongNumber[tme.count]];
	EXIT;
	REPEAT FINISHED => LD10Dash[0];
	ENDLOOP;
      END;
    FindSecond: INTERNAL PROCEDURE [from, to: CARDINAL] =
      BEGIN
      i: CARDINAL;
      tme: LONG POINTER TO ForwarderDefs.TransitMatrixEntry;
      FOR i ← 0, i + 1 UNTIL i = LENGTH[pupStatsTable] DO
	tme ← @pupStatsTable[i];
	IF from # tme.sourceNet OR to # tme.destNet THEN LOOP;
	EXIT; -- Skip first matching entry

	REPEAT FINISHED => BEGIN LD10Dash[0]; RETURN; END;
	ENDLOOP;
      FOR i ← i + 1, i + 1 UNTIL i = LENGTH[pupStatsTable] DO
	tme ← @pupStatsTable[i];
	IF from # tme.sourceNet OR to # tme.destNet THEN LOOP;
	LD10Dash[InlineDefs.BcplToMesaLongNumber[tme.count]/1000];
	EXIT;
	REPEAT FINISHED => LD10Dash[0];
	ENDLOOP;
      END;
    SetLinePointer[6];
    WriteLine["Packets forwarded:"L];
    WriteLine["from                to"L];
    WriteString["      Discard"L];
    FOR to: CARDINAL IN [0..LENGTH[nets]) DO
      WriteString["      "L]; O4[nets[to]]; ENDLOOP;
    WriteCR[];
    FOR from: CARDINAL IN [0..LENGTH[nets]) DO
      O4[nets[from]];
      FindFirst[nets[from], 0];
      FOR to: CARDINAL IN [0..LENGTH[nets]) DO
	FindFirst[nets[from], nets[to]]; ENDLOOP;
      WriteCR[];
      ENDLOOP;
    WriteCR[];
    WriteLine["KBytes forwarded:"L];
    WriteLine["from                to"L];
    WriteString["      Discard"L];
    FOR to: CARDINAL IN [0..LENGTH[nets]) DO
      WriteString["      "L]; O4[nets[to]]; ENDLOOP;
    WriteCR[];
    FOR from: CARDINAL IN [0..LENGTH[nets]) DO
      O4[nets[from]];
      FindSecond[nets[from], 0];
      FOR to: CARDINAL IN [0..LENGTH[nets]) DO
	FindSecond[nets[from], nets[to]]; ENDLOOP;
      WriteCR[];
      ENDLOOP;
    END;

  GateControl: PROCEDURE =
    BEGIN OPEN data;
    b: PupBuffer;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    where: PupAddress ← him;
    text: STRING = [50];
    String.AppendString[text, target];
    String.AppendString[text, " ["L];
    AppendPupAddress[text, him];
    String.AppendString[text, "] "L];
    where.socket ← [31415, 9265];
    gcSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]];
    UNTIL pleaseStop DO
      b ← GetFreePupBuffer[];
      b.pupID.a ← 27182;
      b.pupID.b ← (packetNumber ← packetNumber + 1);
      b.pupType ← GateControlDefs.gateControlStatsSend;
      SetPupContentsWords[b, 0];
      gcSoc.put[b];
      UNTIL (b ← gcSoc.get[]) = NIL DO
	-- Until timeout, or we find the expected one
	SELECT TRUE FROM
	  (b.pupType = error) => ShowErrorPup[b];
	  (b.pupType = GateControlDefs.gateControlStatsAck) AND
	    (b.pupID.b = packetNumber) => PrintFirstHalf[text, b];
	  ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END;
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF b # NIL THEN ReturnFreePupBuffer[b]
      ENDLOOP;
    PupSocketDestroy[gcSoc];
    data.activeProcesses ← data.activeProcesses - 1;
    END;

  PrintFirstHalf: ENTRY PROCEDURE [text: STRING, b: PupBuffer] =
    BEGIN
    gse: LONG POINTER TO GateControlDefs.GateControlStatsEntry;
    gse ← LOOPHOLE[@b.pupBody];
    SetLinePointer[0];
    WriteString[text];
    FOR i: CARDINAL IN [0..gse.versionText.length) DO
      WriteChar[gse.versionText.char[i]]; ENDLOOP;
    PrintUpTime[gse.startTime];
    WriteCR[];
    WriteCR[];
    PrintItem[data.fastBoot, "Boot: "L];
    PrintItem[data.echo, "Echo: "L];
    PrintItem[data.name, "Name: "L];
    PrintItem[data.route, "Route: "L];
    PrintItem[data.time, "Time: "L];
    WriteCR[];
    IF (data.slowBoot + data.newBoot + data.dirsSent) # 0 THEN
      BEGIN
      PrintItem[data.slowBoot, "SlowBoot: "L];
      PrintItem[data.newBoot, "NewBoot: "L];
      PrintItem[data.dirsSent, "NetDirsSent: "L];
      WriteCR[];
      END;
    PrintItem[gse.freeBuffers, "Buffers: "L];
    PrintItem[gse.freeDiskPages, "Disk pages: "L];
    WriteCR[];
    END;

  PrintUpTime: INTERNAL PROCEDURE [startTime: InlineDefs.BcplLongNumber] =
    BEGIN
    now, then: LONG INTEGER;
    sec: LONG INTEGER;
    min: LONG INTEGER;
    hours: LONG INTEGER;
    WriteString[" up "L];
    then ← InlineDefs.BcplToMesaLongNumber[startTime];
    now ← Time.Current[];
    sec ← now - then;
    hours ← sec/3600;
    sec ← sec - hours*3600;
    min ← sec/60;
    sec ← sec - min*60;
    WriteLongDecimal[hours];
    WriteChar[':];
    IF min < 10 THEN WriteChar['0];
    WriteLongDecimal[min];
    WriteChar[':];
    IF sec < 10 THEN WriteChar['0];
    WriteLongDecimal[sec];
    END;

  PrintItem: INTERNAL PROCEDURE [d: LONG CARDINAL, s: STRING] =
    BEGIN
    IF d = 0 THEN RETURN;
    WriteString[s];
    WriteLongDecimal[d];
    WriteString["  "L];
    END;


  FindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    text: STRING = [100];
    String.AppendString[text, "Looking at "L];
    String.AppendString[text, data.target];
    String.AppendString[text, "="L];
    GetPupAddress[
      @data.him, data.target !
      PupNameTrouble =>
	BEGIN
	String.AppendString[text, e];
	MsgSW.Post[msg, text];
	GOTO Trouble;
	END];
    AppendPupAddress[text, data.him];
    MsgSW.Post[msg, text];
    RETURN[TRUE];
    EXITS Trouble => RETURN[FALSE];
    END;

  -- IO things

  SetLinePointer: INTERNAL PROCEDURE [n: [0..maxLines)] = INLINE
    BEGIN data.line ← n; data.lines[data.line].length ← 0; END;

  WriteChar: INTERNAL PROCEDURE [c: CHARACTER] =
    BEGIN String.AppendChar[data.lines[data.line], c]; END;

  WriteCR: INTERNAL PROCEDURE =
    BEGIN
    string: STRING = data.lines[data.line];
    ss: String.SubStringDescriptor ← [string, 0, string.length];
    tail: Window.Place;
    tail ← Window.DisplaySubstring[
      window: info, ss: @ss, place: [indentation, data.line*data.height],
      bbop: replace];
    Window.DisplayWhite[info, [tail, [rightEdge - tail.x, data.height]]];
    data.line ← data.line + 1;
    data.lines[data.line].length ← 0;
    END;

  WriteString: INTERNAL PROCEDURE [s: STRING] =
    BEGIN i: CARDINAL; FOR i IN [0..s.length) DO WriteChar[s[i]]; ENDLOOP; END;

  WriteLine: INTERNAL PROCEDURE [s: STRING] =
    BEGIN WriteString[s]; WriteCR[]; END;

  WriteLongDecimal: INTERNAL PROCEDURE [n: LONG CARDINAL] =
    BEGIN
    s: STRING = [20];
    String.AppendLongNumber[s, n, 10];
    WriteString[s];
    END;

  LD10: INTERNAL PROCEDURE [n: LONG INTEGER] =
    BEGIN
    s: STRING = [20];
    String.AppendLongNumber[s, n, 10];
    THROUGH [s.length..10) DO WriteChar[' ]; ENDLOOP;
    WriteString[s];
    END;

  LD10Dash: INTERNAL PROCEDURE [n: LONG INTEGER] =
    BEGIN IF n = 0 THEN WriteString["        - "L] ELSE LD10[n]; END;

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

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

  WriteNumber: INTERNAL 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: INTERNAL PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 8]; END;

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

  O4: INTERNAL PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 4]; END;

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

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

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

  ShowErrorPup: PUBLIC PROCEDURE [b: PupDefs.PupBuffer] =
    BEGIN
    text: STRING = [100];
    PupDefs.AppendErrorPup[text, b];
    MsgSW.Post[msg, text];
    END;

  ClearThings: PROCEDURE =
    BEGIN
    FOR i: CARDINAL IN [0..maxLines) DO data.lines[i].length ← 0; ENDLOOP;
    RepaintThings[];
    END;

  indentation: CARDINAL = 10;
  rightEdge: CARDINAL = 500;
  RepaintThings: PROCEDURE =
    BEGIN
    FOR i: CARDINAL IN [0..maxLines) DO
      string: STRING = data.lines[i];
      ss: String.SubStringDescriptor ← [string, 0, string.length];
      tail: Window.Place;
      tail ← Window.DisplaySubstring[
	window: info, ss: @ss, place: [indentation, i*data.height],
	bbop: replace];
      Window.DisplayWhite[info, [tail, [rightEdge - tail.x, data.height]]];
      ENDLOOP;
    END;

  DisplayInfo: ToolWindow.DisplayProcType = BEGIN RepaintThings[]; END;

  ZapCounters: PROCEDURE =
    BEGIN
    data.fastBoot ← data.slowBoot ← data.newBoot ← data.echo ← data.name ←
      data.dirsSent ← data.route ← data.time ← 0;
    END;




  Start: FormSW.ProcType = BEGIN GateWatcherOn[]; END;

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

  MakeSWs: Tool.MakeSWsProc =
    BEGIN
    msg ← Tool.MakeMsgSW[window: window, lines: 5];
    form ← Tool.MakeFormSW[window: window, formProc: MakeForm];
    info ← ToolWindow.CreateSubwindow[parent: window, display: DisplayInfo];
    info.box.dims.h ← 20*36;
    Tool.AddThisSW[window: window, sw: info, swType: vanilla];
    END;

  startIX: CARDINAL = 0;
  stopIX: CARDINAL = 1;
  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 3;
    items ← FormSW.AllocateItemDescriptor[nParams];
    items[0] ← FormSW.CommandItem[
      tag: "Start"L, proc: Start, place: FormSW.newLine];
    items[1] ← FormSW.CommandItem[
      tag: "Stop"L, proc: Stop, place: FormSW.newLine, invisible: TRUE];
    items[2] ← FormSW.StringItem[tag: "Target"L, string: @data.target];
    RETURN[items, TRUE];
    END;

  AlreadyActive: ERROR = CODE;
  NotActive: ERROR = CODE;

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN
    SELECT TRUE FROM
      old = inactive =>
	BEGIN
	IF data # NIL THEN ERROR AlreadyActive;
	data ← Storage.Node[SIZE[Data]];
	data↑ ← [];
	data.height ← WindowFont.FontHeight[];
	data.target ← Storage.String[20];
	String.AppendString[data.target, "ME"L];
	FOR i: CARDINAL IN [0..maxLines) DO
	  data.lines[i] ← Storage.String[150]; ENDLOOP;
	PupDefs.PupPackageMake[];
	END;
      new = inactive =>
	BEGIN
	IF data = NIL THEN ERROR NotActive;
	msg ← form ← info ← NIL;
	IF data.running THEN GateWatcherOff[];
	PupDefs.PupPackageDestroy[];
	FOR i: CARDINAL IN [0..maxLines) DO
	  Storage.FreeString[data.lines[i]]; ENDLOOP;
	Storage.FreeString[data.target];
	data ← Storage.FreeNodeNil[data];
	END;
      ENDCASE;
    END;

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


  -- Main Body

  Init[];
  END.