-- SortLabels.mesa
--   Edited by Sweet, 29-Sep-81 22:38:39

DIRECTORY
  Ascii,
  Exec,
  Format,
  GSort,
  Inline,
  MDSStorage,
  OutputDefs,
  PressDefs,
  PrintUtilities,
  Segments,
  Streams,
  String;

SortLabels: PROGRAM 
  IMPORTS  
    Exec, GSort, Inline, MDSStorage, OutputDefs, PressDefs,
    PrintUtilities, Segments, Streams, String =
  BEGIN OPEN PressDefs, String, Streams;

  in: Streams.Handle;

  Label: TYPE = LONG BASE POINTER TO LabelBody;
  RelLabel: TYPE = Label RELATIVE POINTER TO StringBody;
  LabelBody: TYPE = RECORD [
   count, last: [0..256), 
   lines: ARRAY [0..8) OF RelLabel];
  nextRP: RelLabel;

  AddLine: PROC =
    BEGIN
    s: LONG STRING = @buffer[nextRP];
    s↑ ← [length: 0, maxlength: line.length, text: NULL];
    String.AppendString[s, line];
    buffer.lines[buffer.count] ← nextRP;
    nextRP ← nextRP + String.WordsForString[line.length];
    WHILE s.length # 0 AND s[s.length-1] = Ascii.SP DO
      s.length ← s.length - 1;
      ENDLOOP;
    IF ~slashSeen AND line[0] = '/ THEN {
      slashSeen ← TRUE; buffer.last ← buffer.count};
    buffer.count ← buffer.count + 1;
    END;

  StartLabel: PROC =
    BEGIN
    buffer.count ← 0; buffer.last ← 0;
    slashSeen ← FALSE;
    nextRP ← LOOPHOLE[SIZE[LabelBody]];
    END;

  slashSeen: BOOLEAN;
  pfdBody: PressFileDescriptor;
  pfd: POINTER TO PressFileDescriptor = @pfdBody;
  Mica: TYPE = CARDINAL;
  MBox: TYPE = RECORD [x,y,w,h: Mica];
  CharHeight: Mica;
  CharWidth: POINTER TO ARRAY CHARACTER OF Mica;
  TextCharWidth: ARRAY CHARACTER OF Mica;
  TextCharHeight: Mica;

  PointsToMicas: PROC [points: CARDINAL] RETURNS [Mica] =
    {RETURN [Inline.LongDiv[Inline.LongMult[points, MicasPerInch],72]]};

  StringWidth: PROC [s: LONG STRING] RETURNS [l: Mica] =
    BEGIN
    l ← 0;
    FOR i: CARDINAL IN [0..s.length) DO
      l ← l + CharWidth[s[i]];
      ENDLOOP;
    END;

  LineY: PROC [box: POINTER TO MBox, line, of: CARDINAL, lead: Mica ← 0] 
    RETURNS [Mica] =
    BEGIN
    h: Mica = CharHeight;
    bottom: Mica;
    line ← of-1-line; -- count from top
    bottom ← (box.h- of*h - (of-1)*lead)/2;
    RETURN [box.y + bottom + (line-1)*(h+lead)];
    END;

  LJLine: PROC [s: LONG STRING, box: POINTER TO MBox, line, of: CARDINAL, lead: Mica ← 1] =
    BEGIN
    ss: STRING = MDSStorage.String[s.length];
    y: Mica = LineY[box: box, line: line, of: of, lead: lead];
    String.AppendString[ss, s];
    PutText[pfd, ss, box.x, y];
    MDSStorage.FreeString[ss];
    END;

  P1: Mica = PointsToMicas[1];
  P2: Mica = PointsToMicas[2];
  M1: Mica = MicasPerInch;
  M12: Mica = MicasPerInch/2;
  M14: Mica = MicasPerInch/4;
  M34: Mica = (3*MicasPerInch)/4;
  M38: Mica = (3*MicasPerInch)/8;

  TextFont: PROC =
    BEGIN
    SetFont[p: pfd, Name: "Helvetica", PointSize: outPointSize, Face: 0];
    CharHeight ← TextCharHeight;
    CharWidth ← @TextCharWidth;
    END;

  outPointSize: CARDINAL ← 10;
  
  DigestFonts: PROC =
    BEGIN
    [] ← PrintUtilities.FindFontWidths[
      family: "Helvetica"L,
      points: outPointSize,
      weight: medium,
      slope: regular,
      widths: LOOPHOLE[@TextCharWidth]];
    TextCharHeight ← PointsToMicas[outPointSize];
    END;

  ReadLabel: PROC =
    BEGIN
    StartLabel[];
    DO
      ReadLine[];
      IF line.length = 0 AND (buffer.count # 0 OR Streams.Ended[in]) THEN EXIT;
      IF line.length # 0 THEN AddLine[];
      ENDLOOP;
    END;

  ReadLine: PROC =
    BEGIN
    c: CHARACTER;
    n: CARDINAL ← 0;
    line.length ← 0;
    BEGIN
    DO
      c ← Streams.GetByte[in ! Streams.End[] => EXIT];
      IF c = Ascii.CR THEN EXIT;
      IF n = line.maxlength-1 THEN GO TO tooLong;
      line[n] ← c;
      n ← n + 1;
      ENDLOOP;
    line.length ← n;
    EXITS
      tooLong => {
	WriteLine["Line truncated:"L];
	WriteLine[line];
	line.length ← n};
    END;
    END;

  sline: STRING ← [200];
  line: LONG STRING ← sline;
  onPage: CARDINAL ← 0;
  linesPerPage: CARDINAL ← 72;

  Put: PROCEDURE [p: Label, len: CARDINAL] =
    BEGIN 
    IF paginateList THEN {
      IF onPage + p.count > linesPerPage THEN {
	OutputDefs.PutChar[Ascii.FF]; onPage ← 0};
      onPage ← onPage + p.count + 1};
    FOR i: CARDINAL IN [0..p.count) DO
      OutputDefs.PutString[@p[p.lines[i]]];
      OutputDefs.PutCR[];
      ENDLOOP;
    OutputDefs.PutCR[];
    IF makePress THEN PressLabel[p];
    END;

  row, col: CARDINAL;
  LWidth: Mica = (8*M1 + M12)/3;
  LHeight: Mica = M1;
  yDelta: Mica ← 0;
  scaleFactor: CARDINAL ← 100;
  
  Scale: PROC [d: Mica] RETURNS [Mica] = { -- d must be > 0
    RETURN [Inline.LongDiv[Inline.LongMult[d, scaleFactor], 100]]};

  PressLabel: PROC [p: Label] =
    BEGIN
    name: STRING = [100];
    l1: LONG STRING = @p[p.lines[0]];
    comma: CARDINAL ← l1.length;
    j: CARDINAL;
    box: MBox;
    IF row = 11 THEN {row ← 0; col ← col + 1};
    IF col = 3 THEN {WritePage[pfd]; col ← 0};
    box.x ← M38 + col * LWidth;
    box.y ← yDelta + 10*M1 - Scale[row*LHeight];
    box.w ← LWidth - M38;
    box.h ← M1;
    FOR i: CARDINAL IN [0..l1.length) DO 
      IF l1[i] = ', THEN {comma ← i; EXIT};
      ENDLOOP;
    j ← comma + 1;
    WHILE j < l1.length AND l1[j] = Ascii.SP DO j ← j + 1; ENDLOOP;
    FOR i: CARDINAL IN [j..l1.length) DO 
      String.AppendChar[name, l1[i]];
      ENDLOOP;
    IF name.length # 0 AND name[name.length-1] # Ascii.SP THEN
      String.AppendChar[name, Ascii.SP];
    FOR i: CARDINAL IN [0..comma) DO 
      String.AppendChar[name, l1[i]];
      ENDLOOP;
    LJLine[s: name, box: @box, line: 0, of: p.last];
    FOR i: CARDINAL IN [1..p.last) DO
      LJLine[s: @p[p.lines[i]], box: @box, line: i, of: p.last];
      ENDLOOP;
    row ← row + 1;
    END;

  InitPressThings: PROC =
    BEGIN
    InitPressFileDescriptor[pfd, "Labels.press"];
    DigestFonts[];
    TextFont[];
    row ← col ← 0;
    END;

  OtherComp: ARRAY [0..5) OF CompProc;
  flagChar: ARRAY [0..5) OF CHARACTER;
  down: ARRAY [0..5) OF BOOLEAN ← ALL[FALSE];
  nOthers: CARDINAL ← 0;

  CompProc: TYPE = PROCEDURE [p1, p2: Label, c: CHARACTER] 
    RETURNS [i: INTEGER];

  AddCompare: PROC [p: CompProc, c: CHARACTER, sortDown: BOOLEAN] =
    BEGIN
    IF nOthers = 5 THEN {WriteLine["Too many keys"L]; RETURN};
    OtherComp[nOthers] ← p;
    flagChar[nOthers] ← c;
    down[nOthers] ← sortDown;
    nOthers ← nOthers + 1;
    END;

  Date: TYPE = RECORD [SELECT OVERLAID * FROM
    lc => [val: LONG CARDINAL],
    ymd => [month, day: [0..256), year: CARDINAL],
    ENDCASE];

  GetDate: PROC [s: LONG STRING] RETURNS [d: Date] =
    BEGIN
    i: CARDINAL ← 1;
    SB: PROC = {
      WHILE i < s.length AND (s[i] = Ascii.SP OR s[i] = Ascii.TAB) DO 
        i ← i+1; 
        ENDLOOP};
    N: PROC RETURNS [n: CARDINAL ← 0] = {
      SB[];
      WHILE i < s.length AND s[i] IN ['0..'9] DO
        n ← n * 10 + (s[i] - '0);
        i ← i+1; 
	ENDLOOP};
      
    d.val ← 0;
    WHILE i < s.length AND s[i] # Ascii.SP AND s[i] # Ascii.TAB DO
      i ← i + 1;
      ENDLOOP;
    d.month ← N[]; SB[];
    IF i = s.length OR s[i] # '/ THEN RETURN[[lc[0]]];
    i ← i+1;
    d.day ← N[]; SB[];
    IF i = s.length OR s[i] # '/ THEN RETURN[[lc[0]]];
    i ← i+1;
    d.year ← N[];
    IF d.year = 0 THEN RETURN[[lc[0]]];
    IF d.year < 100 THEN d.year ← d.year + 1900;
    END;

  CompareBirthdays: CompProc = {
    s1: LONG STRING = FindFlag[p1, 'B];
    s2: LONG STRING = FindFlag[p2, 'B];
    d1, d2: Date;
    IF s1 = NIL THEN {IF s2 = NIL THEN RETURN[0] ELSE RETURN[-1]};
    IF s2 = NIL THEN RETURN[1];
    d1 ← GetDate[s1];
    d2 ← GetDate[s2];
    SELECT d1.val FROM
      < d2.val => RETURN[-1];
      > d2.val => RETURN[1];
      ENDCASE;
    RETURN[0]};

  CompareFlag: CompProc = {
    s1: LONG STRING = FindFlag[p1, c];
    s2: LONG STRING = FindFlag[p2, c];
    i1, i2: CARDINAL ← 1;
    IF s1 = NIL THEN {IF s2 = NIL THEN RETURN[0] ELSE RETURN[-1]};
    IF s2 = NIL THEN RETURN[1];
    WHILE i1 < s1.length AND s1[i1] # Ascii.SP AND s1[i1] # Ascii.TAB DO
      i1 ← i1 + 1;
      ENDLOOP;
    WHILE i2 < s2.length AND s2[i2] # Ascii.SP AND s2[i2] # Ascii.TAB DO
      i2 ← i2 + 1;
      ENDLOOP;
    WHILE i1 < s1.length AND (s1[i1] = Ascii.SP OR s1[i1] = Ascii.TAB) DO
      i1 ← i1 + 1;
      ENDLOOP;
    WHILE i2 < s2.length AND (s2[i2] = Ascii.SP OR s2[i2] = Ascii.TAB) DO
      i2 ← i2 + 1;
      ENDLOOP;
    DO
      IF i1 = s1.length THEN 
	IF i2 = s2.length THEN RETURN[0]
	ELSE RETURN[-1];
      IF i2 = s2.length THEN RETURN[1];
      SELECT s1[i1] FROM
	<s2[i2] => RETURN [-1];
	>s2[i2] => RETURN [1];
	ENDCASE;
      i1 ← i1+1; i2 ← i2+1;
      ENDLOOP};

  FindFlag: PROC [p: Label, c: CHARACTER] RETURNS [LONG STRING] =
    BEGIN
    FOR i: CARDINAL IN [p.last..p.count) DO
      s: LONG STRING = @p[p.lines[i]];
      IF s.length >= 2 AND String.UpperCase[s[1]] = c THEN
	RETURN [s];
      ENDLOOP;
    RETURN[NIL];
    END;

  FindZip: PROC [p: Label] RETURNS [zip: LONG CARDINAL ← 0] =
    BEGIN
    s: LONG STRING = @p[p.lines[p.last-1]];
    i: INTEGER ← s.length-1;
    IF p.last = 0 THEN RETURN;
    WHILE i >= 0 AND s[i] = Ascii.SP DO i ← i-1; ENDLOOP;
    WHILE i >= 0 AND s[i] IN ['0..'9] DO i ← i-1; ENDLOOP;
    i ← i+1; 
    WHILE i < INTEGER[s.length] AND s[i] IN ['0..'9] DO
      zip ← zip*10 + s[i] - '0;
      i ← i+1;
      ENDLOOP;
    END;
    
  CompareZip: CompProc = {
    z1: LONG CARDINAL = FindZip[p1];
    z2: LONG CARDINAL = FindZip[p2];
    SELECT z1 FROM
      < z2 => RETURN [-1];
      > z2 => RETURN[1];
      ENDCASE => RETURN[0]};

  CompareNames: PROC [s1, s2: LONG STRING] RETURNS [INTEGER] =
    BEGIN
    RETURN[String.CompareStrings[s1, s2]];
    END;

  Compare: PROCEDURE [p1, p2: Label] RETURNS [i: INTEGER] =
    BEGIN
    FOR c: CARDINAL IN [0..nOthers) DO
      i ← OtherComp[c][p1, p2, flagChar[c]];
      IF down[c] THEN i ← -i;
      IF i # 0 THEN RETURN;
      ENDLOOP;
    i ← CompareNames[@p1[p1.lines[0]], @p2[p2.lines[0]]];
    RETURN
    END;

  buffer: Label;

  makePress, paginateList: BOOLEAN ← FALSE;
  eh: Exec.Handle ← NIL;
  
  EOut: Format.StringProc;
  WriteLine: PROC [s: LONG STRING] = {EOut[s]; Exec.PutChar[eh, Ascii.CR]};

  DoIt: Exec.ExecProc =
    BEGIN
    c: CHARACTER;
    file, switches: LONG STRING ← NIL;
      BEGIN
      i: CARDINAL;
      GetSwitchNumber: PROC RETURNS [n: CARDINAL ← 0] =
        BEGIN
	i ← i+1;
	WHILE switches[i] IN ['0..'9] DO
	  n ← n * 10 + (switches[i] - '0);
	  i ← i+1;
	  ENDLOOP;
	i ← i-1; -- since loop increments at bottom
	END;
      NumberNext: PROC RETURNS [BOOLEAN] = {
        RETURN[i+1 < switches.length AND switches[i+1] IN ['0..'9]]};
	
      eh ← h;
      EOut ← Exec.OutputProc[eh];
      
      [file, switches] ← Exec.GetToken[eh];
      
      in ← Streams.NewStream[file, Streams.Read !
	Segments.FileNameProblem[] => GO TO cantFind];
      i ← 0;
      WHILE i < switches.length DO
	SELECT (c ← switches[i]) FROM
	  'z, 'Z => AddCompare[CompareZip, 0C, c = 'Z];
	  'b, 'B => AddCompare[CompareBirthdays, 'B, c = 'B];
	  's, 'S => 
	    IF NumberNext[] THEN scaleFactor ← GetSwitchNumber[]
	    ELSE AddCompare[CompareFlag, 'S, c = 'S];
	  'y, 'Y =>
	    IF NumberNext[] THEN yDelta ← GetSwitchNumber[]
	    ELSE AddCompare[CompareFlag, 'Y, c = 'Y];
	  'p, 'P =>
	    IF NumberNext[] THEN outPointSize ← GetSwitchNumber[]
	    ELSE AddCompare[CompareFlag, 'P, c = 'P];
	  IN ['a..'y], IN ['A..'Y] => 
	    AddCompare[CompareFlag, String.UpperCase[c], c IN ['A..'Z]];
	  '$ =>  makePress ← TRUE;
	  '+ => {
	    paginateList ← TRUE;
	    IF NumberNext[] THEN
	       linesPerPage ← GetSwitchNumber[]};
	  ENDCASE => GO TO badSwitch;
	i ← i+1;
	ENDLOOP;
      LOOPHOLE[OutToSort, GSort.Port].out ← GSort.Sort;
      buffer ← LOOPHOLE[OutToSort, GSort.SortStarter][
        nextItem: @OutToSort,
        put: Put,
        compare: Compare,
        expectedItemSize: 100];
    -- go through list of names, calling OutToSort
      DO
	ReadLabel[];
	IF buffer.count = 0 THEN EXIT;
	buffer ← OutToSort[nextRP];
        ENDLOOP;
      Streams.Destroy[in];
      -- shut down the sort package (and call Put many times)
      OutputDefs.OpenOutput["Labels",".list"L];
      IF makePress THEN InitPressThings[];
      LOOPHOLE[OutToSort, GSort.SortStopper][];
      OutputDefs.CloseOutput[];
      IF makePress THEN {
	WritePage[pfd];
	ClosePressFile[pfd]};
      WriteLine["--done"L];
      EXITS
        cantFind => WriteLine[" file not found"L];
        badSwitch => WriteLine[" bad switch"L];
      END;
    IF file # NIL THEN [] ← Exec.FreeTokenString[file];
    IF switches # NIL THEN [] ← Exec.FreeTokenString[switches];
    END;

  OutToSort: GSort.SortItemPort;

  Exec.AddCommand["Labels.~", DoIt];
  END.