-- File: BSPTestTool.mesa,  Last Edit: HGM  February 9, 1981  7:48 PM

DIRECTORY
  Process USING [Detach, Yield],
  Storage USING [Node, String, Free, FreeNodeNil, FreeString],
  String USING [AppendString],
  System USING [Pulses, GetClockPulses, PulsesToMicroseconds],
  Time USING [AppendCurrent, Current],

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

  Stream USING [Handle, PutWord, PutBlock, SendNow, Delete],
  PupDefs USING [
    AppendPupAddress, GetPupAddress, PupNameTrouble, PupPackageMake,
    PupPackageDestroy, SecondsToTocks],
  PupStream USING [PupByteStreamCreate, StreamClosing, CloseReason],
  PupTypes USING [PupAddress, bspTestSoc];

BSPTestTool: PROGRAM
  IMPORTS
    Process, Storage, String, System, Time, Event, FormSW, MsgSW, Put, Tool,
    Stream, PupDefs, PupStream =
  BEGIN OPEN PupStream, PupTypes;

  defualtClumpLength: CARDINAL = 1000; -- words
  defualtNumberOfClumps: CARDINAL = 1000; -- 1 mega word

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

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

  Data: TYPE = RECORD [
    where: PupAddress ← [[0], [0], PupTypes.bspTestSoc],
    clumpLength: CARDINAL ← defualtClumpLength,
    numberOfClumps: CARDINAL ← defualtNumberOfClumps,
    pleaseStop: BOOLEAN ← FALSE,
    alignTheFink0: WORD ← NULL,
    running: BOOLEAN ← FALSE,
    alignTheFink1: WORD ← NULL,
    verbose: BOOLEAN ← FALSE,
    alignTheFink2: WORD ← NULL,
    sendForever: BOOLEAN ← FALSE,
    alignTheFink3: WORD ← NULL,
    forceOutClumps: BOOLEAN ← FALSE,
    alignTheFink4: WORD ← NULL,
    sendFirstWord: BOOLEAN ← FALSE,
    target: STRING ← NULL];


  Init: PROCEDURE =
    BEGIN
    [] ← Tool.Create[
      name: "BSP Tester of February 9, 1981"L, makeSWsProc: MakeSWs,
      clientTransition: ClientTransition];
    Event.AddNotifier[@eventItem];
    END;

  BSPTestOn: PROCEDURE =
    BEGIN
    WriteCR[];
    IF data.clumpLength ~IN [2..10000) THEN
      BEGIN
      MsgSW.Post[msg, "ClumpLength should be between 2 and 10000."L];
      RETURN;
      END;
    WriteCurrentDateAndTime[];
    WriteString["  Sending to "L];
    IF ~FindPath[] THEN RETURN;
    data.running ← TRUE;
    UpdatePicture[];
    Process.Detach[FORK Push[]];
    END;

  BSPTestOff: PROCEDURE =
    BEGIN
    data.pleaseStop ← TRUE;
    WHILE data.running DO Process.Yield[]; ENDLOOP;
    data.pleaseStop ← FALSE;
    UpdatePicture[];
    END;

  Finished: PROCEDURE =
    BEGIN data.running ← FALSE; IF ~data.pleaseStop THEN UpdatePicture[]; END;

  UpdatePicture: PROCEDURE =
    BEGIN
    FormSW.FindItem[form, startIX].flags.invisible ← data.running;
    FormSW.FindItem[form, stopIX].flags.invisible ← ~data.running;
    FormSW.FindItem[form, clumpIX].flags.readOnly ← data.running;
    FormSW.Display[form];
    END;

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

  Push: PROCEDURE =
    BEGIN OPEN data;
    sh: Stream.Handle;
    start, stop: LONG CARDINAL;
    words: LONG CARDINAL ← 0;
    recent: System.Pulses;
    startOpen, endOpen, startFirstSend, endFirstSend, startClose, endClose:
      System.Pulses;
    buffer: POINTER;
    hits: CARDINAL ← 0;
    info: ARRAY CloseReason OF STRING =
      [localClose: "Closed locally"L, localAbort: "Local Abort"L,
	remoteClose: "Remote close"L,
	noRouteToNetwork: "No route to that network"L,
	transmissionTimeout: "Transmission timeout"L,
	remoteReject: "Remote reject"L];
    WriteString["Clump size is "L];
    WriteLongDecimal[clumpLength];
    WriteLine[" words."L];
    start ← Time.Current[];
    startOpen ← System.GetClockPulses[];
    sh ← PupByteStreamCreate[
      where, PupDefs.SecondsToTocks[10] !
      StreamClosing =>
	BEGIN
	WriteString["Connection failed: "L];
	IF text # NIL THEN
	  BEGIN WriteChar['(]; WriteString[text]; WriteString[") "L]; END;
	WriteLine[info[why]];
	GOTO Failed;
	END];
    endOpen ← System.GetClockPulses[];
    IF sendFirstWord THEN
      BEGIN
      ENABLE StreamClosing => CONTINUE;
      startFirstSend ← System.GetClockPulses[];
      Stream.PutWord[sh, 0];
      words ← words + 1;
      endFirstSend ← System.GetClockPulses[];
      END;
    recent ← System.GetClockPulses[];
    buffer ← Storage.Node[clumpLength];
    FOR clumps: CARDINAL ← 0, clumps + 1 UNTIL pleaseStop OR
      (~sendForever AND clumps >= data.numberOfClumps) DO
      Stream.PutBlock[
	sh, [buffer, 0, 2*clumpLength], forceOutClumps !
	StreamClosing =>
	  BEGIN
	  WriteString["Push Stream closed: "L];
	  IF text # NIL THEN
	    BEGIN WriteChar['(]; WriteString[text]; WriteString[") "L]; END;
	  WriteLine[info[why]];
	  GOTO Closed;
	  END];
      words ← words + clumpLength;
      IF verbose THEN
	BEGIN
	WriteChar['!];
	IF ((hits ← hits + 1) MOD 50) = 0 THEN
	  BEGIN
	  ms: LONG CARDINAL;
	  -- Oh shit.  This overflows at a clumpLength of 20000 wrods.
	  fudge: LONG CARDINAL = 100*LONG[16*50];
	  ms ←
	    System.PulsesToMicroseconds[[System.GetClockPulses[] - recent]]/10000;
	  WriteString["  "L];
	  WriteLongDecimal[(fudge*clumpLength)/ms];
	  WriteLine[" bits/sec"L];
	  recent ← System.GetClockPulses[];
	  END;
	END;
      REPEAT Closed => NULL;
      ENDLOOP;
    startClose ← System.GetClockPulses[];
    Stream.SendNow[sh ! StreamClosing => CONTINUE];
    Stream.Delete[sh];
    endClose ← System.GetClockPulses[];
    stop ← Time.Current[];
    Storage.Free[buffer];
    WriteCR[];
    WriteLongDecimal[words];
    WriteLine[" words sent."L];
    IF stop - start > 100 OR words > 100000 THEN
      BEGIN WriteLongDecimal[16*words/(stop - start)]; END
    ELSE
      BEGIN
      t: System.Pulses ← [endClose - startOpen];
      WriteLongDecimal[16*words*1000/(System.PulsesToMicroseconds[t]/1000)];
      END;
    WriteLine[" data bits per second."L];
    PrintTiming["Open"L, endOpen, startOpen];
    IF sendFirstWord THEN PrintTiming["First Ack"L, endFirstSend, startFirstSend];
    PrintTiming["Close"L, endClose, startClose];
    IF words < 5000000 THEN PrintTiming["Total"L, endClose, startOpen];
    WriteCR[];
    Finished[];
    EXITS Failed => Finished[];
    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;

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

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

  PrintTiming: PROCEDURE [s: STRING, end, start: System.Pulses] =
    BEGIN
    t: System.Pulses ← [end - start];
    WriteString[s];
    WriteString[" took "L];
    WriteLongDecimal[System.PulsesToMicroseconds[t]/1000];
    WriteLine[" ms."L];
    END;

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

  Stop: FormSW.ProcType = BEGIN BSPTestOff[]; 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: "BSPTest.log$"L];
    END;

  startIX: CARDINAL = 0;
  stopIX: CARDINAL = 1;
  clumpIX: CARDINAL = 4;
  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 10;
    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.BooleanItem[
      tag: "Running"L, switch: @data.running, readOnly: TRUE];
    items[3] ← FormSW.BooleanItem[tag: "Verbose"L, switch: @data.verbose];
    items[4] ← FormSW.BooleanItem[tag: "SendForever"L, switch: @data.sendForever];
    items[5] ← FormSW.BooleanItem[
      tag: "SendFirstWord"L, switch: @data.sendFirstWord];
    items[6] ← FormSW.BooleanItem[
      tag: "ForceOutClumps"L, switch: @data.forceOutClumps];
    items[7] ← FormSW.NumberItem[
      tag: "ClumpLength"L, value: @data.clumpLength, place: FormSW.newLine];
    items[8] ← FormSW.NumberItem[
      tag: "NumberOfClumps"L, value: @data.numberOfClumps, place: FormSW.newLine];
    items[9] ← FormSW.StringItem[
      tag: "Target"L, string: @data.target, 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↑ ← [];
	data.target ← Storage.String[20];
	String.AppendString[data.target, "ME"L];
	PupDefs.PupPackageMake[];
	END;
      new = inactive =>
	BEGIN
	IF data = NIL THEN ERROR NotActive;
	IF data.running THEN BSPTestOff[];
	PupDefs.PupPackageDestroy[];
	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 BSPTestOff[];
	PupDefs.PupPackageDestroy[];
	END;
      startImage, restartCheck, continueCheck =>
	BEGIN IF data = NIL THEN RETURN; PupDefs.PupPackageMake[]; END;
      ENDCASE => NULL;
    END;

  -- Main Body

  Init[];
  END.