-- LedgerControl.mesa  Edited by Sweet, March 30, 1981  10:36 PM

DIRECTORY
  AltoDefs: FROM "altodefs" USING [CharsPerPage],
  GPsortDefs: FROM "gpsortdefs" USING [CompareProcType, Sort],
  IODefs: FROM "iodefs" USING [
    ControlZ, CR, DEL, FF, ReadChar, ReadID, Rubout, SP, TAB, WriteChar, 
    WriteLine, WriteString],
  LedgerDefs: FROM "ledgerdefs" USING [
    bravoLedger, categoryKey, Column, DataInput, dateFirst, Destination, 
    EndOfFile, FormCreate, InputLine,
    instream, lastCheck, lastDate, LedgerOptions, 
    LedgerOut, LedgerOutput, MoneyToString, NewItem, NullCheck, NullDate, pageGroup, 
    PageGroup, ProduceDetailReport, ReadUserCm, SortItem,
    ytdTotal],
  MiscDefs: FROM "miscdefs" USING [CallDebugger],
  OutputDefs: FROM "outputdefs" USING [
    CloseOutput, OpenOutput, outStream, PutChar, PutCR, PutDecimal, PutString, 
    PutSubString],
  PressDefs,
  SegmentDefs: FROM "segmentdefs" USING [
    DefaultVersion, FileHandle, FileNameError, LockFile, NewFile, Read, 
    ReleaseFile, UnlockFile],
  StreamDefs: FROM "streamdefs" USING [
    CreateByteStream, GetIndex, Read, StreamHandle, StreamIndex],
  StringDefs: FROM "stringdefs" USING [
    AppendChar, AppendDecimal, AppendString, SubString, SubStringDescriptor];

LedgerControl: PROGRAM
  IMPORTS GPsortDefs, IODefs, LedgerDefs, MiscDefs, OutputDefs, PressDefs, SegmentDefs, StreamDefs, StringDefs
  EXPORTS LedgerDefs =
  BEGIN OPEN PressDefs, LedgerDefs;

  debugging: BOOLEAN ← FALSE;

  BadData: PUBLIC SIGNAL = CODE;
  BadState: PUBLIC SIGNAL = CODE;
  WarnS: PUBLIC PROCEDURE [s1, s2: STRING] =
    BEGIN OPEN IODefs;
    WriteChar[CR];
    WriteString[s1]; WriteChar[' ];
    WriteLine[IF s2 = NIL THEN "<NIL>" ELSE s2];
    IF debugging THEN MiscDefs.CallDebugger["from WarnS"];
    SIGNAL BadState;
    END;

  WarnSS: PUBLIC PROCEDURE [s1: STRING, ss2: StringDefs.SubString] =
    BEGIN OPEN IODefs;
    i: CARDINAL;
    WriteChar[CR];
    WriteLine[s1];
    WriteLine[ss2.base];
    FOR i IN (0..ss2.offset) DO
      WriteChar[IF ss2.base[i-1] = TAB THEN TAB ELSE SP];
      ENDLOOP;
    WriteChar['↑];
    WriteChar[CR];
    IF debugging THEN MiscDefs.CallDebugger["from WarnSS"];
    SIGNAL BadData;
    END;

  pfdbody: PressDefs.PressFileDescriptor;
  pfd: PUBLIC POINTER TO PressDefs.PressFileDescriptor ← @pfdbody;
  
  dest: Destination;
  StartFile: PROCEDURE [name: STRING, where: Destination] =
    BEGIN
    dest ← where;
    InitPressFileDescriptor[pfd, name];
    END;
    
  FinishFile: PROCEDURE =
    BEGIN
    PressDefs.ClosePressFile[pfd];
    END;

  LC: PROCEDURE [c1: CHARACTER] RETURNS [CHARACTER] = INLINE
    BEGIN
    RETURN [IF c1 IN ['A..'Z] THEN c1-'A+'a ELSE c1];
    END;
  
  ComparePayee: PROCEDURE [s1, s2: STRING] RETURNS [INTEGER] =
    BEGIN
    c1, c2: CHARACTER;
    i: CARDINAL;

    FOR i IN [0..MIN[s1.length, s2.length]) DO
      c1 ← LC[s1[i]];
      c2 ← LC[s2[i]];
      IF c1 = IODefs.TAB THEN RETURN [IF c2 = IODefs.TAB THEN 0 ELSE -1];
      IF c2 = IODefs.TAB THEN RETURN[1];
      SELECT c1 FROM
	>c2 => RETURN[1];
	<c2 => RETURN[-1];
	ENDCASE;
      ENDLOOP;
    SELECT s1.length FROM
      >s2.length => RETURN[1];
      <s2.length => RETURN[-1];
      ENDCASE => RETURN[0];
    END;

  OrderByCheck: PROCEDURE[p1, p2: POINTER] RETURNS [INTEGER] =
    BEGIN
    sip1: POINTER TO SortItem = p1;
    sip2: POINTER TO SortItem = p2;
 
    SELECT sip1.check FROM
      >sip2.check => RETURN[1];
      <sip2.check => RETURN[-1];
      ENDCASE;
    SELECT sip1.date.cv FROM
      >sip2.date.cv => RETURN[1];
      <sip2.date.cv => RETURN[-1];
      ENDCASE;
    RETURN [ComparePayee[@sip1.string, @sip2.string]];
    END;

  OrderByDate: PROCEDURE[p1, p2: POINTER] RETURNS [INTEGER] =
    BEGIN
    sip1: POINTER TO SortItem = p1;
    sip2: POINTER TO SortItem = p2;
 
    SELECT sip1.date.cv FROM
      >sip2.date.cv => RETURN[1];
      <sip2.date.cv => RETURN[-1];
      ENDCASE;
    SELECT sip1.check FROM
      >sip2.check => RETURN[1];
      <sip2.check => RETURN[-1];
      ENDCASE;
    RETURN [ComparePayee[@sip1.string, @sip2.string]];
    END;

  OrderByPayee: PROCEDURE[p1, p2: POINTER] RETURNS [INTEGER] =
    BEGIN
    sip1: POINTER TO SortItem = p1;
    sip2: POINTER TO SortItem = p2;
 
    SELECT ComparePayee[@sip1.string, @sip2.string] FROM
      >0 => RETURN[1];
      <0 => RETURN[-1];
      ENDCASE;
    SELECT sip1.date.cv FROM
      >sip2.date.cv => RETURN[1];
      <sip2.date.cv => RETURN[-1];
      ENDCASE;
    SELECT sip1.check FROM
      >sip2.check => RETURN[1];
      <sip2.check => RETURN[-1];
      ENDCASE => RETURN[0];
    END;

  prevCheck: CARDINAL ← 0;
  markGaps: BOOLEAN ← FALSE;
  bravoOut: PUBLIC BOOLEAN ← FALSE;
  firstOut: PUBLIC BOOLEAN ← TRUE;
  AsciiOut: PROCEDURE [p: POINTER, len: CARDINAL] =
    BEGIN OPEN OutputDefs;
    sip: POINTER TO SortItem = p;
    desc: StringDefs.SubStringDescriptor ←
      [base: @sip.string, offset: 0, length: NULL];
    ss: StringDefs.SubString = @desc;

    PutDecimal[sip.date.month];
    PutChar['/];
    PutDecimal[sip.date.day];
    PutChar['/];
    PutDecimal[sip.date.year];
    PutChar[IODefs.TAB];
    IF firstOut THEN IODefs.WriteString["Writing..."]
    ELSE
      IF markGaps AND prevCheck # 0 AND sip.check # prevCheck+1 THEN
        PutChar['*];
    PutDecimal[sip.check];
    PutChar[IODefs.TAB];
    FOR i IN [0..sip.string.length) DO
      IF sip.string.text[i] = IODefs.ControlZ THEN
	BEGIN ss.length ← i; EXIT; END;
      REPEAT
        FINISHED => ss.length ← sip.string.length;
      ENDLOOP;
    PutSubString[ss];
    IF bravoOut THEN
      BEGIN
      PutChar[IODefs.ControlZ];
      IF firstOut THEN PutString["(0,4928)(1,6080)(2,9878)"];
      END;
    PutCR[];
    prevCheck ← sip.check;
    firstOut ← FALSE;
    END;

  inFile: SegmentDefs.FileHandle ← NIL;
  GetInFile: PROCEDURE =
    BEGIN OPEN IODefs, SegmentDefs;
    infileName: STRING ← [40];
    DO
      WriteString[" input file: "]; ReadID[infileName]; WriteChar[CR];
      inFile ← NewFile[infileName, Read, DefaultVersion !
	FileNameError => LOOP];
      EXIT;
      ENDLOOP;
    LockFile[inFile];
    EndOfFile ← FALSE;
    END;

  CloseInFile: PROCEDURE =
    BEGIN
    IF instream # NIL THEN
      BEGIN
      instream.destroy[instream];
      instream ← NIL;
      END;
    IF inFile # NIL THEN
      BEGIN
      SegmentDefs.UnlockFile[inFile];
      SegmentDefs.ReleaseFile[inFile];
      inFile ← NIL;
      END;
    END;

  Confirm: PROCEDURE [prompt: STRING] RETURNS [BOOLEAN] =
    BEGIN OPEN IODefs;
    WriteString[prompt];
    DO
      SELECT ReadChar[] FROM
	'y, 'y, CR => BEGIN WriteLine[" Yes"]; RETURN [TRUE]; END;
	'n, 'N, DEL => BEGIN WriteLine[" No"]; RETURN[FALSE]; END;
	ENDCASE;
      ENDLOOP;
    END;

  ProduceBravoLedger: PROCEDURE  =
    BEGIN OPEN StringDefs;

    bravoLedger ← TRUE;
    firstOut ← TRUE;
    instream ← StreamDefs.CreateByteStream[inFile, StreamDefs.Read];
    EndOfFile ← FALSE;
    ytdTotal ← ALL[0];
    IODefs.WriteString["Reading..."];
    GPsortDefs.Sort[
      get: InputLine,
      put: LedgerOut,
      compare: OrderByDate,
      expectedItemSize: 25,
      maxItemSize: 500,
      reservedPages: 110];
    instream.destroy[instream]; instream ← NIL;
    NewItem[NullDate, 0, NIL, 0]; -- to finish current month
    END;

  DoDetail: PROCEDURE  =
    BEGIN OPEN StringDefs;

    bravoLedger ← TRUE;
    firstOut ← TRUE;
    instream ← StreamDefs.CreateByteStream[inFile, StreamDefs.Read];
    EndOfFile ← FALSE;
    IODefs.WriteString["Reading..."];
    ProduceDetailReport[];
    instream.destroy[instream]; instream ← NIL;
    END;

  ProduceLedger: PROCEDURE [dest: Destination, grp: PageGroup, ext: STRING] =
    BEGIN OPEN StringDefs;
    fileName: STRING ← [40];
    i: CARDINAL;

    bravoLedger ← FALSE;
    instream ← StreamDefs.CreateByteStream[inFile, StreamDefs.Read];
    EndOfFile ← FALSE;
    FOR i IN [0..outName.length) WHILE outName[i] # '. DO
      AppendChar[fileName, outName[i]];
      ENDLOOP;
    AppendString[fileName, ext];
    pageGroup ← grp;
    StartFile[fileName, dest];
    IF grp # left THEN PressDefs.WritePage[pfd];
    ytdTotal ← ALL[0];
    IODefs.WriteString["Reading..."];
    GPsortDefs.Sort[
      get: InputLine,
      put: LedgerOut,
      compare: OrderByDate,
      expectedItemSize: 25,
      maxItemSize: 500,
      reservedPages: 110];
    instream.destroy[instream]; instream ← NIL;
    NewItem[NullDate, 0, NIL, 0]; -- to finish current month
    IF grp # right THEN PressDefs.WritePage[pfd];
    FinishFile[];
    END;

  i: CARDINAL;
  outName: STRING ← [40];
  outFiles: CARDINAL ← 0;
  tabString: PUBLIC STRING ← NIL;

  maxBSize: PUBLIC CARDINAL ← 30000;
  BravoNewPage: PUBLIC PROCEDURE =
    BEGIN OPEN OutputDefs;
    index: StreamDefs.StreamIndex = StreamDefs.GetIndex[outStream];
    pos: CARDINAL = index.page * AltoDefs.CharsPerPage + index.byte;
    IF pos > maxBSize THEN
      BEGIN
      CloseOutput[];
      outFiles ← outFiles + 1;
      OpenOutputFile[];
      PutChar[IODefs.FF]; PutChar[IODefs.ControlZ]; 
      PutString[tabString]; PutCR[];
      END
    ELSE BEGIN PutChar[IODefs.FF]; PutCR[]; END;
    END;

  OpenOutputFile: PROCEDURE =
    BEGIN
    outExt: STRING ← [20];
    dotSeen: BOOLEAN ← FALSE;
    outExt.length ← 0;
    FOR i IN [0..outName.length) DO
      IF outName[i] = '. THEN dotSeen ← TRUE;
      IF dotSeen THEN StringDefs.AppendChar[outExt, outName[i]];
      ENDLOOP;
    IF outFiles > 1 THEN StringDefs.AppendDecimal[outExt, outFiles];
    OutputDefs.OpenOutput[outName, outExt];
    END;

  -- *************************************************************
  --  Main body
  -- *************************************************************

  BEGIN -- to set up exits
  START LedgerOptions;
  START FormCreate;
  START DataInput;
  START LedgerOutput;
  ReadUserCm[ ! BadData, BadState =>GO TO fini];
  DO OPEN IODefs, OutputDefs;
    ENABLE 
      BEGIN
      Rubout =>
        BEGIN CloseInFile[]; WriteString[" XXX"]; LOOP END;
      BadData, BadState =>
        BEGIN CloseInFile[]; WriteString[" ABORTED"]; LOOP END;
      END;
    dateFirst ← TRUE;  -- unless told otherwise
    lastDate ← NullDate;
    lastCheck ← NullCheck;
    WriteChar[IODefs.CR];
    WriteChar['←];
    SELECT ReadChar[] FROM
      'r, 'R, 's, 'S => 
	BEGIN
	compare: GPsortDefs.CompareProcType;

        markGaps ← FALSE;
	WriteString["Reorder"];
	GetInFile[];
	instream ← StreamDefs.CreateByteStream[inFile, StreamDefs.Read];
	DO 
          WriteString["  Sorted by "];
	  SELECT ReadChar[] FROM
	    'c, 'C, 'n, 'N => 
	      BEGIN
	      WriteString["check number"];
              compare ← OrderByCheck;
              markGaps ←
		Confirm["  Mark gaps and non-zero duplicates?"];
	      END;
	    'p, 'P => 
	      BEGIN
	      WriteString["payee"];
              compare ← OrderByPayee;
	      END;
	    DEL => SIGNAL Rubout;
	    ENDCASE => 
	      BEGIN
	      WriteString["date"];
              compare ← OrderByDate;
	      END;
	  IF Confirm[" [Confirm]"] THEN EXIT;
	  WriteChar[CR];
	  ENDLOOP;
	WriteString["Output to: "];
	ReadID[outName];
        outFiles ← 1; OpenOutputFile[];
	WriteChar[CR];
        bravoOut ← Confirm["Bravo format? "];
        WriteString["Reading..."];
        firstOut ← TRUE; prevCheck ← 0;
	GPsortDefs.Sort[
	  get: InputLine,
	  put: AsciiOut,
	  compare: compare,
	  expectedItemSize: 25,
	  maxItemSize: 500,
	  reservedPages: 110];
	CloseInFile[];
        CloseOutput[];
	END;
      'b, 'B => 
	BEGIN
	WriteString["Bravo format ledger "];
	GetInFile[];
	instream ← StreamDefs.CreateByteStream[inFile, StreamDefs.Read];
	WriteString["Output to: "];
	ReadID[outName];
        outFiles ← 1; OpenOutputFile[];
	WriteChar[CR];
        ProduceBravoLedger[];
        CloseOutput[];
	END;
      'd, 'D => 
	BEGIN
	WriteString["Detail Report "];
	GetInFile[];
	instream ← StreamDefs.CreateByteStream[inFile, StreamDefs.Read];
	WriteString["Output to: "];
	ReadID[outName];
        outFiles ← 1; OpenOutputFile[];
	WriteChar[CR];
        bravoOut ← Confirm["Figure spaces? "];
        DoDetail[];
        CloseOutput[];
	END;
      'p, 'P, 'l, 'L =>
	BEGIN
	d: Destination;
	WriteString["Produce ledger from"];
	GetInFile[];
	WriteString["Output name: "];
	ReadID[outName];
	WriteChar[CR];
	IF Confirm["Separate files for left and right pages? "] THEN
	    BEGIN
	    ProduceLedger[d, left, ".left"];
	    ProduceLedger[d, right, ".right"];
	    END
	ELSE ProduceLedger[d, both, ".press"];
	CloseInFile[];
	BEGIN
	col: Column;
	ms: STRING ← [20];
	first: BOOLEAN ← TRUE;
	OpenOutput["YTDTotals",".ts"];
	FOR col IN Column DO
	  IF ytdTotal[col] # 0 THEN
	    BEGIN
	    PutString[IF first THEN "TOTALS "L ELSE " / "L];
	    first ← FALSE;
	    MoneyToString[ms, ytdTotal[col]];
	    PutString[ms];
	    PutChar[IODefs.SP];
	    PutString[categoryKey[col]];
	    END;
	  ENDLOOP;
	PutCR[];
	CloseOutput[];
	END;
	END;
      'q, 'Q => BEGIN WriteString["Quit"]; IF Confirm[" [Confirm]"] THEN GO TO fini END;
      ENDCASE => WriteLine["?"];
    ENDLOOP;

  EXITS
    fini => NULL;
  END;
  END.