-- LedgerOutput.mesa  Edited by Sweet  March 27, 1981  12:07 PM

DIRECTORY
  InlineDefs: FROM "inlinedefs",
  IODefs: FROM "iodefs",
  LedgerDefs: FROM "ledgerdefs",
  OutputDefs: FROM "outputdefs",
  PressDefs,
  StringDefs: FROM "stringdefs",
  SystemDefs: FROM "systemdefs";

LedgerOutput: PROGRAM
  IMPORTS InlineDefs, IODefs, LedgerDefs, OutputDefs, PressDefs, StringDefs, SystemDefs
  EXPORTS LedgerDefs =
  BEGIN OPEN PressDefs, LedgerDefs;

  pageGroup: PUBLIC PageGroup ← both;
  leftShift: Mica ← 380;
  rightShift: Mica ← 610;
  bravoLedger: PUBLIC BOOLEAN ← FALSE;

  ARJOut: PUBLIC PROCEDURE [s: STRING, l: CARDINAL] =
    BEGIN OPEN OutputDefs;
    THROUGH [s.length..l) DO
      PutChar[IODefs.SP];
      ENDLOOP;
    PutString[s];
    END;

  ALJOut: PUBLIC PROCEDURE [s: STRING, l: CARDINAL] =
    BEGIN OPEN OutputDefs;
    PutString[s];
    THROUGH [s.length..l) DO
      PutChar[IODefs.SP];
      ENDLOOP;
    END;

  ADateOut: PUBLIC PROCEDURE [date: SmallDate, checkNum: CARDINAL] =
    BEGIN OPEN OutputDefs;
    PutDecimal[date.month];
    PutChar['/];
    PutDecimal[date.day];
    PutChar['/];
    PutDecimal[date.year];
    PutChar[IODefs.TAB];
    IF checkNum = 0 THEN PutChar['-] ELSE PutDecimal[checkNum];
    END;

  LedgerOut: PUBLIC PROCEDURE [p: POINTER, len: CARDINAL] =
    BEGIN
    ENABLE BadData => 
      BEGIN OPEN IODefs;
      lip: POINTER TO SortItem = p;
      WriteString["    Aborting item "];
      WriteDecimal[lip.date.month]; WriteChar['/];
      WriteDecimal[lip.date.day]; WriteChar['/];
      WriteDecimal[lip.date.year]; WriteChar[SP];
      WriteDecimal[lip.check]; WriteChar[CR];
      GO TO gunIt;
      END;
    sip: POINTER TO SortItem = p;
    adesc: StringDefs.SubStringDescriptor ← [base: @sip.string, offset: 0, length: 1];
    ss: StringDefs.SubString = @adesc;

    NoteTotal: PROCEDURE [payee: STRING, val: Money] =
      BEGIN
      NewItem[sip.date, sip.check, payee, val];
      END;

    IF ~bravoLedger THEN 
      SetCurrentFont[Hv8, medium, regular]; -- to compute nw in Place

    BreakApart[ss, NoteTotal, Place];

    EXITS gunIt => NULL;
    END;

  BreakApart: PUBLIC PROCEDURE [
    ss: StringDefs.SubString,
    total: PROCEDURE [STRING, Money],
    detail: PROCEDURE [Column, Money, STRING, BOOLEAN]] =
    BEGIN
    payee, note: STRING;
    col: Column;
    val: Money;
    ded: BOOLEAN;
    princ: BOOLEAN ← TRUE;
    pStuff: RECORD [val: Money, ded: BOOLEAN, col: Column, note: STRING];

    payee ← GetUpTo[ss, IODefs.TAB];
    [val, ded] ← GetMoney[ss];
    total[payee, val];
    DO
      [col, note] ← GetCategory[ss];
      IF totalInfo[col].isTotal THEN
	WarnSS["Distribution to a total column", ss];
      IF princ THEN
	BEGIN
	pStuff ← [val, ded, col, note];
	princ ← FALSE;
	END
      ELSE
	BEGIN
	detail[col, val, note, ded];
	pStuff.val ← pStuff.val - val;
	END;
      SkipBlanks[ss];
      IF Exhausted[ss] THEN EXIT;
      IF CurrentChar[ss] # '/ THEN WarnSS["Expected /",ss];
      Bump[ss];
      [val, ded] ← GetMoney[ss];
      ENDLOOP;
    detail[pStuff.col, pStuff.val, pStuff.note, pStuff.ded];
    END;

  row: ARRAY [1..LAST[Row]] OF ARRAY Column OF ColumnEntry ←
	ALL[ALL[[free[]]]];
  head: ARRAY [1..LAST[Row]] OF RowHead;
  monthTotal: ARRAY Column OF Money ← ALL[0];
  ytdTotal: PUBLIC ARRAY Column OF Money ← ALL[0];
  negMonthTotal, posMonthTotal: Money ← 0;
  negYtdTotal, posYtdTotal: Money ← 0;
  

  firstCurrentRow: Row ← 0;
  maxCurrentRow: Row ← 0;

  Place: PUBLIC PROCEDURE [col: Column, amt: Money, note: STRING,
	ded: BOOLEAN] =
    BEGIN
    nw: Mica ← IF note = NIL THEN 0 
	       ELSE IF bravoLedger THEN 1 ELSE GetWidthOfString[note];
    nc: CARDINAL = HowMany[of: CategoryColWidth, in: nw];
    r: Row;
    noteCol, c: Column;
    flush: Side;
    roomLeft: BOOLEAN = col IN [nc..BreakColumn) OR col >= BreakColumn+nc;
    roomRight: BOOLEAN = (col+nc) < BreakColumn OR
	(col >= BreakColumn AND col+nc <= LAST[Column]);
  -- find room for amt and note
    FOR r ← firstCurrentRow, r+1 DO
      IF r > LastCheckRow THEN
	BEGIN
	rr: Row;
	FinishPage[FALSE]; -- sets row[1..firstCurrentRow) to ALL free
	FOR rr IN [firstCurrentRow..LAST[Row]] DO
	  row[1+rr-firstCurrentRow] ← row[rr];
	  row[rr] ← ALL[[free[]]];
	  ENDLOOP;
	r ← r - firstCurrentRow + 1;
	head[1] ← head[firstCurrentRow];
	maxCurrentRow ← maxCurrentRow + 1 - firstCurrentRow;
	firstCurrentRow ← 1;
	END;
      IF row[r][col].tag # free THEN LOOP;
      IF nw = 0 THEN EXIT;
      IF roomRight THEN FOR c IN (col..col+nc] DO
	IF row[r][c].tag # free THEN EXIT;
	REPEAT
	  FINISHED => BEGIN noteCol ← col+1; flush ← left; GO TO found; END;
	ENDLOOP;
      IF roomLeft THEN FOR c IN [col-nc..col) DO
	IF row[r][c].tag # free THEN EXIT;
	REPEAT
	  FINISHED => BEGIN noteCol ← col-nc; flush ← right; GO TO found; END;
	ENDLOOP;
      REPEAT
	found => NULL;
      ENDLOOP;
    -- amount goes at r,col
    -- note goes at r,noteCol
    maxCurrentRow ← MAX[maxCurrentRow, r];
    row[r][col] ← [money[ded, amt]];
    IF nw # 0 THEN
      BEGIN
      row[r][noteCol] ← [note[note, flush, nc]];
      FOR c IN (noteCol..noteCol+nc) DO row[r][c] ← [taken[]]; ENDLOOP;
      END;
    END;

  lastItemDate: SmallDate ← NullDate;

  NewItem: PUBLIC PROCEDURE [date: SmallDate, check: CARDINAL, payee: STRING, amt: Money] =
    BEGIN
    r: Row;
    IF lastItemDate = NullDate THEN IODefs.WriteString["Writing..."];
    FOR r IN (firstCurrentRow..maxCurrentRow] DO
      head[r] ← [taken[]];
      ENDLOOP;
    firstCurrentRow ← maxCurrentRow+1;
    IF date = NullDate THEN
      BEGIN
      FinishPage[TRUE];
      firstCurrentRow ← maxCurrentRow ← 0;
      lastItemDate ← date;
      RETURN
      END;
    IF lastItemDate # NullDate AND lastItemDate.month # date.month THEN
      BEGIN FinishPage[TRUE]; firstCurrentRow ← 1; END;
    IF firstCurrentRow > LAST[Row] THEN
      BEGIN FinishPage[FALSE]; firstCurrentRow ← 1 END;
    maxCurrentRow ← firstCurrentRow;
    head[firstCurrentRow] ← [first[
      checkNum: check,
      date: date,
      payee: payee,
      amount: amt]];
    lastItemDate ← date;
    END;

  MoneyToString: PUBLIC PROCEDURE [s: STRING, v: Money] =
    BEGIN OPEN StringDefs;
    j: CARDINAL;
    neg: BOOLEAN ← v<0;
    s.length ← 0;
    IF v = 0 THEN 
      BEGIN 
      AppendChar[s,IF bravoLedger THEN '- ELSE IODefs.ControlS]; 
      RETURN 
      END;
    IF neg THEN BEGIN v ← -v; AppendChar[s, '<]; END;
    AppendLongDecimal[s, v/100];
    AppendChar[s, '.];
    j ← InlineDefs.LowHalf[v MOD 100];
    AppendChar[s, '0 + j/10];
    AppendChar[s, '0 + j MOD 10];
    IF neg THEN AppendChar[s, '>];
    END;

  FinishPage: PUBLIC PROCEDURE [totals: BOOLEAN] =
    BEGIN OPEN StringDefs;
    str: STRING ← [50];
    r: Row;
    rY: Mica;
    c: Column;

    DateAndNumber: PROCEDURE [date: SmallDate, check: CARDINAL] =
      BEGIN
      SetCurrentFont[Hv10, medium, regular];
      str.length ← 0;
      AppendDecimal[str, date.day];
      RJString[x: DateColPos,
        y: rY,
        s: str,
        width: DateColWidth];
      str.length ← 0;
      IF check = 0 THEN AppendChar[str, IODefs.ControlS]
      ELSE AppendDecimal[str, check];
      RJString[x: CheckColPos,
        y: rY,
        s: str,
        width: CheckColWidth];
      END;

    OutCat: PROCEDURE [cl: Column] RETURNS [width: Mica] =
      BEGIN
      WITH row[r][cl] SELECT FROM
        note =>
	  BEGIN
	  SetCurrentFont[Hv8, medium, regular];
	  IF flush = right THEN
	    BEGIN
	    RJString[x: CategoryX[cl],
	      y: rY,
	      s: s,
	      width: (columns+1)*CategoryColWidth - 3*Dot -
		OutCat[cl+columns]];
	    END
	  ELSE
	    LJString[x: CategoryX[cl],
	      y: rY,
	      s: s];
	  width ← 0;
	  SystemDefs.FreeHeapString[s];
	  END;
	money =>
	  BEGIN
	  av: Money = IF categoryNeg[cl] THEN -v ELSE v;
	  SetCurrentFont[Hv8, medium,
	          (IF deductable THEN italic ELSE regular)];
	  MoneyToString[str, av];
	  width ← GetWidthOfString[str];
	  RJString[x: CategoryX[cl],
	    y: rY,
	    s: str,
	    width: CategoryColWidth];
	  monthTotal[cl] ← monthTotal[cl] + v;
	  END;
	ENDCASE;
      row[r][cl] ← [free[]];
      END;

    FreeCat: PROCEDURE [cl: Column] =
      BEGIN
      WITH row[r][cl] SELECT FROM
        note => SystemDefs.FreeHeapString[s];
	money => monthTotal[cl] ← monthTotal[cl] + v;
	ENDCASE;
      row[r][cl] ← [free[]];
      END;

    IF bravoLedger THEN
      BEGIN
      BravoFinishPage[totals];
      RETURN;
      END;

    IF totals THEN
      BEGIN
      IF firstCurrentRow +(IF budgetGiven THEN TotalRows ELSE 2) > LAST[Row]+1 THEN
	BEGIN
	FinishPage[FALSE];
	firstCurrentRow ← maxCurrentRow ← 1;
	END;
      LastCheckRow ← LAST[Row]-(IF budgetGiven THEN TotalRows ELSE 2)
      END;

    BEGIN -- to set up noLeft label
    IF pageGroup = right THEN
      BEGIN
      FOR r IN [1..firstCurrentRow) DO
        FOR c IN [0..BreakColumn) DO FreeCat[c] ENDLOOP;
        ENDLOOP;
      IF totals THEN FOR c IN [0..BreakColumn) DO
        FakeTotal[c];
        ENDLOOP;
      GO TO noLeft;
      END;
    bindingMargin ← -leftShift;
    CreateForm[1, lastItemDate, totals];
    FOR r IN [1..firstCurrentRow) DO
      rY ← RowY[r];
      WITH head[r] SELECT FROM
	first =>
	  BEGIN
	  DateAndNumber[date, checkNum];
	  LJString[x: PayeeColPos,
	    y: rY,
	    s: payee];
	  MoneyToString[str, ABS[amount]];
	  IF amount < 0 THEN
	    BEGIN
	    negMonthTotal ← negMonthTotal - amount;
	    RJString[x: IncomeColPos,
	      y: rY,
	      s: str,
	      width: AmountColWidth];
	    END
	  ELSE
	    BEGIN
	    posMonthTotal ← posMonthTotal + amount;
	    RJString[x: ExpenseColPos,
	      y: rY,
	      s: str,
	      width: AmountColWidth];
	    END;
	  SystemDefs.FreeHeapString[payee];
	  END;
	ENDCASE;
      FOR c IN [0..BreakColumn) DO [] ← OutCat[c] ENDLOOP;
      ENDLOOP;
    IF totals THEN
      BEGIN
      SetCurrentFont[Hv10, medium, regular];
      TotalOut[x: IncomeColPos, width: AmountColWidth, i: 1,
	val: negMonthTotal];
      TotalOut[x: ExpenseColPos, width: AmountColWidth, i: 1,
	val: posMonthTotal];
      TotalOut[x: NetColPos, width: NetAmountColWidth, i: 1,
	val: negMonthTotal - posMonthTotal];
      TotalOut[x: IncomeColPos, width: AmountColWidth,
	i: IF budgetGiven THEN 4 ELSE 2,
	val: negYtdTotal ← negYtdTotal + negMonthTotal];
      TotalOut[x: ExpenseColPos, width: AmountColWidth,
	i: IF budgetGiven THEN 4 ELSE 2,
	val: posYtdTotal ← posYtdTotal + posMonthTotal];
      TotalOut[x: NetColPos, width: NetAmountColWidth,
	i: IF budgetGiven THEN 4 ELSE 2,
	val: negYtdTotal - posYtdTotal];
      negMonthTotal ← posMonthTotal ← 0;
      SetCurrentFont[Hv8, medium, regular];
      FOR c IN [0..BreakColumn) DO
        DoTotal[c];
        ENDLOOP;
      END;
    PressDefs.WritePage[pfd];
    EXITS
      noLeft => NULL;
    END;

    BEGIN -- to set up noRight label
    IF pageGroup = left THEN
      BEGIN
      FOR r IN [1..firstCurrentRow) DO
        FOR c IN [BreakColumn..LAST[Column]] DO FreeCat[c] ENDLOOP;
        ENDLOOP;
      IF totals THEN FOR c IN [BreakColumn..LAST[Column]] DO
        FakeTotal[c];
        ENDLOOP;
      GO TO noRight;
      END;
    bindingMargin ← rightShift;
    CreateForm[2, lastItemDate, totals];
    FOR r IN [1..firstCurrentRow) DO
      rY ← RowY[r];
      FOR c IN [BreakColumn..LAST[Column]] DO [] ← OutCat[c] ENDLOOP;
      ENDLOOP;
    IF totals THEN
      BEGIN
      SetCurrentFont[Hv8, medium, regular];
      FOR c IN [BreakColumn..LAST[Column]] DO
        DoTotal[c];
        ENDLOOP;
      END;
    PressDefs.WritePage[pfd];
    EXITS
      noRight => NULL;
    END;
    IF totals THEN monthTotal ← ALL[0];
    LastCheckRow ← LAST[Row];
    END;

-- l12256d2998(0,4608)(1,5664)(2,10272)(3,12256)
--

  ATotCols: CARDINAL = 12;
  ADetailCols: CARDINAL = 10;
  BravoFinishPage: PROCEDURE [totals: BOOLEAN] =
    BEGIN OPEN OutputDefs;
    str: STRING ← [50];
    r: Row;
    c: Column;
    itemAmount: Money;
    firstCat: BOOLEAN ← TRUE;
    activeItem: BOOLEAN ← FALSE;

    OutCat: PROCEDURE [cl: Column] =
      BEGIN
      WITH row[r][cl] SELECT FROM
        note =>
	  BEGIN
	  IF flush = right THEN OutCat[cl+columns];
	  PutString[" ("];
	  PutString[s];
	  PutChar[')];
	  SystemDefs.FreeHeapString[s];
	  END;
	money =>
	  BEGIN
	  av: Money = IF categoryNeg[cl] THEN -v ELSE v;
	  IF v # itemAmount THEN
	    BEGIN
	    IF firstCat THEN
	      BEGIN PutChar[IODefs.TAB]; firstCat ← FALSE; END
	    ELSE PutChar[IODefs.CR];
	    MoneyToString[str, av];
	    ARJOut[str, ADetailCols];
	    END;
	  PutChar[IODefs.SP]; PutString[categoryKey[cl]];
	  monthTotal[cl] ← monthTotal[cl] + v;
	  END;
	ENDCASE;
      row[r][cl] ← [free[]];
      END;
    
    BravoFinishLine: PROCEDURE =
      BEGIN
      IF ~activeItem THEN RETURN;
      PutChar[IODefs.ControlZ];
      PutString["l12256d2998"L];
      IF firstOut THEN
        BEGIN
        PutString["(0,4608)(1,5664)(2,10272)(3,12256)"L];
        firstOut ← FALSE;
        END;
      activeItem ← FALSE;
      PutCR[];
      END;

    FOR r IN [1..firstCurrentRow) DO
      WITH head[r] SELECT FROM
	first =>
	  BEGIN
	  BravoFinishLine[];
	  ADateOut[date, checkNum];
	  PutChar[IODefs.TAB];
	  PutString[payee];
	  PutChar[IODefs.TAB];
	  itemAmount ← amount;
	  firstCat ← TRUE;
	  MoneyToString[str, amount];
	  ARJOut[str, ADetailCols];
	  IF amount < 0 THEN
	    negMonthTotal ← negMonthTotal - amount
	  ELSE posMonthTotal ← posMonthTotal + amount;
	  SystemDefs.FreeHeapString[payee];
	  activeItem ← TRUE;
	  END;
	ENDCASE;
      FOR c IN Column DO OutCat[c]; ENDLOOP;
      ENDLOOP;
    BravoFinishLine[];
    IF totals THEN
      BEGIN
      PutCR[]; PutCR[]; PutString["TOTALS"];
      PutCR[];
      ALJOut[MonthName[lastItemDate.month], 10];
      PutString[" deposits:    "];
      MoneyToString[str, negMonthTotal];
      ARJOut[str, ATotCols];
      PutCR[];
      ALJOut[MonthName[lastItemDate.month], 10];
      PutString[" withdrawals: "];
      MoneyToString[str, posMonthTotal];
      ARJOut[str, ATotCols];
      PutCR[];
      ALJOut[MonthName[lastItemDate.month], 10];
      PutString[" net:         "];
      MoneyToString[str, negMonthTotal - posMonthTotal];
      ARJOut[str, ATotCols];
      PutCR[];
      ALJOut["YTD", 10];
      PutString[" deposits:    "];
      MoneyToString[str, negYtdTotal ← negYtdTotal + negMonthTotal];
      ARJOut[str, ATotCols];
      PutCR[];
      ALJOut["YTD", 10];
      PutString[" withdrawals: "];
      MoneyToString[str, posYtdTotal ← posYtdTotal + posMonthTotal];
      ARJOut[str, ATotCols];
      PutCR[];
      ALJOut["YTD", 10];
      PutString[" net:         "];
      MoneyToString[str, negYtdTotal - posYtdTotal];
      ARJOut[str, ATotCols];
      PutCR[]; 
      PutChar[IODefs.ControlZ]; PutString["l2998"]; PutCR[];
      negMonthTotal ← posMonthTotal ← 0;
      PutString["Category"];
      ARJOut[MonthName[lastItemDate.month], ATotCols];
      IF budgetGiven THEN
	BEGIN
	ARJOut["budget", ATotCols];
	ARJOut["net", ATotCols];
	END;
      ARJOut["YTD", ATotCols];
      IF budgetGiven THEN
	BEGIN
	ARJOut["budget", ATotCols];
	ARJOut["net", ATotCols];
	END;
      PutCR[];
      FOR c IN Column DO
	IF categoryKey[c] # NIL THEN
	  BEGIN
	  ALJOut[categoryKey[c], 8];
	  DoTotal[c]; PutCR[];
	  END;
        ENDLOOP;
      monthTotal ← ALL[0];
      PutChar[IODefs.FF]; 
      PutChar[IODefs.ControlZ]; PutString["l2998"]; PutCR[];
      END;
    END;

  DoTotal: PROCEDURE [c: Column] =
    BEGIN
    ti: TotalRec = totalInfo[c];
    TotalOut[x: CategoryX[c], width: CategoryColWidth, i: 1,
      val: monthTotal[c], invertSign: categoryNeg[c]];
    IF ti.hasTotal THEN
      monthTotal[ti.myTotal] ←
        monthTotal[ti.myTotal] + monthTotal[c];
    IF budgetGiven THEN
      BEGIN
      mb: Money;
      mb ← budget[c][lastItemDate.month];
      TotalOut[x: CategoryX[c], width: CategoryColWidth, i: 2,
        val: mb, invertSign: categoryNeg[c]];
      TotalOut[x: CategoryX[c], width: CategoryColWidth, i: 3,
        val: mb - monthTotal[c]];
      END;
    TotalOut[x: CategoryX[c], width: CategoryColWidth,
      i: IF budgetGiven THEN 4 ELSE 2,
      val: ytdTotal[c] ← ytdTotal[c] + monthTotal[c],
      invertSign: categoryNeg[c]];
    IF budgetGiven THEN
      BEGIN
      ytdb: Money;
      m: CARDINAL;
      ytdb ← 0;
      FOR m IN [1..lastItemDate.month] DO 
        ytdb ← ytdb + budget[c][m];
        ENDLOOP;
      TotalOut[x: CategoryX[c], width: CategoryColWidth, i: 5,
        val: ytdb, invertSign: categoryNeg[c]];
      TotalOut[x: CategoryX[c], width: CategoryColWidth, i: 6,
        val: ytdb - ytdTotal[c]];
      END;
    END;

  FakeTotal: PROCEDURE [c: Column] =
    BEGIN
    ti: TotalRec = totalInfo[c];
    IF ti.hasTotal THEN
      monthTotal[ti.myTotal] ←
        monthTotal[ti.myTotal] + monthTotal[c];
    ytdTotal[c] ← ytdTotal[c] + monthTotal[c];
    END;

  TotalOut: PROCEDURE [i: [1..TotalRows], x, width: Mica, val: Money, invertSign: BOOLEAN ← FALSE] =
    BEGIN
    str: STRING ← [20];
    MoneyToString[str, IF invertSign THEN -val ELSE val];
    IF bravoLedger THEN ARJOut[str, ATotCols]
    ELSE RJString[x: x,
        y: RowY[LastCheckRow+i],
        s: str,
        width: width];
    END;

  END.