-- LedgerDetail.mesa  Edited by Sweet, March 30, 1981  10:34 PM

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

LedgerDetail: PROGRAM
  IMPORTS GPsortDefs, InlineDefs, IODefs, LedgerDefs, OutputDefs, StringDefs, SystemDefs
  EXPORTS LedgerDefs =
  BEGIN OPEN LedgerDefs;

  NRJOut: PUBLIC PROCEDURE [s: STRING, l: CARDINAL] =
    BEGIN OPEN OutputDefs;
    THROUGH [s.length..l) DO
      PutChar[IF bravoOut THEN IODefs.ControlY ELSE IODefs.SP];
      ENDLOOP;
    PutString[s];
    END;

  OrderByDetail: PUBLIC PROCEDURE[p1, p2: POINTER] RETURNS [INTEGER] =
    BEGIN
    dip1: POINTER TO DetailItem = p1;
    dip2: POINTER TO DetailItem = p2;
 
    SELECT dip1.category FROM
      >dip2.category => RETURN[1];
      <dip2.category => RETURN[-1];
      ENDCASE;
    SELECT dip1.date.cv FROM
      >dip2.date.cv => RETURN[1];
      <dip2.date.cv => RETURN[-1];
      ENDCASE;
    SELECT dip1.check FROM
      >dip2.check => RETURN[1];
      <dip2.check => RETURN[-1];
      ENDCASE => RETURN [0];
    END;

  dBuffer: POINTER TO SortItem;
  nDetail: CARDINAL ← 0;
  maxDetail: CARDINAL ← 30;
  detail: POINTER TO ARRAY [0..0) OF InternalDetailItem;
  currentDate: SmallDate;
  currentCheck: CARDINAL;
  currentPayee: STRING;

  InternalDetailItem: TYPE = RECORD [
    category: Column,
    amount: Money,
    taxded: BOOLEAN,
    comment: STRING];

  SetPayee: PROCEDURE [payee: STRING, total: Money] =
    BEGIN
    currentPayee ← payee;
    END;

  AddDetail: PROCEDURE [col: Column, amt: Money, note: STRING, ded: BOOLEAN] =
    BEGIN
    IF nDetail = maxDetail THEN
      BEGIN
      newDetail: POINTER TO ARRAY [0..0) OF InternalDetailItem;
      maxDetail ← maxDetail + 20;
      newDetail ← SystemDefs.AllocateHeapNode[maxDetail];
      InlineDefs.COPY[
	from: detail, 
	to: newDetail, 
	nwords: (nDetail-1)*SIZE[InternalDetailItem]];
      SystemDefs.FreeHeapNode[detail];
      detail ← newDetail;
      END;
    IF categoryNeg[col] THEN amt ← - amt;
    detail[nDetail] ← [
      category: col,
      amount: amt,
      taxded: ded,
      comment: note];
    nDetail ← nDetail + 1;
    END;

  DetailInput: PUBLIC PROCEDURE [p: POINTER] RETURNS [CARDINAL] =
    BEGIN
    dip: POINTER TO DetailItem = p;

    DO
      ENABLE BadData => 
        BEGIN OPEN IODefs;
	i: CARDINAL;
        WriteString["    Aborting item "];
        WriteDecimal[currentDate.month]; WriteChar['/];
        WriteDecimal[currentDate.day]; WriteChar['/];
        WriteDecimal[currentDate.year]; WriteChar[SP];
        WriteDecimal[currentCheck]; WriteChar[CR];
        FOR i IN [0..nDetail) DO
	  SystemDefs.FreeHeapString[detail[i].comment];
          ENDLOOP;
        nDetail ← 0;
        LOOP
        END;
      IF nDetail = 0 THEN
        BEGIN
        desc: StringDefs.SubStringDescriptor;
        IF currentPayee # NIL THEN SystemDefs.FreeHeapString[currentPayee];
        currentPayee ← NIL;
        IF InputLine[dBuffer] = 0 THEN RETURN[0];
        currentDate ← dBuffer.date;
        currentCheck ← dBuffer.check;
        desc ← [base: @dBuffer.string, offset: 0, length: 1];
        BreakApart[@desc, SetPayee, AddDetail];
        END;
      IF nDetail = 0 THEN RETURN[0];
      nDetail ← nDetail - 1;
      dip↑ ← [date: currentDate, check: currentCheck,
        category: detail[nDetail].category,
        amount: detail[nDetail].amount,
        taxded: detail[nDetail].taxded,
        text: [length: 0, maxlength: (200-SIZE[DetailItem])*2, text: ]];
      StringDefs.AppendString[@dip.text, currentPayee];
      StringDefs.AppendChar[@dip.text, IODefs.TAB];
      IF detail[nDetail].comment # NIL THEN
        BEGIN
        StringDefs.AppendString[@dip.text, detail[nDetail].comment];
        SystemDefs.FreeHeapNode[detail[nDetail].comment];
        END;
      RETURN [SIZE[DetailItem]+(dip.text.length+1)/2];
      ENDLOOP;
    END;

  lastCat: Column ← LAST[Column];
  prevDate: SmallDate ← NullDate;
  monthTotal, yearTotal: Money;
  monthCount, yearCount: CARDINAL;
  taxDedOnly: PUBLIC BOOLEAN ← FALSE;

  DetailOutput: PUBLIC PROCEDURE [p: POINTER, len: CARDINAL] =
    BEGIN OPEN OutputDefs;
    dip: POINTER TO DetailItem = p;
    str: STRING = [30];
    payee, note: STRING;
    desc: StringDefs.SubStringDescriptor ← [
      base: @dip.text,
      offset: 0,
      length: 1];

    DoMonthTotal: PROCEDURE =
      BEGIN
      IF monthCount > 1 THEN
	BEGIN
	mName: STRING = MonthName[prevDate.month];
	PutString[mName]; PutString[" total"];
	PutChar[IODefs.TAB];
        str.length ← 0;
        MoneyToString[str, monthTotal];
	NRJOut[str, 10];
	PutChar[IODefs.ControlZ];
	PutString["e4\bg"]; PutDecimal[mName.length+6];
	PutString["t3 1t0"];
	PutCR[];
	END;
      yearTotal ← yearTotal + monthTotal;
      yearCount ← yearCount + 1;
      monthTotal ← 0; monthCount ← 0;
      END;

    DoYearTotal: PROC =
      BEGIN
      IF yearCount > 1 THEN
	BEGIN
	PutString["Annual total"];
	PutChar[IODefs.TAB];
        str.length ← 0;
        MoneyToString[str, yearTotal];
	NRJOut[str, 10];
	PutChar[IODefs.ControlZ];
	PutString["e6\gb12t3 1t0"];
	PutCR[];
	END;
      yearCount ← 0; yearTotal ← 0;
      END;

    IF p = NIL THEN {DoMonthTotal[]; DoYearTotal[]; RETURN};

    IF firstOut THEN
      BEGIN
      IODefs.WriteString["Writing..."]; firstOut ← FALSE;
      PutChar[IODefs.ControlZ];
      PutString[tabString ← "(0,4608)(1,5664)(2,10272)(3,12384)"];
      PutCR[];
      END;
    IF dip.category # lastCat THEN 
      BEGIN
      DoMonthTotal[];
      DoYearTotal[];
      lastCat ← dip.category;
      BravoNewPage[];
      PrintCategoryName[dip.category];
      PutChar[IODefs.ControlZ]; PutString["c\f5b"L]; PutCR[];
      prevDate ← NullDate;
      END;
    IF dip.date.month # prevDate.month THEN 
      BEGIN DoMonthTotal[]; PutCR[]; END;
    monthTotal ← monthTotal + dip.amount;
    monthCount ← monthCount + 1;
    prevDate ← dip.date;
    ADateOut[dip.date, dip.check]; PutChar[IODefs.TAB];
    payee ← GetUpTo[@desc, IODefs.TAB];  Bump[@desc];
    PutString[payee]; SystemDefs.FreeHeapString[payee];
    PutChar[IODefs.TAB];
    str.length ← 0;
    MoneyToString[str, dip.amount];
    IF dip.taxded THEN StringDefs.AppendChar[str, 'T];
    NRJOut[str, IF dip.taxded THEN 11 ELSE 10];
    note ← GetRestOf[@desc];
    IF note # NIL THEN
      BEGIN
      PutChar[IODefs.TAB];
      PutString[note]; SystemDefs.FreeHeapNode[note];
      END;
    PutChar[IODefs.ControlZ]; PutString["\g"L]; PutChar[IODefs.CR];
    END;

  PrintCategoryName: PUBLIC PROCEDURE [cat: Column] =
    BEGIN OPEN OutputDefs;
    cName: STRING ← categoryName[cat];
    ch: CHARACTER;
    i: CARDINAL;
    FOR i ← 0, i+1 WHILE i < cName.length DO
      ch ← cName[i];
      SELECT ch FROM
        '-  => IF cName[i+1] = '/ THEN i ← i+1
               ELSE PutChar[ch];
        '/ => PutChar[IODefs.SP];
        ENDCASE => PutChar[ch];
      ENDLOOP;
    END;

  GetRestOf: PUBLIC PROCEDURE [ss: StringDefs.SubString]
      RETURNS [rest: STRING] =
    BEGIN
    IF Exhausted[ss] THEN RETURN[NIL];
    rest ← SystemDefs.AllocateHeapString[ss.base.length - ss.offset];
    UNTIL Exhausted[ss] DO
      StringDefs.AppendChar[rest, CurrentChar[ss]];
      Bump[ss];
      ENDLOOP;
    END;

  ProduceDetailReport: PUBLIC PROCEDURE =
    BEGIN
    lastCat ← LAST[Column];
    currentPayee ← NIL;
    prevDate ← NullDate;
    monthCount ← yearCount ← 0;
    monthTotal ← yearTotal ← 0;
    GPsortDefs.Sort[
      get: DetailInput,
      put: DetailOutput,
      compare: OrderByDetail,
      expectedItemSize: 25,
      maxItemSize: 200,
      reservedPages: 110];
    DetailOutput[NIL,0];
    END;

  dBuffer ← SystemDefs.AllocateResidentPages[2];
  detail ← SystemDefs.AllocateHeapNode[maxDetail * SIZE [InternalDetailItem]];
  END.