-- File: NameLookupTool.mesa,  Last Edit: HGM  January 23, 1981  12:32 AM
-- Please don't forget to update the herald....

DIRECTORY
  InlineDefs USING [BcplToMesaLongNumber],
  Storage USING [Node, String, FreeNodeNil, FreeString],
  String USING [AppendString, AppendNumber],
  Time USING [AppendCurrent],

  Event USING [Item, Reason, AddNotifier],
  FormSW USING [
    ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, CommandItem,
    StringItem],
  MsgSW USING [Post],
  Put USING [Char, CR, Text, Line, LongDecimal],
  Tool USING [Create, MakeSWsProc, MakeMsgSW, MakeFormSW, MakeFileSW],
  ToolWindow USING [TransitionProcType],
  Window USING [Handle],

  NameServerDefs USING [nameStatsRequest, nameStatsReply, NameStatsEntry],
  PupDefs USING [
    PupPackageMake, PupPackageDestroy, GetFreePupBuffer, ReturnFreePupBuffer,
    PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake, SecondsToTocks,
    GetPupContentsBytes, SetPupContentsWords, MoveStringBodyToPupBuffer,
    AppendPupAddress, AppendErrorPup, GetPupAddress, PupNameTrouble],
  PupTypes USING [PupAddress, fillInNetID, fillInSocketID, allHosts, miscSrvSoc];

NameLookupTool: PROGRAM
  IMPORTS
    InlineDefs, Storage, String, Time, Event, FormSW, MsgSW, Put, Tool, PupDefs =
  BEGIN OPEN PupDefs, PupTypes;

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

  data: POINTER TO Data ← NIL;

  Data: TYPE = RECORD [
    where: PupAddress, target: STRING, name: STRING, address: STRING];

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

  NameToAddress: FormSW.ProcType =
    BEGIN
    soc: PupSocket;
    b: PupBuffer;
    hit: BOOLEAN ← FALSE;
    IF data.name = NIL OR data.name.length = 0 THEN
      BEGIN MsgSW.Post[msg, "Name needed"L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Name=>Address "L];
    IF ~FindPath[] THEN RETURN;
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← GetFreePupBuffer[];
      b.pupType ← nameLookup;
      MoveStringBodyToPupBuffer[b, data.name];
      b.pupID ← [0, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
	IF data.where # b.source THEN
	  BEGIN
	  WriteString["Reply from: "L];
	  PrintPupAddress[@b.source];
	  WriteCR[];
	  END;
	SELECT b.pupType FROM
	  nameIs =>
	    BEGIN
	    i, n: CARDINAL;
	    addresses: LONG POINTER TO ARRAY [0..0) OF PupAddress ←
	      LOOPHOLE[@b.pupBody];
	    hit ← TRUE;
	    WriteString[data.name];
	    WriteString[" => "L];
	    n ← GetPupContentsBytes[b]/(2*SIZE[PupAddress]);
	    FOR i IN [0..n) DO
	      IF i # 0 THEN WriteString[", "L];
	      PrintPupAddress[@addresses[i]];
	      ENDLOOP;
	    END;
	  nameError =>
	    BEGIN
	    hit ← TRUE;
	    WriteString[data.name];
	    WriteString[" => ERROR: "L];
	    PrintBodyAsText[b];
	    END;
	  ENDCASE => PrintErrorPup[b];
	WriteCR[];
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    END;

  AddressToName: FormSW.ProcType =
    BEGIN
    soc: PupSocket;
    b: PupBuffer;
    a: PupAddress ← [, , [0, 0]];
    hit: BOOLEAN ← FALSE;
    IF data.address = NIL OR data.address.length = 0 THEN
      BEGIN MsgSW.Post[msg, "Address needed"L]; RETURN; END;
    GetPupAddress[
      @a, data.address !
      PupNameTrouble =>
	BEGIN MsgSW.Post[msg, e]; WriteLine[e]; GOTO Trouble; END];
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Address=>Name "L];
    IF ~FindPath[] THEN RETURN;
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← GetFreePupBuffer[];
      b.pupType ← addressLookup;
      b.pupID ← [0, 0];
      b.address ← a;
      SetPupContentsWords[b, SIZE[PupAddress]];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
	IF data.where # b.source THEN
	  BEGIN
	  WriteString["Reply from: "L];
	  PrintPupAddress[@b.source];
	  WriteCR[];
	  END;
	SELECT b.pupType FROM
	  addressIs =>
	    BEGIN
	    hit ← TRUE;
	    WriteString[data.address];
	    WriteString[" => "L];
	    PrintBodyAsText[b];
	    END;
	  nameError =>
	    BEGIN
	    hit ← TRUE;
	    WriteString[data.address];
	    WriteString[" => ERROR: "L];
	    PrintBodyAsText[b];
	    END;
	  ENDCASE => PrintErrorPup[b];
	WriteLine["."L];
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    EXITS Trouble => NULL;
    END;

  Stats: FormSW.ProcType =
    BEGIN
    soc: PupSocket;
    b: PupBuffer;
    hit: BOOLEAN ← FALSE;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Pup Name Server Statistics "L];
    IF ~FindPath[] THEN RETURN;
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← GetFreePupBuffer[];
      b.pupType ← NameServerDefs.nameStatsRequest;
      b.pupID ← [0, 0];
      b.pupWords[0] ← 0;
      SetPupContentsWords[b, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
	IF data.where # b.source THEN
	  BEGIN
	  WriteString["Reply from: "L];
	  PrintPupAddress[@b.source];
	  WriteCR[];
	  END;
	SELECT b.pupType FROM
	  NameServerDefs.nameStatsReply =>
	    BEGIN
	    nse: LONG POINTER TO NameServerDefs.NameStatsEntry;
	    hit ← TRUE;
	    nse ← LOOPHOLE[@b.pupWords];
	    PrintInfo[
	      "Requests"L, InlineDefs.BcplToMesaLongNumber[nse.nameRequests]];
	    PrintInfo[
	      "Directories sent"L, InlineDefs.BcplToMesaLongNumber[
	      nse.directoriesSend]];
	    PrintInfo[
	      "Cache hits"L, InlineDefs.BcplToMesaLongNumber[nse.cacheHits]];
	    PrintInfo[
	      "Cache misses"L, InlineDefs.BcplToMesaLongNumber[nse.cacheMisses]];
	    END;
	  ENDCASE => PrintErrorPup[b];
	WriteCR[];
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    END;

  PrintInfo: PROCEDURE [s: STRING, n: LONG CARDINAL] =
    BEGIN
    IF n = 0 THEN RETURN;
    WriteString[s];
    WriteString[" = "L];
    WriteLongDecimal[n];
    WriteLine["."L];
    END;

  Version: FormSW.ProcType =
    BEGIN
    soc: PupSocket;
    b: PupBuffer;
    hit: BOOLEAN ← FALSE;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Pup Network Directory Version "L];
    IF ~FindPath[] THEN RETURN;
    soc ← PupSocketMake[PupTypes.fillInSocketID, data.where, SecondsToTocks[2]];
    THROUGH [0..10) UNTIL hit DO
      b ← GetFreePupBuffer[];
      b.pupType ← netDirVersion;
      b.pupID ← [0, 0];
      b.pupWords[0] ← 0;
      SetPupContentsWords[b, 1];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
	IF data.where # b.source THEN
	  BEGIN
	  WriteString["Reply from: "L];
	  PrintPupAddress[@b.source];
	  WriteCR[];
	  END;
	SELECT b.pupType FROM
	  netDirVersion =>
	    BEGIN
	    hit ← TRUE;
	    WriteString["Pup Network Directory version is "L];
	    WriteDecimal[b.pupWords[0]];
	    END;
	  ENDCASE => PrintErrorPup[b];
	WriteLine["."L];
	ReturnFreePupBuffer[b];
	ENDLOOP;
      IF ~hit THEN MsgSW.Post[msg, "No Response that try."L];
      ENDLOOP;
    PupSocketDestroy[soc];
    END;

  FindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN OPEN data;
    data.where ← [fillInNetID, allHosts, miscSrvSoc];
    IF data.target = NIL OR data.target.length = 0 THEN
      BEGIN WriteLine["via broadcasting on local net(s)."L]; RETURN[TRUE]; END
    ELSE BEGIN WriteString["from "L]; END;
    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;

  -- 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;

  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;

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

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

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

  O3Z: PROCEDURE [n: CARDINAL] =
    BEGIN
    temp: STRING = [25];
    String.AppendNumber[temp, n, 8];
    THROUGH [temp.length..3) DO WriteChar['0]; ENDLOOP;
    WriteString[temp];
    END;

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

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

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

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

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

  PrintErrorPup: PROCEDURE [b: PupBuffer] =
    BEGIN temp: STRING = [200]; AppendErrorPup[temp, b]; WriteString[temp]; END;

  PrintBodyAsText: PROCEDURE [b: PupBuffer] =
    BEGIN
    FOR i: CARDINAL IN [0..GetPupContentsBytes[b]) DO
      WriteChar[b.pupChars[i]]; ENDLOOP;
    END;

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

  startIX: CARDINAL = 0;
  stopIX: CARDINAL = 1;
  runningIX: CARDINAL = 2;
  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 7;
    items ← FormSW.AllocateItemDescriptor[nParams];
    items[0] ← FormSW.CommandItem[
      tag: "NameToAddress"L, proc: NameToAddress, place: FormSW.newLine];
    items[1] ← FormSW.CommandItem[tag: "AddressToName"L, proc: AddressToName];
    items[2] ← FormSW.CommandItem[tag: "Stats"L, proc: Stats];
    items[3] ← FormSW.CommandItem[tag: "Version"L, proc: Version];
    items[4] ← FormSW.StringItem[
      tag: "Target"L, string: @data.target, place: FormSW.newLine];
    items[5] ← FormSW.StringItem[
      tag: "Name"L, string: @data.name, place: FormSW.newLine];
    items[6] ← FormSW.StringItem[
      tag: "Address"L, string: @data.address, place: FormSW.newLine];
    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↑ ←
	  [where:, target: Storage.String[20], name: Storage.String[20],
	    address: Storage.String[20]];
	String.AppendString[data.target, "ME"L];
	PupDefs.PupPackageMake[];
	END;
      new = inactive =>
	BEGIN
	IF data = NIL THEN ERROR NotActive;
	PupDefs.PupPackageDestroy[];
	Storage.FreeString[data.target];
	Storage.FreeString[data.name];
	Storage.FreeString[data.address];
	data ← Storage.FreeNodeNil[data];
	END;
      ENDCASE;
    END;

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


  -- Main Body

  Init[];
  END.