-- PupTool.mesa; Steve Temple on August 27, 1982 9:39 am
-- Adapted from PupHacks by Murray
-- Last edited March 25, 1983 1:39 pm by Schroeder


DIRECTORY
   
   Buttons:          TYPE USING [Button, ButtonProc, Create],
   Containers:       TYPE USING [ChildXBound, ChildYBound, Container, Create],
   ConvertUnsafe:  TYPE USING [AppendRope, ToRope],
   DriverDefs:      TYPE USING [Network],
   IO,
   Labels:            TYPE USING [Create, Label, Set, SetDisplayStyle],
   Menus:           TYPE USING [AppendMenuEntry, CreateEntry, CreateMenu, 
                                       Menu, MenuProc],
   Process:           TYPE USING [Pause, SecondsToTicks], 
   PupDefs:         TYPE USING [AppendPupAddress, DataWordsPerPupBuffer,
                                       GetFreePupBuffer, GetPupAddress, GetPupContentsBytes,
                                       MoveStringBodyToPupBuffer, ReturnFreePupBuffer, 
                                       PupBuffer, PupNameTrouble, PupPackageDestroy,
                                       PupPackageMake, PupRouterBroadcastThis, PupSocket,
                                       PupSocketDestroy, PupSocketMake, SecondsToTocks,
                                       SetPupContentsBytes],
   PupRouterDefs:  TYPE USING [GetRoutingTable, RoutingTableEntry, RoutingTableObject],
   PupTypes:        TYPE USING [echoSoc, fillInPupAddress, fillInSocketID,
                                       maxDataWordsPerGatewayPup, miscSrvSoc, PupAddress],
   Rope:              TYPE USING [Concat, FromChar, Length, ROPE, Substr],
   Runtime:         TYPE USING [GetBcdTime],
   UserExec:         TYPE USING [CommandProc, RegisterCommand],
   VFonts:           TYPE USING [CharWidth],
   ViewerClasses:   TYPE USING [Viewer, ViewerClassRec],
   ViewerIO:        TYPE USING [CreateViewerStreams],
   ViewerOps:      TYPE USING [CreateViewer, DestroyViewer, PaintViewer],  
   ViewerTools:     TYPE USING [GetContents, MakeNewTextViewer, SetSelection];


PupTool: MONITOR

IMPORTS Buttons,
            Containers,
            ConvertUnsafe, 
            IO, 
            Labels,  
            Menus,
            Process, 
            PupDefs, 
            PupRouterDefs,
	         Runtime, 
	         Rope, 
	         UserExec, 
	         VFonts, 
	         ViewerIO, 
	         ViewerOps, 
	         ViewerTools = BEGIN

entryHeight: CARDINAL = 15;
entryVSpace: CARDINAL = 8;
entryHSpace: CARDINAL = 10;
 
-- a ToolHandle is a REF to the data for a particular instance of the tool.

ToolHandle: TYPE = REF ToolHandleRec;

ToolHandleRec: TYPE = RECORD [
	pleaseStop, echoInUse: BOOL ← FALSE,	-- useful flags
	viewer, root, msg: ViewerClasses.Viewer, -- viewer and parent 
	out: IO.STREAM,							-- text stream
	echoThrough: CONDITION];				-- echo process is done
	

--******************************************************************************
--	The following procedure creates an instance of the Pup Tool
--	Multiple instances of the tool can coexist
--******************************************************************************
	
		
MakeTool: PROC[] = BEGIN

	handle: ToolHandle ← NEW[ToolHandleRec];
	promptButton, textBox: ViewerClasses.Viewer;
	menu: Menus.Menu ← Menus.CreateMenu[1];
	height: CARDINAL ← 0;
	charSize: INTEGER = VFonts.CharWidth['0];
	initData: Rope.ROPE = "ME";
	
	PupDefs.PupPackageMake[];
		
	handle.root ← Containers.Create[[
		name: "Pup Tool",
		iconic: FALSE,
		menu: menu,
		column: left,
		scrollable: FALSE
		]];
		

	Menus.AppendMenuEntry[menu: menu, line: 0,
	        entry: Menus.CreateEntry[clientData: handle, name: "Destroy", proc: QuitProc, fork: FALSE]];
	        	
	Menus.AppendMenuEntry[menu: menu, line: 0,
	        entry: Menus.CreateEntry[clientData: handle, name: "EchoOn", proc: EchoProc, fork: TRUE]];
	        				
	Menus.AppendMenuEntry[menu: menu,  line: 0,
	        entry: Menus.CreateEntry[clientData: handle, name: "EchoOff", proc: StopProc, fork: FALSE]];
	        
	Menus.AppendMenuEntry[menu: menu, line: 0,
	        entry: Menus.CreateEntry[clientData: handle, name: "Route", proc: RouteProc, fork: FALSE]];
	        	
	Menus.AppendMenuEntry[menu: menu, line: 0,
	        entry: Menus.CreateEntry[clientData: handle, name: "ToName", proc: ToNameProc, fork: FALSE]];
	        	
	Menus.AppendMenuEntry[menu: menu,  line: 0,
	        entry: Menus.CreateEntry[clientData: handle, name: "ToAddress", proc: ToAddressProc, fork: FALSE]];		
	
	height ← height + entryVSpace;
			
	promptButton ← Buttons.Create[
		info: [name: "Name or Address", wy: height, wh: entryHeight, 
			   wx: 10*charSize, parent: handle.root],
		proc: Prompt,
		clientData: handle];
				
	handle.viewer ← ViewerTools.MakeNewTextViewer[[
		parent: handle.root,
		wx: promptButton.wx + promptButton.ww + entryHSpace,
		wy: height+2,	-- (hack: add 2 to align text with button baseline)
		ww: 50*charSize,	-- 50 digits worth of width
		wh: entryHeight,
		data: initData,
		scrollable: FALSE,
		border: FALSE
		]];
		
	height ← height + entryHeight + entryVSpace;
			
	handle.msg ← Labels.Create[[
		parent: handle.root,
		wx: 10*charSize,
		wy: height,
		ww: 80*charSize,	-- 80 digits worth of width
		wh: entryHeight,
		border: FALSE
		]];
		
	height ← height + entryHeight + entryVSpace;	-- interline spacing
	
	textBox ← ViewerOps.CreateViewer [flavor: $Typescript,
	 info: [
      parent: handle.root,
      border: FALSE,
      wy: height ]
    ];
   
   Containers.ChildXBound[handle.root, textBox]; 
   Containers.ChildYBound[handle.root, textBox];
    
   ViewerOps.PaintViewer[handle.root, all];

	[, handle.out]  ← ViewerIO.CreateViewerStreams[viewer: textBox, name: NIL];
	
   PrintHeaderLine[handle.out];
   END;
 
--******************************************************************************
--	Procedures to do IO and manipulate PUP addresses and
--	messages as ROPEs follow
--******************************************************************************


Prompt: Buttons.ButtonProc = TRUSTED BEGIN
	handle: ToolHandle ← NARROW[clientData];	-- get our data
	ViewerTools.SetSelection[handle.viewer];		-- force the selection
	END;

PrintHeaderLine: PROC[out: IO.STREAM] = BEGIN
	me: PupTypes.PupAddress ← [,,[0,0]];
	time: IO.GreenwichMeanTime ← Runtime.GetBcdTime[];
	
 	PupDefs.GetPupAddress[@me, "ME"];
 	IO.PutF[out, "PupTool of%g running on %g\n", IO.time[time], IO.rope[PupAddressToRope[me]]];
	END;

ErrorMsg: PROC[l: Labels.Label, r: Rope.ROPE ← NIL] = BEGIN
	IF Rope.Length[r] > 70 THEN r ← Rope.Substr[r, 0, 70];
	IF Rope.Length[r] < 62 THEN r ← Rope.Concat["ERROR > ", r];
	Labels.Set[l, r];
	Labels.SetDisplayStyle[l, $WhiteOnBlack];
	Process.Pause[Process.SecondsToTicks[1]];
	Labels.SetDisplayStyle[l, $BlackOnWhite];
	END;
	
ClearMsg: PROC[l: Labels.Label] = BEGIN
	Labels.Set[l, NIL];
	Labels.SetDisplayStyle[l, $BlackOnWhite];
	END;
	
InRope: PROC[v: ViewerClasses.Viewer] RETURNS [r: Rope.ROPE ← NIL] = BEGIN
	r ← ViewerTools.GetContents[v];
	END;
	
PutSep: PROC[out: IO.STREAM] = BEGIN
	IO.PutText[out, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"];
	END;
	
NewLine: PROC[out: IO.STREAM] = BEGIN
IO.PutChar[out, '\n];
END;

ErrorPupToRope: PROC[b: PupDefs.PupBuffer]  RETURNS[s: Rope.ROPE]=
	BEGIN
	source: PupTypes.PupAddress ← b.source;
	IF b.pupType=error THEN
    	BEGIN
    	len: CARDINAL = PupDefs.GetPupContentsBytes[b];
    	s ← IO.PutFR["[Error Pup, code=%b, from: ", IO.card[LOOPHOLE[b.errorCode, CARDINAL]]];
    	s ← Rope.Concat[s, PupAddressToRope[source]];
    	s ← Rope.Concat[s, "] "];
    	FOR i: CARDINAL IN [0..len-2*(10+1+1)) DO
      		s ← Rope.Concat[s, Rope.FromChar[b.errorText[i]]]; ENDLOOP;
     	END
	ELSE
   	s ← IO.PutFR["## Funny PupType = %b ##\n ", IO.card[LOOPHOLE[b.pupType, CARDINAL]]];
   END; 

PupAddressToRope: PROC[a: PupTypes.PupAddress] RETURNS[s: Rope.ROPE]=
  BEGIN
  buffer: STRING ← [40];
  PupDefs.AppendPupAddress[buffer, a];
  s ← ConvertUnsafe.ToRope[buffer];
  END;

PupBodyToRope: PROC[b: PupDefs.PupBuffer] RETURNS[s: Rope.ROPE]=
  BEGIN
  s ← NIL;
  FOR i: CARDINAL IN [0..PupDefs.GetPupContentsBytes[b]) DO
    s ← Rope.Concat[s, Rope.FromChar[b.pupChars[i]]]; ENDLOOP;
  END;

	
--******************************************************************************
--	The following procedures are invoked when the corresponding
--	menu button is clicked
--******************************************************************************

EchoProc: Menus.MenuProc = TRUSTED
  BEGIN OPEN PupDefs, PupTypes;
  
  h: ToolHandle = NARROW[clientData];
  bytesPerBuffer: CARDINAL;
  funny, late, recv, sent, wrong: LONG CARDINAL ← 0;
  me, where: PupAddress ← [,,echoSoc];
  mySoc: PupSocket;
  packetNumber: CARDINAL ← 0;
  routing: POINTER TO PupRouterDefs.RoutingTableObject;
  r: Rope.ROPE;
  fudge: STRING ← [40];

  IF EchoIsInUse[h] THEN RETURN;
  ClearMsg[h.msg]; 
  r ← InRope[h.viewer];
  IF Rope.Length[r] = 0 THEN { ErrorMsg[h.msg, "Name needed"]; GOTO GetOut};
  ConvertUnsafe.AppendRope[fudge, r];
  GetPupAddress[@where, fudge
    ! PupNameTrouble =>
      { ErrorMsg[h.msg, ConvertUnsafe.ToRope[e]]; GOTO GetOut }];
  mySoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[2]];
  me ← mySoc.getLocalAddress[];
  
  PutSep[h.out];
  IO.PutF[h.out, "Echo to <%g> via [%g] => [", IO.rope[r], IO.rope[PupAddressToRope[me]]];
  routing ← @PupRouterDefs.GetRoutingTable[][where.net];
  IF (routing.hop#0) AND (routing.network#NIL) THEN 
  IO.PutF[h.out, "%b#%b#] => [",  IO.card[LOOPHOLE[routing.network.netNumber.b, CARDINAL]], 
       IO.card[LOOPHOLE[routing.route, CARDINAL]]];    
  IO.PutF[h.out, "%g]\n", IO.rope[PupAddressToRope[where]]];
  
  bytesPerBuffer ←
    2*MIN[DataWordsPerPupBuffer[], maxDataWordsPerGatewayPup];
  UNTIL EchoShouldStop[h] DO
    FOR len: CARDINAL IN [0..bytesPerBuffer] UNTIL EchoShouldStop[h] DO
      b: PupBuffer ← GetFreePupBuffer[];
      b.pupID.a ← b.pupID.b ← (packetNumber←packetNumber+1);
      b.pupType ← echoMe;
      SetPupContentsBytes[b,len];
      FOR i: CARDINAL IN [0..len) DO b.pupBytes[i] ← i MOD 400B; ENDLOOP;
      mySoc.put[b];
      sent ← sent+1;
        UNTIL (b←mySoc.get[])=NIL DO
        SELECT TRUE FROM
          (b.pupType#iAmEcho) =>
            { funny ← funny+1; ErrorMsg[h.msg, ErrorPupToRope[b]];
              IO.PutChar[h.out, 'x]; GOTO Wrong };
          ((b.pupID.a#packetNumber)
           OR (b.pupID.b#packetNumber)
           OR (len#GetPupContentsBytes[b])) =>
            { IO.PutChar[h.out, '#]; late ← late+1 };
          ENDCASE =>
            BEGIN
            FOR i: CARDINAL IN [0..len) DO
              IF b.pupBytes[i]#(i MOD 400B) THEN
                { wrong ← wrong+1; IO.PutChar[h.out, '~]; GOTO Wrong };
              ENDLOOP;
            IO.PutChar[h.out, '!];
            recv ← recv+1;
            EXIT;
            END;
        ReturnFreePupBuffer[b];
        REPEAT Wrong => NULL;
        ENDLOOP;
      IF b#NIL THEN ReturnFreePupBuffer[b] ELSE IO.PutChar[h.out, '?];
      ENDLOOP;
    NewLine[h.out];
    ENDLOOP;
  PupSocketDestroy[mySoc];
  IO.PutF[h.out, "Out: %d, In: %d (%d%%)\n", IO.card[sent], IO.card[recv], IO.card[(recv*100)/sent]];
  IF late#0    THEN IO.PutF[h.out, "Late: %d (%d%%)\n", IO.card[late], IO.card[(late*100)/sent]];
  IF funny#0 THEN IO.PutF[h.out, "%d funny\n", IO.card[funny]];
  IF wrong#0 THEN IO.PutF[h.out, "%d wrong data\n", IO.card[wrong]];
  EchoUserStopped[h];
  EXITS GetOut => {h: ToolHandle = NARROW[clientData]; EchoUserStopped[h]};
  END;
	
RouteProc: Menus.MenuProc = TRUSTED BEGIN
  pupRt: DESCRIPTOR FOR ARRAY OF PupRouterDefs.RoutingTableObject;
  h: ToolHandle ← NARROW[clientData];
  k: CARDINAL ← 0;

  PutSep[h.out];
  ClearMsg[h.msg];
  IO.PutText[h.out, "Local PupRouting Table\n"];
  IO.PutText[h.out, "|  Net   Via   Hops |  Net   Via   Hops  |  Net   Via   Hops  |\n"];
  IO.PutText[h.out, "|-------------------|-------------------|-------------------|\n"];
  pupRt ← PupRouterDefs.GetRoutingTable[];
  
  FOR i: CARDINAL IN [0..LENGTH[pupRt]) DO
    r: PupRouterDefs.RoutingTableEntry=@pupRt[i];
    network: DriverDefs.Network = r.network;
    IF network=NIL THEN LOOP;
    IF k=0 THEN IO.PutChar[h.out, '|];
    IO.PutF[h.out, "%4b%4b#", IO.card[i], IO.card[LOOPHOLE[network.netNumber.b, CARDINAL]]];
    
    IO.PutF[h.out, "%03b#%4b   |", IO.card[IF r.hop#0 THEN r.route ELSE network.hostNumber], IO.card[r.hop]];
    IF (k←k+1)=3 THEN { NewLine[h.out]; k←0 };
    ENDLOOP;
  IF k#0 THEN NewLine[h.out];
  END;

	
ToNameProc: Menus.MenuProc = TRUSTED
  BEGIN OPEN PupTypes, PupDefs;
  h: ToolHandle = NARROW[clientData];
  soc: PupSocket;
  b: PupBuffer;
  a: PupAddress ← [, , [0, 0]];
  hit: BOOLEAN ← FALSE;
  r: Rope.ROPE;
  fudge: STRING ← [40];

  r ← InRope[h.viewer];
  ClearMsg[h.msg];
  IF Rope.Length[r] = 0 THEN BEGIN ErrorMsg[h.msg, "Address needed"]; RETURN; END;
  ConvertUnsafe.AppendRope [fudge, r];
  GetPupAddress[@a, fudge ! PupNameTrouble =>
      					  BEGIN
      					  ErrorMsg[h.msg, ConvertUnsafe.ToRope[e]];
      					  GOTO NoName;
      					  END];
  soc ← PupSocketMake[fillInSocketID, fillInPupAddress, SecondsToTocks[2]];
        
  THROUGH [0..10) UNTIL hit DO
    b ← GetFreePupBuffer[];
    b.pupType ← addressLookup;
    b.pupID ← [0, 0];
    b.dest.socket ← PupTypes.miscSrvSoc;
    b.source ← soc.getLocalAddress[];
    b.address ← a;
    SetPupContentsBytes[b, 2*SIZE[PupAddress]];
    PupRouterBroadcastThis[b];
    UNTIL hit OR (b ← soc.get[]) = NIL DO
	SELECT b.pupType FROM
	  addressIs =>
	    BEGIN
	    hit ← TRUE;
	    PutSep[h.out];
	    IO.PutF[h.out, "Name of <%g> is: %g\n", IO.rope[r], IO.rope[PupBodyToRope[b]]];
	    END;
	  nameError =>
	    BEGIN
	    hit ← TRUE;
	    ErrorMsg[h.msg, PupBodyToRope[b]];
	    END;
	  ENDCASE => ErrorMsg[h.msg, ErrorPupToRope[b]];
	ReturnFreePupBuffer[b];
	ENDLOOP;
    IF ~hit THEN ErrorMsg[h.msg, "No Response that try"];
    ENDLOOP;
  PupSocketDestroy[soc];
  EXITS NoName => NULL;
  END;
	
		
ToAddressProc: Menus.MenuProc = TRUSTED
  BEGIN OPEN PupTypes, PupDefs;
  h: ToolHandle = NARROW[clientData];
  soc: PupSocket;
  b: PupBuffer;
  hit: BOOLEAN ← FALSE;
  r: Rope.ROPE;
  fudge: STRING ← [40];
  
  r ← InRope[h.viewer];
  ClearMsg[h.msg];
  IF Rope.Length[r] = 0 THEN BEGIN ErrorMsg[h.msg, "Name needed"]; RETURN; END;
  ConvertUnsafe.AppendRope [fudge, r];
  soc ← PupSocketMake[fillInSocketID, fillInPupAddress, SecondsToTocks[2]];
  THROUGH [0..10) UNTIL hit DO
    b ← GetFreePupBuffer[];
    b.pupType ← nameLookup;
    b.pupID ← [0, 0];
    b.dest.socket ← PupTypes.miscSrvSoc;
    b.source ← soc.getLocalAddress[];
    MoveStringBodyToPupBuffer[b, fudge];
    PupRouterBroadcastThis[b];
    UNTIL hit OR (b ← soc.get[]) = NIL DO
	SELECT b.pupType FROM
	  nameIs =>
	    BEGIN
	    n: CARDINAL ← GetPupContentsBytes[b]/(2*SIZE[PupAddress]);
	    addresses: LONG POINTER TO ARRAY [0..0) OF PupAddress ←
	      LOOPHOLE[@b.pupBody];
	    hit ← TRUE;
	    PutSep[h.out];
	    IO.PutF[h.out, "Address of <%g> is: ", IO.rope[r]];
	    FOR i: CARDINAL IN [0..n) DO
	      IF i # 0 THEN IO.PutText[h.out, ", "];
	      IO.PutRope[h.out, PupAddressToRope[addresses[i]]];
	      ENDLOOP;
	    NewLine[h.out];
	    END;
	  nameError =>
	    BEGIN
	    hit ← TRUE;
	    ErrorMsg[h.msg, PupBodyToRope[b]];
	    END;
	  ENDCASE => ErrorMsg[h.msg, ErrorPupToRope[b]];
	ReturnFreePupBuffer[b];
	ENDLOOP;
   IF ~hit THEN ErrorMsg[h.msg, "No Response that try"];
   ENDLOOP;
  PupSocketDestroy[soc];
 END;
 
QuitProc: Menus.MenuProc = TRUSTED BEGIN
	h: ToolHandle = NARROW[clientData];
	StopEchoUser[h];
	ViewerOps.DestroyViewer[h.root];
	PupDefs.PupPackageDestroy[];
	END;
 
StopProc: Menus.MenuProc = TRUSTED BEGIN
	h: ToolHandle = NARROW[clientData];
	StopEchoUser[h];
	END;

StopEchoUser: ENTRY PROC [h: ToolHandle] =
	BEGIN
	h.pleaseStop ← TRUE;
	UNTIL NOT h.echoInUse DO WAIT h.echoThrough ENDLOOP;
	h.pleaseStop ← FALSE;
	END;

EchoShouldStop: ENTRY PROC [h: ToolHandle] RETURNS[BOOLEAN] = INLINE
   {RETURN[h.pleaseStop]};

EchoUserStopped: ENTRY PROC [h: ToolHandle]  =
	{h.echoInUse ← FALSE; NOTIFY h.echoThrough};

EchoIsInUse: ENTRY PROC [h: ToolHandle] RETURNS [BOOLEAN] =
	{IF h.echoInUse THEN RETURN [TRUE]; h.echoInUse ← TRUE; RETURN[FALSE]};


--******************************************************************************
--	Now the procedure to create a new tool and
--	its registration with the UserExec
--******************************************************************************


DoIt: UserExec.CommandProc = TRUSTED BEGIN
	MakeTool[]	
	END;

UserExec.RegisterCommand["PupTool", DoIt];

MakeTool[]

END.