-- DiagnosticsImplD.mesa  -  edited by:
-- Poskanzer		28-Mar-83 11:42:06

DIRECTORY
  Ascii USING [ControlG],
  DiagnosticsOps USING [
    CheckForAbort, GetConfirmation, GetFloppyChoice, GetYesOrNo, PutChar, PutCR,
    PutLine, PutMessage, PutText, PutTextCentered, running],
  Format USING [Blanks, Char, LongString, Number, NumberFormat, StringProc],
  Heap USING [systemZone],
  OnlineDiagnostics USING [
    DisplayFieldsProc, DisplayNumberedTableProc, DisplayTableProc,
    ErrorHandling, FieldDataType, FloppyCleanReadWriteHeads,
    FloppyCommandFileTest, FloppyDisplayErrorLog, FloppyExerciser,
    FloppyFormatDiskette, FloppyMessage, FloppyReturn, FloppyStandardTest,
    PutMessageProc, SectorLength, SingleDouble],
  String USING [
    AppendNumber, AppendString, AppendStringAndGrow, CopyToNewString];

DiagnosticsImplD: PROGRAM
  IMPORTS DiagnosticsOps, Format, Heap, OnlineDiagnostics, String
  EXPORTS DiagnosticsOps =
  BEGIN

  z: UNCOUNTED ZONE = Heap.systemZone;

  FloppyStandardTest: PUBLIC PROCEDURE =
    BEGIN
    errFlag: BOOLEAN ← FALSE;
    floppyReturn: OnlineDiagnostics.FloppyReturn;
    floppyReturn ← OnlineDiagnostics.FloppyStandardTest[
      DisplayFields, DisplayTable, DisplayNumberedTable,
      DiagnosticsOps.PutMessage, DiagnosticsOps.GetConfirmation,
      DiagnosticsOps.GetYesOrNo, DiagnosticsOps.GetFloppyChoice !
      ABORTED =>
        BEGIN
        errFlag ← TRUE;
        CONTINUE;
        END];
    IF NOT errFlag THEN FloppyReturnMessage[floppyReturn];
    FloppyFinished[errFlag];
    END;

  FloppyCleanReadWriteHeads: PUBLIC PROCEDURE =
    BEGIN
    errFlag: BOOLEAN ← FALSE;
    floppyReturn: OnlineDiagnostics.FloppyReturn;
    floppyReturn ← OnlineDiagnostics.FloppyCleanReadWriteHeads[
      DisplayFields, DisplayTable, DisplayNumberedTable,
      DiagnosticsOps.PutMessage, DiagnosticsOps.GetConfirmation,
      DiagnosticsOps.GetYesOrNo, DiagnosticsOps.GetFloppyChoice !
      ABORTED =>
        BEGIN
        errFlag ← TRUE;
        CONTINUE;
        END];
    IF NOT errFlag THEN FloppyReturnMessage[floppyReturn];
    FloppyFinished[errFlag];
    END;

  FloppyCommandFileTest: PUBLIC PROCEDURE [
    doubleDensity: BOOLEAN, doubleSided: BOOLEAN,
    sectorsPerTrack: CARDINAL [8..26],
    sectorLength: OnlineDiagnostics.SectorLength,
    errorHandling: OnlineDiagnostics.ErrorHandling, cmdFile: LONG STRING] =
    BEGIN
    errFlag: BOOLEAN ← FALSE;
    density, sides: OnlineDiagnostics.SingleDouble;
    temp: LONG STRING;
    density ← IF doubleDensity THEN double ELSE single;
    sides ← IF doubleSided THEN double ELSE single;
    temp ← String.CopyToNewString[cmdFile, z];
    String.AppendStringAndGrow[@temp, "X,"L, z];
    OnlineDiagnostics.FloppyCommandFileTest[
      density, sides, sectorsPerTrack, sectorLength, errorHandling, temp,
      DisplayFields, DisplayTable, DisplayNumberedTable,
      DiagnosticsOps.PutMessage, DiagnosticsOps.GetConfirmation,
      DiagnosticsOps.GetYesOrNo, DiagnosticsOps.GetFloppyChoice !
      ABORTED =>
        BEGIN
        errFlag ← TRUE;
        CONTINUE;
        END];
    z.FREE[@temp];
    FloppyFinished[errFlag];
    END;

  FloppyDisplayErrorLog: PUBLIC PROCEDURE =
    BEGIN
    errFlag: BOOLEAN ← FALSE;
    OnlineDiagnostics.FloppyDisplayErrorLog[
      DisplayFields, DisplayTable, DisplayNumberedTable,
      DiagnosticsOps.PutMessage, DiagnosticsOps.GetConfirmation,
      DiagnosticsOps.GetYesOrNo, DiagnosticsOps.GetFloppyChoice !
      ABORTED =>
        BEGIN
        errFlag ← TRUE;
        CONTINUE;
        END];
    FloppyFinished[errFlag];
    END;

  FloppyExerciser: PUBLIC PROCEDURE =
    BEGIN
    errFlag: BOOLEAN ← FALSE;
    OnlineDiagnostics.FloppyExerciser[
      DisplayFields, DisplayTable, DisplayNumberedTable,
      DiagnosticsOps.PutMessage, DiagnosticsOps.GetConfirmation,
      DiagnosticsOps.GetYesOrNo, DiagnosticsOps.GetFloppyChoice !
      ABORTED =>
        BEGIN
        errFlag ← TRUE;
        CONTINUE;
        END];
    FloppyFinished[errFlag];
    END;

  FloppyFormatDiskette: PUBLIC PROCEDURE =
    BEGIN
    errFlag: BOOLEAN ← FALSE;
    OnlineDiagnostics.FloppyFormatDiskette[
      DisplayFields, DisplayTable, DisplayNumberedTable,
      DiagnosticsOps.PutMessage, DiagnosticsOps.GetConfirmation,
      DiagnosticsOps.GetYesOrNo, DiagnosticsOps.GetFloppyChoice !
      ABORTED =>
        BEGIN
        errFlag ← TRUE;
        CONTINUE;
        END];
    FloppyFinished[errFlag];
    END;

  FloppyReturnMessage: PROCEDURE [
    floppyReturn: OnlineDiagnostics.FloppyReturn] =
    BEGIN
    DiagnosticsOps.PutLine[
      SELECT floppyReturn FROM
        deviceNotReady => "The floppy disk unit isn't ready."L,
        notDiagDiskette =>
          "The floppy diskette isn't the Diagnostic Diskette."L,
        floppyFailure => "An error was found."L,
        ENDCASE => "No error was found."L];
    END;

  FloppyFinished: PROCEDURE [errFlag: BOOLEAN ← FALSE ] =
    BEGIN
    DiagnosticsOps.running ← FALSE;
    DiagnosticsOps.PutLine[IF errFlag THEN "Aborted..."L ELSE "Floppy diagnostic finished."L];
    DiagnosticsOps.PutCR[];
    END;


  -- These routines are for displaying either a number of fields of data or an entire table of fields of data.  The title parameter is displayed prior to display of the data.  If no title is wished, the client may omit this parameter.  The fieldType parameter is used to determine how to interpret the data to be displayed, whether as boolean, character, cardinal, etc.

  DisplayFields: OnlineDiagnostics.DisplayFieldsProc =
    BEGIN
    --"fields" contains the data to be displayed: the name of the field is paired with its value.  "numberOfColumns" is the number of columns that the client wishes the fields to be displayed in; if this parameter is omitted, the default number of columns is three.
    screenWidth: CARDINAL = 80;
    columnWidth, fieldNameWidth, thisColumnNumber: CARDINAL;
    str: LONG STRING = [screenWidth];

    IF numberOfColumns = 0 THEN RETURN;
    columnWidth ← screenWidth/numberOfColumns;

    fieldNameWidth ← 0;
    FOR a: CARDINAL IN [0..LENGTH[fields]) DO
      str.length ← 0;
      AppendMessageText[str, fields[a].fieldName];
      fieldNameWidth ← MAX[fieldNameWidth, str.length + 1];
      ENDLOOP;
    fieldNameWidth ← MIN[
      fieldNameWidth,
      columnWidth -
        ((SELECT fieldType FROM
            boolean, character => 1,
            cardinal => 5,
            hexadecimal => 4,
            hexbyte => 2,
            integer => 6,
            octal => 7,
            ENDCASE => 10) + 2)];

    IF title # tFirst THEN DiagnosticsOps.PutMessage[title];
    thisColumnNumber ← 0;
    FOR a: CARDINAL IN [0..LENGTH[fields]) DO
      str.length ← 0;
      AppendMessageText[str, fields[a].fieldName];
      IF str.length > fieldNameWidth THEN str.length ← fieldNameWidth;
      DiagnosticsOps.PutText[str, fieldNameWidth];
      str.length ← 0;
      AppendFieldData[str, fields[a].fieldValue, fieldType];
      DiagnosticsOps.PutText[str];
      IF thisColumnNumber >= numberOfColumns THEN
        BEGIN thisColumnNumber ← 0; DiagnosticsOps.PutCR[]; END
      ELSE
        BEGIN
        thisColumnNumber ← thisColumnNumber + 1;
        DiagnosticsOps.PutText["  "L];
        END;
      DiagnosticsOps.CheckForAbort[];
      ENDLOOP;
    DiagnosticsOps.PutCR[];
    END;

  DisplayTable: OnlineDiagnostics.DisplayTableProc =
    BEGIN
    --"headers" are the strings displayed at the tops of the columns of data.  "rowNames" are the strings displayed at the beginning of each row of data.  "values" are the values of the data which is to be displayed; value[c][r] is displayed in row r of column c.
    screenWidth: CARDINAL = 80;
    fieldWidth, rowNameWidth: CARDINAL;
    str: LONG STRING = [screenWidth];

    rowNameWidth ← 0;
    FOR r: CARDINAL IN [0..LENGTH[rowNames]) DO
      str.length ← 0;
      AppendMessageText[str, rowNames[r]];
      rowNameWidth ← MAX[rowNameWidth, str.length + 2];
      ENDLOOP;
    fieldWidth ←
      (SELECT fieldType FROM
         boolean, character => 1,
         cardinal => 5,
         hexadecimal => 4,
         hexbyte => 2,
         integer => 6,
         octal => 8,
         ENDCASE => 10) + 2;
    FOR c: CARDINAL IN [0..LENGTH[headers]) DO
      str.length ← 0;
      AppendMessageText[str, headers[c]];
      fieldWidth ← MAX[fieldWidth, str.length + 2];
      ENDLOOP;
    fieldWidth ← MAX[
      fieldWidth, ((screenWidth - rowNameWidth)/LENGTH[headers])];

    IF title # tFirst THEN DiagnosticsOps.PutMessage[title];
    THROUGH [0..rowNameWidth) DO DiagnosticsOps.PutChar[' ]; ENDLOOP;
    FOR c: CARDINAL IN [0..LENGTH[headers]) DO
      str.length ← 0;
      AppendMessageText[str, headers[c]];
      DiagnosticsOps.PutTextCentered[str, fieldWidth];
      ENDLOOP;
    DiagnosticsOps.PutCR[];

    FOR r: CARDINAL IN [0..LENGTH[rowNames]) DO
      str.length ← 0;
      AppendMessageText[str, rowNames[r]];
      DiagnosticsOps.PutText[str, rowNameWidth];
      FOR c: CARDINAL IN [0..LENGTH[headers]) DO
        str.length ← 0;
        AppendFieldData[str, values[c][r], fieldType];
        DiagnosticsOps.PutTextCentered[str, fieldWidth];
        ENDLOOP;
      DiagnosticsOps.PutCR[];
      DiagnosticsOps.CheckForAbort[];
      ENDLOOP;
    END;

  DisplayNumberedTable: OnlineDiagnostics.DisplayNumberedTableProc =
    BEGIN
    -- Numbers are displayed at the beginning of each row of data, beginning with "startNum."  The "rowNameHeader," if any, is displayed at the top of the numbers at the beginning of the rows.  "values" are the values of the data which is to be displayed; values[r*numOfColumns + c] is displayed in row r of column c (rows and columns are numbered starting with 0).
    screenWidth: CARDINAL = 80;
    fieldWidth, rowNameWidth: CARDINAL;
    str: LONG STRING = [screenWidth];
    index: CARDINAL ← 0;

    str.length ← 0;
    AppendMessageText[str, rowNameHeader];
    rowNameWidth ← MAX[5, str.length + 2];
    fieldWidth ←
      (SELECT fieldType FROM
         boolean, character => 1,
         cardinal => 5,
         hexadecimal => 4,
         hexbyte => 2,
         integer => 6,
         octal => 8,
         ENDCASE => 10) + 2;
    fieldWidth ← MAX[fieldWidth, ((screenWidth - rowNameWidth)/numOfColumns)];

    IF title # tFirst THEN DiagnosticsOps.PutMessage[title];
    DiagnosticsOps.PutText[str, rowNameWidth];
    FOR c: CARDINAL IN [0..numOfColumns) DO
      str.length ← 0;
      String.AppendNumber[
        str, c, SELECT numOfColumns FROM 8 => 8, 16 => 16, ENDCASE => 10];
      DiagnosticsOps.PutTextCentered[str, fieldWidth];
      ENDLOOP;
    DiagnosticsOps.PutCR[];

    index ← 0;
    UNTIL index > LENGTH[values] DO
      str.length ← 0;
      String.AppendNumber[
        str, startNum + index,
        SELECT numOfColumns FROM 8 => 8, 16 => 16, ENDCASE => 10];
      DiagnosticsOps.PutText[str, rowNameWidth];
      THROUGH [0..numOfColumns) UNTIL index > LENGTH[values] DO
        str.length ← 0;
        AppendFieldData[str, values[index], fieldType];
        DiagnosticsOps.PutTextCentered[str, fieldWidth];
        index ← index + 1;
        ENDLOOP;
      DiagnosticsOps.PutCR[];
      DiagnosticsOps.CheckForAbort[];
      ENDLOOP;
    END;

  AppendFieldData: PROCEDURE [
    str: LONG STRING, value: UNSPECIFIED,
    fieldType: OnlineDiagnostics.FieldDataType] =
    BEGIN
    StrProc: Format.StringProc = BEGIN String.AppendString[str, s]; END;
    SELECT fieldType FROM
      boolean =>
        Format.Char[StrProc, IF LOOPHOLE[value, BOOLEAN] THEN 'T ELSE 'F];
      cardinal =>
        Format.Number[
          StrProc, LOOPHOLE[value, CARDINAL], Format.NumberFormat[
          10, FALSE, TRUE, 5]];
      character =>
        Format.Char[
          StrProc,
          SELECT LOOPHOLE[value, CHARACTER] FROM
            IN [' ..'↑], IN [140C..'~] => LOOPHOLE[value, CHARACTER],
            ENDCASE => Ascii.ControlG];
      hexadecimal =>
        Format.Number[
          StrProc, LOOPHOLE[value, CARDINAL], Format.NumberFormat[
          16, TRUE, TRUE, 4]];
      hexbyte =>
        Format.Number[
          StrProc, LOOPHOLE[value, CARDINAL] MOD 256, Format.NumberFormat[
          16, TRUE, TRUE, 2]];
      integer =>
        Format.Number[
          StrProc, LOOPHOLE[value, INTEGER], Format.NumberFormat[
          10, FALSE, FALSE, 6]];
      octal =>
        BEGIN
        Format.Number[
          StrProc, LOOPHOLE[value, CARDINAL], Format.NumberFormat[
          8, FALSE, TRUE, 6]];
        Format.Char[StrProc, 'B];
        END;
      ENDCASE =>
        BEGIN
        temp: LONG STRING = [30];
        temp.length ← 0;
        --AppendMessageText[temp, LOOPHOLE[value, OnlineDiagnostics.FloppyMessage]];
        String.AppendString[temp, LOOPHOLE[value, STRING]];
        Format.LongString[StrProc, temp];
        Format.Blanks[StrProc, 10 - temp.length];
        END;
    END;


  PutMessage: PUBLIC OnlineDiagnostics.PutMessageProc =
    BEGIN
    text: LONG STRING = [100];
    text.length ← 0;
    AppendMessageText[text, msg];
    DiagnosticsOps.PutLine[text];
    DiagnosticsOps.CheckForAbort[];
    END;

  AppendMessageText: PROCEDURE [
    text: LONG STRING, msg: OnlineDiagnostics.FloppyMessage] =
    BEGIN
    String.AppendString[
      text,
      SELECT msg FROM
        cFirst => "cFirst"L,
        cCallCSC =>
          "Please call the CSC; tell them the maintenance-panel code."L,
        cCloseWn => "cCloseWn"L,
        cEnsureReady =>
          "Ensure that the floppy disk unit is ready and the door closed."L,
        cExit => "That's all, folks!"L,
        cInsDiffCleanDisk =>
          "Please insert a different head-cleaning diskette."L,
        cInsertCleanDisk => "Please insert a head-cleaning diskette."L,
        cInsertDiagDisk => "Please insert the diagnostic floppy disk. (1)"L,
        cInsertWriteable =>
          "Please insert a diskette that is not write-protected."L,
        cNBNotReady => "Note that the floppy disk unit is not ready."L,
        cOtherDiskErr =>
          "Note: Any other diskette will cause erroneous results."L,
        cRemoveCleanDisk =>
          "Please remove the head-cleaning diskette from the unit."L,
        cRemoveDiskette =>
          "Please remove the diskette from the unit and leave door open."L,
        cLast => "cLast"L,
        hFirst => "hFirst"L,
        hBusy => "  Busy"L,
        hExpec1 => "      Expected = "L,
        hExpec2 => "EXPECTED"L,
        hCRC1 => "CRC Byte 1"L,
        hCRC2 => "CRC Byte 2"L,
        hCRCErr => "  CRC Error"L,
        hDelSector => "  Deleted Sector"L,
        hDiskChng => "  Disk Changed"L,
        hErrDetc => "  Error Detected"L,
        hGoodComp => "  Good Completion"L,
        hHead => "      Head"L,
        hHeadAddr => "Head Address"L,
        hIllglStat => "  Illegal Status"L,
        hIncrtLngth => "  Incorrect Length"L,
        hObser1 => "      Observed = "L,
        hObser2 => "OBSERVED"L,
        hReadHead => "Read Header"L,
        hReadSector => "Read Sector"L,
        hReadStat => "Read Status"L,
        hReady => "  Ready"L,
        hRecal => "Recalibrate"L,
        hRecalErr => "  Recalibrate Error"L,
        hSector => "      Sector"L,
        hSectorAddr => "Sector Address"L,
        hSectorCntErr => "  Sector Count Error"L,
        hSectorLgth => "Sector Length"L,
        hSeekErr => "  Seek Error"L,
        hTimeExc => "TIMES EXECUTED"L,
        hTrack => "      Track"L,
        hTrack0 => "  Track 00"L,
        hTrackAddr => "Track Address"L,
        hTwoSide => "  Double Sided"L,
        hWriteDelSector => "Write Deleted Sectors"L,
        hWritePro => "  Write Protected"L,
        hWriteSector => "Write Sectors"L,
        hLast => "hLast"L,
        iFirst => "iFirst"L,
        iBadContext => "Can't set context."L,
        iBadLabel => "Bad label area."L,
        iBadSector => "Bad sector found."L,
        iBadTrack0 => "Bad track 0."L,
        iCheckPanel => "Check the MP code and possibly the Summary Error Log."L,
        iCIERec => "Command in error: Recalibrate"L,
        iCleanDone => "Head cleaning completed"L,
        iCleanProgress => "Head cleaning in progress."L,
        iErrDet => "  Error Detected"L,
        iErrNoCRCErr => "A data error without a CRC error has occured."L,
        iExerWarning =>
          "Warning: This test will destroy the contents of the diskette."L,
        iFormDone => "Formatting successfully completed."L,
        iFormProgress => "Formatting in progress."L,
        iFormWarning =>
          "Warning: Formatting will destroy the contents of the diskette."L,
        iHardErr => "A hard error has occured."L,
        iHeadDataErr => "    A HEADER DATA ERROR HAS OCCURED."L,
        iInsertDiagDisk => "Please insert the diagnostic floppy disk. (2)"L,
        iInsertFormDisk => "Please insert a formatted diskette."L,
        iOneSided => "This is a single-sided diskette."L,
        iRunStdTest => "Please run the standard test of the floppy disk unit."L,
        iSoftErr => "Too many soft errors have occured."L,
        iTnx => "Thank you"L,
        iTwoSided => "This is a double-sided diskette."L,
        iUnitNotReady => "The floppy disk unit is still not ready."L,
        iVerDataErr => "    A VERIFY DATA ERROR HAS OCCURED."L,
        iLast => "iLast"L,
        tFirst => "tFirst"L,
        tByteCnt => "Byte Count"L,
        tCIERH => "Command in error: Read Header"L,
        tCIERS => "Command in error: Read Sectors"L,
        tCIEVer => "Command in error: Verify"L,
        tCIEWDS => "Command in error: Write Deleted Sectors"L,
        tCIEWS => "Command in error: Write Sectors"L,
        tHeadDataErr => "    A HEADER DATA ERROR HAS OCCURED."L,
        tHeadDisp => "FLOPPY DISK HEADER DISPLAY IN HEXADECIMAL"L,
        tHeadErrDisp => "FLOPPY DISK HEADER ERROR DISPLAY IN HEXADECIMAL"L,
        tSectorDisp => "FLOPPY SECTOR DISPLAY"L,
        tStatDisp => "FLOPPY STATUS DISPLAY"L,
        tSummErrLog => "SUMMARY ERROR LOG"L,
        tVerDataErr => "    A VERIFY DATA ERROR HAS OCCURED."L,
        tLast => "tLast"L,
        yFirst => "yFirst"L,
        yDispSects => "Display the sectors with the last data error?"L,
        yDispExpObsData =>
          "Display the header-error expected and observed data?"L,
        yDoorJustOpened => "Was the floppy disk unit door just opened?"L,
        yDoorOpenNow => "Is the floppy disk unit door open now?"L,
        yDoorOpenShut =>
          "Was the floppy disk unit door just opened and closed again?"L,
        yIsItDiagDisk => "Is the diskette the diagnostic floppy disk?"L,
        yIsItWrProt => "Is the diskette write-protected?"L,
        yStillContinue => "Do you still wish to continue?"L,
        yStillSure => "Are you still sure?"L,
        yLast => "yLast"L,
        ENDCASE => "Unknown floppy message?!?"L];
    END;

  END.