-- Copyright (C) 1983, 1985  by Xerox Corporation. All rights reserved. 
-- EchoUserTool.mesa, HGM,  7-Nov-85 12:05:21
-- Please don't forget to update the herald....

DIRECTORY
  Display USING [Bitmap, Invert, replaceFlags, White],
  FormSW USING [
    ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, Display,
    Enumerated, EnumeratedItem, FindItem, BooleanItem, CommandItem,
    LongNumberItem, NumberItem, StringItem],
  Heap USING [systemZone],
  Inline USING [BITNOT, BITAND, HighHalf, LowHalf],
  MsgSW USING [Post],
  Process USING [SetPriority],
  Put USING [Char, CR, Text, Line, LongDecimal],
  String USING [AppendString, AppendChar, AppendNumber],
  Time USING [AppendCurrent, Current],
  Tool USING [
    Create, MakeSWsProc, UnusedLogName, MakeMsgSW, MakeFormSW, MakeFileSW,
    AddThisSW],
  ToolWindow USING [CreateSubwindow, DisplayProcType, nullBox, TransitionProcType],
  UserInput USING [UserAbort],
  Window USING [Handle, Box],

  Buffer USING [AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
  PupDefs USING [
    PupPackageMake, PupPackageDestroy,
    PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake, MsToTocks,
    SetPupContentsWords, GetPupContentsBytes, DataWordsPerPupBuffer,
    AppendPupAddress, GetPupAddress, PupNameTrouble],
  PupTypes USING [PupAddress, fillInSocketID, echoSoc, maxDataWordsPerGatewayPup];

EchoUserTool: PROGRAM
  IMPORTS
    Display, FormSW, Heap, Inline, MsgSW, Process, Put,
    String, Time, Tool, ToolWindow, UserInput,
    Buffer, PupDefs =
  BEGIN OPEN PupDefs, PupTypes;
  
  z: UNCOUNTED ZONE = Heap.systemZone;

  msg, form, boxes, log: Window.Handle;

  defaultWaitTime: CARDINAL = 2000;  -- ms

  Pattern: TYPE = {
    ignore, zeros, ones, alternating, pairs, oneTwentyFive, countBytes,
    countWords, constant, longConstant};
  patternChoices: ARRAY Pattern OF FormSW.Enumerated ← [
    ignore: ["Ignore", Pattern[ignore]], zeros: ["Zeros", Pattern[zeros]],
    ones: ["Ones", Pattern[ones]], alternating: ["125252B", Pattern[alternating]],
    pairs: ["146314B", Pattern[pairs]],
    oneTwentyFive: ["125B", Pattern[oneTwentyFive]],
    countBytes: ["CountBytes", Pattern[countBytes]],
    countWords: ["CountWords", Pattern[countWords]],
    constant: ["Constant", Pattern[constant]],
    longConstant: ["DblWdConstant", Pattern[longConstant]]];

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

  Data: TYPE = RECORD [
    length: CARDINAL ← 266,
    fixedLength: BOOLEAN ← FALSE,
    where: PupAddress ← [[0], [0], PupTypes.echoSoc],

    picks: ARRAY [0..16] OF CARDINAL ← ALL[0],
    drops: ARRAY [0..16] OF CARDINAL ← ALL[0],
    sent, good, missed, late, bad, horrible, error, words: LONG CARDINAL ← 0,
    pleaseStop: BOOLEAN ← FALSE,
    alignTheFink: WORD ← NULL,
    lowPriority: BOOLEAN ← FALSE,
    alignTheFinkAgain: WORD ← NULL,
    noBang: BOOLEAN ← FALSE,
    alignTheFinkStillAgain: WORD ← NULL,
    noLate: BOOLEAN ← FALSE,
    alignTheFinkSomeMore: WORD ← NULL,
    noLost: BOOLEAN ← FALSE,
    alignTheFinkYetAgain: WORD ← NULL,
    waitTime: CARDINAL ← defaultWaitTime,
    checkit: BOOLEAN ← TRUE,
    echoer: PROCESS ← NULL,
    indicator: {left, right, off} ← off,
    running: BOOLEAN ← FALSE,
    constant: LONG CARDINAL ← 0,
    pattern: Pattern ← countBytes,
    target: LONG STRING ← NULL];

  Initialize: PROCEDURE =
    BEGIN
    herald: STRING = "Pup Echo User of  7-Nov-85 12:04:12"L;
    [] ← Tool.Create[
      name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition];
    END;

  EchoUserOn: PROCEDURE =
    BEGIN
    IF data.length > PupDefs.DataWordsPerPupBuffer[] THEN
      BEGIN MsgSW.Post[msg, "Length is too long."L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Echoing to "L];
    IF ~FindPath[] THEN RETURN;
    data.running ← TRUE;
    UpdatePicture[];
    data.echoer ← FORK DoIt[];
    END;

  EchoUserOff: PROCEDURE =
    BEGIN
    IF data = NIL THEN RETURN;
    data.pleaseStop ← TRUE;
    JOIN data.echoer[];
    data.running ← data.pleaseStop ← FALSE;
    UpdatePicture[];
    END;

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

  FindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN OPEN data;
    WriteString[target];
    WriteChar['=];
    where ← [[0], [0], PupTypes.echoSoc];
    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;

  ClearCounters: PROCEDURE =
    BEGIN OPEN data;
    sent ← good ← missed ← late ← bad ← horrible ← error ← words ← 0;
    picks ← ALL[0];
    drops ← ALL[0];
    END;

  AddToHist: PROCEDURE [
    hist: LONG POINTER TO ARRAY [0..16] OF CARDINAL, bits: WORD] =
    BEGIN OPEN data;
    i: CARDINAL;
    IF bits = 0 THEN RETURN;
    SELECT bits FROM
      1 => i ← 15;
      2 => i ← 14;
      4 => i ← 13;
      10B => i ← 12;
      20B => i ← 11;
      40B => i ← 10;
      100B => i ← 9;
      200B => i ← 8;
      400B => i ← 7;
      1000B => i ← 6;
      2000B => i ← 5;
      4000B => i ← 4;
      10000B => i ← 3;
      20000B => i ← 2;
      40000B => i ← 1;
      100000B => i ← 0;
      ENDCASE => i ← 16;
    hist[i] ← hist[i] + 1;
    END;

  PrintSummary: PROCEDURE [howLong: LONG CARDINAL] =
    BEGIN OPEN data;
    WriteCR[];
    WriteLongDecimal[sent];
    WriteLine[" packets sent."L];
    IF howLong # 0 THEN
      BEGIN
      WriteLongDecimal[sent/howLong];
      WriteLine[" packets per second."L];
      WriteLongDecimal[16*words/howLong];
      WriteLine[" data bits per second."L];
      END;
    IF sent # 0 THEN
      BEGIN
      ShowPercent[good, "good packets received."L];
      ShowPercent[missed, "packets missed."L];
      ShowPercent[late, "late (or??) packets received."L];
      ShowPercent[bad, "bad packets received."L];
      ShowPercent[horrible, "packets received with more than 10 words wrong."L];
      ShowPercent[error, "error packets received."L];
      END;
    IF bad # 0 THEN
      BEGIN
      i: CARDINAL;
      x: WORD ← 100000B;
      WriteCR[];
      WriteLine["   Bit  Picked Dropped"L];
      FOR i IN [0..16] DO
        IF picks[i] # 0 OR drops[i] # 0 THEN
          BEGIN
          IF i = 16 THEN WriteString[" Other"L] ELSE O6[x];
          D8[picks[i]];
          D8[drops[i]];
          WriteCR[];
          END;
        x ← x/2;
        ENDLOOP;
      END;
    END;

  ShowPercent: PROCEDURE [n: LONG CARDINAL, s: LONG STRING] =
    BEGIN OPEN data;
    IF n = 0 THEN RETURN;
    WriteLongDecimal[n];
    WriteString[" ("L];
    WriteLongDecimal[n*100/sent];
    WriteString["%) "L];
    WriteLine[s];
    END;

  DoIt: PROCEDURE =
    BEGIN OPEN data;
    pool: Buffer.AccessHandle ← Buffer.MakePool[send: 1, receive: 10];
    soc: PupSocket ← PupSocketMake[fillInSocketID, where, MsToTocks[waitTime]];
    k: CARDINAL;
    b: PupBuffer;
    cycle, maxLength, myLength: CARDINAL;
    packetNumber: CARDINAL ← LAST[CARDINAL];
    start, stop: LONG CARDINAL;
    maxLength ← MIN[
      length, PupDefs.DataWordsPerPupBuffer[], PupTypes.maxDataWordsPerGatewayPup];
    IF lowPriority THEN Process.SetPriority[0];
    ClearCounters[];
    IF fixedLength THEN
      BEGIN
      WriteString["Packet length is "L];
      WriteDecimal[maxLength];
      WriteLine[" words."L];
      END;
    SetupBoxes[];
    start ← Time.Current[];
    UNTIL pleaseStop OR UserInput.UserAbort[log] DO
      -- NB: No check for short buffers
      FOR cycle IN [0..256) UNTIL pleaseStop OR UserInput.UserAbort[log] DO
        b ← Buffer.GetBuffer[pup, pool, send];
        myLength ← IF fixedLength THEN maxLength ELSE MIN[maxLength, cycle];
        SELECT pattern FROM
          ignore => NULL;
          zeros => FOR k IN [0..myLength) DO b.pup.pupWords[k] ← 0; ENDLOOP;
          ones => FOR k IN [0..myLength) DO b.pup.pupWords[k] ← 177777B; ENDLOOP;
          alternating =>
            FOR k IN [0..myLength) DO b.pup.pupWords[k] ← 125252B; ENDLOOP;
          pairs => FOR k IN [0..myLength) DO b.pup.pupWords[k] ← 146314B; ENDLOOP;
          oneTwentyFive =>
            FOR k IN [0..myLength) DO b.pup.pupWords[k] ← 125B; ENDLOOP;
          countBytes =>
            FOR k IN [0..myLength) DO
              b.pup.pupWords[k] ← 2*k*400B + (2*k + 1) MOD 400B; ENDLOOP;
          countWords => FOR k IN [0..myLength) DO b.pup.pupWords[k] ← k; ENDLOOP;
          constant =>
            FOR k IN [0..myLength) DO
              b.pup.pupWords[k] ← Inline.LowHalf[data.constant]; ENDLOOP;
          longConstant =>
            BEGIN
            FOR k IN [0..myLength) DO
              b.pup.pupWords[k] ←
                IF Inline.BITAND[k, 1] = 0 THEN Inline.HighHalf[data.constant]
                ELSE Inline.LowHalf[data.constant];
              ENDLOOP;
            END;
          ENDCASE => ERROR;
        b.pup.pupID.a ← b.pup.pupID.b ← (packetNumber ← packetNumber + 1);
        b.pup.pupType ← echoMe;
        SetPupContentsWords[b, myLength];
        soc.put[b];
        sent ← sent + 1;
        words ← words + myLength;
        UNTIL (b ← soc.get[]) = NIL DO
          -- Until timeout, or we find the expected one
          SELECT TRUE FROM
            (b.pup.pupType = error) =>
              BEGIN error ← error + 1; WriteCR[]; PrintErrorPup[b]; END;
            ((b.pup.pupType # iAmEcho) OR (b.pup.pupID.a # packetNumber)
              OR (b.pup.pupID.b # packetNumber)
              OR (GetPupContentsBytes[b] # 2*myLength)) =>
              BEGIN late ← late + 1; IF ~noLate THEN WriteChar['#]; END;
            ENDCASE =>
              BEGIN
              hits: CARDINAL ← 0;
              FlipBoxes[];
              IF data.checkit THEN
                FOR k IN [0..myLength) DO
                  expected: WORD;
                  SELECT pattern FROM
                    ignore => EXIT;
                    zeros => expected ← 0;
                    ones => expected ← 177777B;
                    alternating => expected ← 125252B;
                    pairs => expected ← 146314B;
                    oneTwentyFive => expected ← 125B;
                    countBytes => expected ← 2*k*400B + (2*k + 1) MOD 400B;
                    countWords => expected ← k;
                    constant => expected ← Inline.LowHalf[data.constant];
                    longConstant =>
                      expected ←
                        IF Inline.BITAND[k, 1] = 0 THEN Inline.HighHalf[
                        data.constant] ELSE Inline.LowHalf[data.constant];
                    ENDCASE => ERROR;
                  IF b.pup.pupWords[k] # expected THEN
                    BEGIN OPEN Inline;
                    found, picked, dropped: WORD;
                    IF hits = 0 THEN
                      BEGIN
                      WriteCR[];
                      WriteCurrentDateAndTime[];
                      WriteString["  Data compare error(s) on packet number "L];
                      WriteDecimal[packetNumber];
                      WriteLine["."L];
                      WriteLine["Idx Expected    Found   Picked  Dropped"L];
                      END;
                    found ← b.pup.pupWords[k];
                    picked ← BITAND[found, BITNOT[expected]];
                    dropped ← BITAND[expected, BITNOT[found]];
                    AddToHist[@picks, picked];
                    AddToHist[@drops, dropped];
                    IF hits < 10 THEN
                      BEGIN
                      O3[k];
                      O9[expected];
                      O9[found];
                      O9[picked];
                      O9[dropped];
                      WriteCR[];
                      END;
                    hits ← hits + 1;
                    END;
                  ENDLOOP;
              IF hits = 0 THEN good ← good + 1 ELSE bad ← bad + 1;
              IF hits = 0 AND ~noBang THEN WriteChar['!];
              IF hits > 10 THEN
                BEGIN horrible ← horrible + 1; WriteLine["...."L]; END;
              EXIT;  -- found the expected one
              END;
          Buffer.ReturnBuffer[b];
          ENDLOOP;
        IF b # NIL THEN Buffer.ReturnBuffer[b]
        ELSE BEGIN missed ← missed + 1; IF ~noLost THEN WriteChar['?]; END;
        ENDLOOP;
      IF ~noBang THEN WriteCR[] ELSE WriteChar['.];
      ENDLOOP;
    stop ← Time.Current[];
    WriteCR[];
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    SetDownBoxes[];
    PrintSummary[stop - start];
    IF ~pleaseStop THEN UpdatePicture[];
    END;

  -- IO things

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

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

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

  WriteLine: PROCEDURE [s: LONG 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, 6]; 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;

  PrintErrorPup: PUBLIC PROCEDURE [b: PupDefs.PupBuffer] =
    BEGIN
    i, len: CARDINAL;
    temp: STRING = [100];
    String.AppendString[temp, "Error Pup, code="L];
    String.AppendNumber[temp, b.pup.errorCode, 8];
    String.AppendString[temp, ", from: "L];
    AppendPupAddress[temp, b.pup.source];
    String.AppendString[temp, ": "L];
    len ← PupDefs.GetPupContentsBytes[b];
    FOR i IN [0..len - 2*(10 + 1 + 1)) UNTIL temp.length = temp.maxlength DO
      String.AppendChar[temp, b.pup.errorText[i]]; ENDLOOP;
    WriteLine[temp];
    MsgSW.Post[msg, 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 data.indicator FROM
      left => pattern ← [ALL[left], ALL[right]];
      right => pattern ← [ALL[right], ALL[left]];
      off => pattern ← [ALL[0], ALL[0]];
      ENDCASE;
    Display.Bitmap[window, indicatorBox, [@pattern, 0, 0], 16, Display.replaceFlags]
    END;

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

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

  SetDownBoxes: PROCEDURE =
    BEGIN data.indicator ← off; Display.White[boxes, indicatorBox]; END;

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

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

  Stop: FormSW.ProcType = BEGIN EchoUserOff[]; 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, "EchoUser.log$"L];
    log ← Tool.MakeFileSW[window: window, name: logFileName, allowTypeIn: FALSE];
    END;

  startIX: CARDINAL = 0;
  stopIX: CARDINAL = 1;
  runningIX: CARDINAL = 2;
  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 13;
    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: "LowPriority"L, switch: @data.lowPriority];
    items[3] ← FormSW.NumberItem[
      tag: "WaitTime(ms)"L, value: @data.waitTime, default: defaultWaitTime];
    items[4] ← FormSW.BooleanItem[
      tag: "!-Off"L, switch: @data.noBang, place: FormSW.newLine];
    items[5] ← FormSW.BooleanItem[tag: "#-Off"L, switch: @data.noLate];
    items[6] ← FormSW.BooleanItem[tag: "?-Off"L, switch: @data.noLost];
    items[7] ← FormSW.BooleanItem[tag: "Checkit"L, switch: @data.checkit];
    items[8] ← FormSW.EnumeratedItem[
      tag: "Pattern"L, value: @data.pattern,
      choices: LOOPHOLE[LONG[DESCRIPTOR[patternChoices]]]];
    items[9] ← FormSW.LongNumberItem[
      tag: "Constant"L, value: @data.constant, default: 0, radix: octal];
    items[10] ← FormSW.BooleanItem[
      tag: "FixedLength"L, switch: @data.fixedLength, place: FormSW.newLine];
    items[11] ← FormSW.NumberItem[tag: "(Max)Length"L, value: @data.length];
    items[12] ← FormSW.StringItem[
      tag: "Target"L, string: @data.target, place: FormSW.newLine, inHeap: TRUE];
    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 ← z.NEW[Data];
        data↑ ← [];
        data.target ← z.NEW[StringBody[20]];
        String.AppendString[data.target, "ME"L];
        [] ← PupDefs.PupPackageMake[];
        END;
      new = inactive =>
        BEGIN
        IF data = NIL THEN ERROR NotActive;
        IF data.running THEN EchoUserOff[];
        PupDefs.PupPackageDestroy[];
        z.FREE[@data.target];
        z.FREE[@data];
        END;
      ENDCASE;
    END;

  -- Main Body
  Initialize[];
  END.