USE Tree;

CONST
  SkipIdents = {'QUERY', 'MAIN', 'QIDERR', 'ITEMERR',
                'SQLCA', 'SQLCODE', 'NOHANDLE', 'READABLE',
                'EIBTIME', 'EIBTRMID', 'EIBDATE', 'QUOTE', 'EIBAID',
                'LENGTH', 'LENGERR', 'EIBCALEN'};
VAR                
  UnknownIdents := {};                

PROCEDURE EnclosingBlock(x);
  BEGIN
  RESULT := x.Father;
  WHILE (RESULT <> VOID) AND NOT (RESULT IS StatementBlock) DO
    RESULT := RESULT.Father;
    END;
  END EnclosingBlock;  

PROCEDURE FindDataFields (a);
BEGIN
IF NOT (a IN SkipIdents) THEN
  RESULT := ROOT.DataDivision.WorkingStorageSection.Data.FindField (a);
  IF #RESULT = 0 THEN
    IF ROOT.DataDivision.LinkageSection <> VOID THEN
      RESULT := ROOT.DataDivision.LinkageSection.Data.FindField (a);
          END;
    END;
  END;
END FindDataFields;

PROCEDURE MarkCicsStatement (s);
VAR
  p;
BEGIN
p := s;
WHILE (p <> VOID) AND NOT (p IS ExecBlock) DO
  p := p.Father;
  END;
IF p <> VOID THEN
  PatchNt (p,  '<A NAME=ID' & s.Nr & '><FONT COLOR="RED">',
                '</FONT></A>');
  END;  
END MarkCicsStatement;

PROCEDURE MarkBlockUsage (Bl, Id);
BEGIN
ASSERT Bl IS StatementBlock;
ASSERT Id IS Ident;
IF Id <> Bl.LabelIdent THEN
  IF NOT (Bl CAN Usages) THEN
    Bl.Usages := {};
    END;
  Bl.Usages := Bl.Usages & {Id};  
  END;
END MarkBlockUsage;  

PROCEDURE MarkDataFieldUsage (Field, Id);
BEGIN
ASSERT Field IS DataField;
ASSERT Id IS Ident;
IF Id <> Field.Id THEN
  IF NOT (Field CAN Usages) THEN
    Field.Usages := {};
    END;
  ASSERT Field CAN Usages;  
  Field.Usages := Field.Usages & {Id};  
  END;
END MarkDataFieldUsage;  

PROCEDURE MarkLabel (Id);
VAR
  Bl;
BEGIN
Bl := ROOT.ProcedureDivision.FindBlock (Id.Image);
IF Bl = VOID THEN
  OUT.WriteLn ("Label not found: ", Id.Image);
 ELSE
  MarkBlockUsage (Bl, Id);
  END;
END MarkLabel;  

PROCEDURE MarkDataIdent (Id);
VAR
  a,
  Fields;
BEGIN
a := Id.Image;
Id.Ref := VOID;
IF NOT (a IN SkipIdents) THEN
  Fields := FindDataFields (a);
  IF #Fields = 0 THEN
    IF NOT (a IN UnknownIdents) THEN
      UnknownIdents := UnknownIdents & { a };
      OUT.WriteLn ("Variable not found: ", a);
      END;
   ELSIF #Fields > 1 THEN
    OUT.WriteLn ("Warning: more than a single matching decl: ", a);
   ELSE
    MarkDataFieldUsage (Fields[1], Id);
    END;
  END;
END MarkDataIdent;  

PROCEDURE OutputList (FName, File, Title, l);
  BEGIN
  IF #l > 0 THEN
    File.WriteLn ("<HR><H3>", Title, "</H3><UL>");
    FOR e IN l DO
      File.WriteLn ('<LI><A HREF="' , FName , '#ID', e.Nr, '">',
                  'Line ', e.LineNr, '</A></LI>');
      END;
    File.WriteLn ("</UL>");
    END;
  END OutputList;

PROCEDURE ColorComments;
  VAR
    a;
  BEGIN
  FOR i := 1 TO PATCH.LineCount DO
    a := PATCH.GetLines (i);
    IF (a <> VOID) AND (#a > 0) AND (a[1] = '*') THEN
      ColorLine (i, 'GREEN');
      END;
    END;
  END ColorComments;  

ON CicsStatement DO
  MarkCicsStatement (X);
  END;    

ON LabelIdent DO
  MarkLabel (X);
  END;  
  
ON DataIdent DO
  MarkDataIdent (X);
  END;  

PROCEDURE PatchNt (Nt,  BeforeStr, AfterStr);
  BEGIN
  PATCH.Insert (Nt.LineNr, Nt.ColNr, BeforeStr);
  PATCH.Insert (Nt.EndLineNr, Nt.EndColNr+1, AfterStr);
  END PatchNt;

PROCEDURE ColorNt (Nt, Col);
  BEGIN
  PatchNt (Nt, '<FONT COLOR="' & Col & '">', '</FONT>');
  END ColorNt;  

PROCEDURE MarkLine (LineNr, BeforeString, AfterString);
  VAR
    a;
  BEGIN
  a := PATCH.GetLines (LineNr);
  PATCH.Insert(LineNr, 1, BeforeString);
  PATCH.Insert(LineNr, #a+1, AfterString);
  END MarkLine;

PROCEDURE ColorLine (LineNr, Col);
  BEGIN
  MarkLine (LineNr, '<FONT COLOR="' & Col & '">', '</FONT>');
  END ColorLine;

PROCEDURE SaveHtml (FName, 
                    LIndexFName, 
                    DIndexFName,
                    TViewFName,
                    SpecList);
  VAR
    a, b, Fields;
  BEGIN
  FILE1.Redirect (LIndexFName);
  FILE2.Redirect (DIndexFName);
  PATCH.InsertLines(1, {"<!RainCode Generated document>",
                        "<HTML><HEAD><TITLE>" & ROOT.SourceName &"</TITLE>",
                        "</HEAD><BODY>",
                        '<H2><A HREF="' & LIndexFName & '">Goto inventory and label index...</A></H2><BR>',
                        '<H2><A HREF="' & DIndexFName & '">Goto data index...</A></H2><BR>',
                        '<H2><A HREF="' & TViewFName & '">Goto tree view...</A></H2><BR>',
                        '<H2><A HREF=#PROCDIVI>Procedure division...</A></H2><HR>',
                        "<PRE>"});
  MarkLine (ROOT.ProcedureDivision.LineNr, "<A NAME=PROCDIVI>", "</A>");
  FOR Bl IN ROOT.ProcedureDivision.Blocks DO
    Bl.HRef := '"' & FName & "#ID" & Bl.Nr & '"';
    END;
  
FOR Bl IN ROOT.ProcedureDivision.Blocks | NOT (X CAN Usages) DO
  ColorNt (Bl.LabelIdent, "GRAY");
  END;

  FOR Bl IN ROOT.ProcedureDivision.Blocks | X CAN Usages DO
    Bl.Usages := LIST.Sort(Bl.Usages);
    
ASSERT FOR_ALL IN Bl.Usages :- X <> Bl.LabelIdent;
FOR Id IN Bl.Usages DO
  PatchNt (Id, "<A " 
                    & " NAME=ID" & Id.Nr 
                    & ' HREF="#ID' & Bl.Nr & '">', "</A>");
  END;
a := Bl.LabelIdent;
IF #Bl.Usages = 1 THEN
  PatchNt (a,
      '<a NAME="ID' & Bl.Nr & '"' &
      ' HREF=#ID' & (Bl.Usages[1]).Nr & '>',
      '</a>');
 ELSE      
  PatchNt(a,
         '<a NAME="ID' & Bl.Nr & '"' &
          ' HREF="' & LIndexFName & "#ID" & Bl.Nr & '">',
          '</a>');
  END;      

    END;
  
FILE1.WriteLn ("<!RainCode Generated document>",
              "<HTML><HEAD><TITLE> Index of " & ROOT.SourceName & " labels</TITLE>",
                        "</HEAD><BODY>");
FOR IN SpecList DO
  OutputList(FName, FILE1, X[1], X[2]);
  END;
FOR Bl IN ROOT.ProcedureDivision.Blocks | (X CAN Usages) AND
                                          (#X.Usages > 1) DO
  
FILE1.WriteLn ('<HR>Usages of <A NAME=ID', Bl.Nr, 
                   ' HREF=', Bl.HRef, '>',
                   Bl.Label, "</A><BR><UL>");
FOR Id IN Bl.Usages DO
  FILE1.Write ("<LI>",
                     '<A HREF="', FName, '#ID', Id.Nr, '">',
                     "Line " , Id.LineNr);
  b := EnclosingBlock (Id);
  IF (b <> VOID) AND (b.LabelIdent <> VOID) THEN             
    FILE1.Write (" (", b.LabelIdent.Image, ")");
    END;
  FILE1.WriteLn ("</A></LI>");
  END;               
FILE1.WriteLn ("</UL>");

  END;
FILE1.WriteLn ("</BODY>");
FILE1.Redirect;

FILE2.WriteLn ("<!RainCode Generated document>",
              "<HTML><HEAD><TITLE> Index of " & ROOT.SourceName & " variables</TITLE>",
                        "</HEAD><BODY>");
Fields := ROOT.DataDivision.WorkingStorageSection.Data.Fields | X CAN Usages;                      
IF ROOT.DataDivision.LinkageSection <> VOID THEN
  Fields := Fields & ROOT.DataDivision.LinkageSection.Data.Fields | X CAN Usages;                      
  END;
ASSERT FOR_ALL IN Fields :- (#X.Usages > 0);
FOR Field IN LIST.Sort(Fields) DO
  
a := Field.Id;
PatchNt (a, '<A NAME=D' & a.Nr  &
                ' HREF="' & DIndexFName & '#D' & a.Nr & '">',
                '</A>');
FILE2.WriteLn ("<HR><H3>Usages of <A NAME=D", a.Nr,
                   ' HREF="', FName, '#D', a.Nr, '">', 
                   a.Image, "</A></H3><UL>");
FOR Id IN Field.Usages DO
  ASSERT Id IS DataIdent;
  ASSERT Id <> a;
  PatchNt (Id, '<A HREF=#D' & a.Nr & 
                    ' NAME="D' & Id.Nr & '">', '</A>');
  FILE2.Write ("<LI>",
                     '<A HREF="', FName, '#D', Id.Nr, '">',
                     "Line ", Id.LineNr);
  b := EnclosingBlock (Id);
  IF (b <> VOID) AND (b.LabelIdent <> VOID) THEN
    FILE2.Write (" (", b.LabelIdent.Image, ")");
    END;               
  FILE2.WriteLn("</A></LI>");
  END;
FILE2.WriteLn ("</UL>");  

  END;
FILE2.WriteLn ("</BODY>");                      
FILE2.Redirect;

  OUT.WriteLn (PATCH.PatchCount, " patches applied");    
  PATCH.Save (FName);
  END SaveHtml;

PROCEDURE TERMINATE;
  VAR
    StaticCalls,
    DynamicCalls,
    Targets,
    Radix,
    CicsStats,
    SpecList;
  BEGIN
  
Radix := ROOT.SourceName;

Radix := Radix[1..#Radix-4];

Radix := STR.Words(Radix, '\/');

Radix := Radix[#Radix];

Radix := STR.Words(Radix, '.');
Radix := Radix[1];

  ColorComments;
  
CicsStats := ROOT.SubNodes | X IS CicsStatement;

SpecList := {
               {
                   "CICS SEND MAP statements", 
                    CicsStats | ((X IS CicsSend) AND NOT X.Text)
               },
               {
                   "CICS SEND TEXT statements", 
                    CicsStats | ((X IS CicsSend) AND X.Text)
               },
               {
                   "CICS RETRIEVE statements", 
                    CicsStats | (X IS CicsRetrieve)
               },
               {
                   "CICS RETURN statements", 
                    CicsStats | (X IS CicsReturn)
               },
               {
                   "CICS START statements", 
                    CicsStats | (X IS CicsStart)
               },
               {
                   "CICS ASKTIME statements", 
                    CicsStats | (X IS CicsAsktime)
               },
               {
                   "CICS WRITEQ statements", 
                    CicsStats | (X IS CicsWriteQ)
               }
             };

CicsStats := LIST.Diff(CicsStats, LIST.RecursiveFlat(SpecList));
SpecList := SpecList & {{"Other CICS statements", CicsStats}};

StaticCalls := ROOT.SubNodes | X IS CallStatement;
FOR IN StaticCalls DO
  PATCH.Insert (X.LineNr, X.ColNr, 
                "<A NAME=ID" & X.Nr & ">");
  PATCH.Insert (X.LineNr, X.ColNr+4, 
                "</A>");
  END;
DynamicCalls := StaticCalls | NOT (X.ProcToken IS StringLiteral);
StaticCalls := LIST.Diff(StaticCalls, DynamicCalls);
ASSERT FOR_ALL IN StaticCalls :- X.ProcToken IS StringLiteral;
Targets := StaticCalls || STR.UpperCase (X.ProcToken.Image);
Targets := LIST.Unique (Targets);
SpecList := SpecList & 
              (Targets || 
                {
                  "Calls to " & X, 
                  StaticCalls | [y] y.ProcToken.Image =@ X
                });
SpecList := SpecList & {{"Dynamic calls", DynamicCalls}};                          

SaveHtml (Radix & ".html", 
          "LX" & Radix & ".html",
          "DX" & Radix & ".html",
          "TX" & Radix & ".html",
          SpecList);
Tree.ProduceTreeOutput("TX" & Radix & ".html");

  END TERMINATE;