# This script does some consistency checks of the manual.
#
# Run it in the 'doc/ref' directory
#
#

Read( "makedocreldata.g" );

doc := ComposedXMLString(
    GAPInfo.ManualDataRef.pathtodoc, 
    GAPInfo.ManualDataRef.main, 
    GAPInfo.ManualDataRef.files,
    true);;

# Detect which ManSection should be used to document obj. Returns one of
# "Func", "Oper", "Meth", "Filt", "Prop", "Attr", "Var", "Fam", "InfoClass"
#
# See PRINT_OPERATION where some of the code below is borrowed
#
ManSectionType:=function( op )
local   class,  flags,  types,  catok,  repok,  propok,  seenprop,  t;
if IsInfoClass( op ) then
    return "InfoClass";
elif IsFamily( op ) then
    return "Fam";
elif not IsFunction( op ) then
    return "Var";  
elif IsFunction( op ) and not IsOperation( op ) then
    return "Func";
elif IsOperation( op ) then
    class := "Oper";
    if IS_IDENTICAL_OBJ(op,IS_OBJECT) then
        class := "Filt";
    elif IS_CONSTRUCTOR(op) then
        class := "Constructor"; # seem to never get one
    elif IsFilter(op) then
        class := "Filt";
        flags := TRUES_FLAGS(FLAGS_FILTER(op));
        types := [];
        for t in flags do
            AddSet(types, INFO_FILTERS[t]);
        od;
        catok := true;
        repok := true;
        propok := true;
        seenprop := false;
        for t in types do
            if not t in FNUM_REPS then
                repok := false;
            fi;
            if not t in FNUM_CATS then
                catok := false;
            fi;
            if not t in FNUM_PROS and not t in FNUM_TPRS then
                propok := false;
            fi;
            if t in FNUM_PROS then
                seenprop := true;
            fi;
        od;
        if seenprop and propok then
            class := "Prop";
        elif catok then
            class := "Filt"; # in PRINT_OPERATION - "Category";
        elif repok then
            class := "Filt"; # in PRINT_OPERATION - "Representation";
        fi;
    elif Tester(op) <> false  then
        # op is an attribute
        class := "Attr";
    fi;
    return class;
else
  return fail;
fi;    
end;

#
# Checks whether ManSections are using the right kind of elements
#
CheckManSectionTypes := function( doc, verbose... )
local types, type, r, s, t, x, y, yint, elt, stats, name, pos, obj, man,
      matches, matches2, match, errcount, referrcount, warncount, display_warnings;
if Length( verbose ) = 0 then
  display_warnings := false;
else
  display_warnings := verbose[1];
fi;
types:=[ "Func", "Oper", "Meth", "Filt", "Prop", "Attr", "Var", "Fam", "InfoClass" ];
r := ParseTreeXMLString(doc[1]);;
CheckAndCleanGapDocTree(r);
x := XMLElements( r, types );;
errcount:=0;
Print( "****************************************************************\n" );
Print( "*** Checking types in ManSections \n" );
Print( "****************************************************************\n" );
for elt in x do
  name := elt.attributes.Name;
  if not name in [ "IsBound", "Unbind", "Info", "Assert", "TryNextMethod", "QUIT", "-infinity" ] then
    if EvalString( Concatenation("IsBound(", name, ")") ) <> true then
      pos:=OriginalPositionDocument(doc[2],elt.start);
      Print( pos[1], ":", pos[2], " : ", name, " is unbound \n" );
      errcount:=errcount+1;
    else
      obj := EvalString( name );
      man := ManSectionType( obj );
      # we allow to use "Meth" for "Oper", "Attr", "Prop" but issue a warning
      # if there is no at least one "Oper", "Attr" or "Prop" for any "Meth"
      if ( man <> elt.name ) and not ( man in ["Attr","Prop","Oper"] and elt.name="Meth") then
        pos:=OriginalPositionDocument(doc[2],elt.start);
        Print( pos[1], ":", pos[2], " : ", name, " uses ", elt.name, " instead of ", man, "\n");
        errcount:=errcount+1;
      fi;
      if elt.name="Meth" then
        if Number( x, t -> t.attributes.Name=name and t.name in ["Attr","Prop","Oper"] ) = 0 then
          pos:=OriginalPositionDocument(doc[2],elt.start);
          Print( pos[1], ":", pos[2], " : ", name, " uses Meth with no matching Oper/Attr/Prop\n" );
          errcount:=errcount+1;
        fi;
      fi;
    fi;
  fi;
od;

Print( "****************************************************************\n" );
Print( "*** Checking types in cross-references \n" );
Print( "****************************************************************\n" );
# get all ref elements
y := XMLElements( r, [ "Ref" ] );
Print( "Found ", Length(y), " Ref elements " );
# select only those which point to the reference manual
yint := Filtered( y, elt ->
      not IsBound(elt.attributes.BookName) or
      (IsBound(elt.attributes.BookName) and elt.attributes.BookName="ref"));
Print( "including ", Length(yint), " within the Reference manual\n" );
# select only those which refer to one of these given in the list `types`:
# "Func", "Oper", "Meth", "Filt", "Prop", "Attr", "Var", "Fam", "InfoClass"
y := Filtered( yint, elt -> ForAny( types, t -> IsBound(elt.attributes.(t))));

referrcount:=0;
warncount:=0;
for elt in y do
  type := First( types, t -> IsBound(elt.attributes.(t)));
  if type <> fail then
    matches := Filtered(x, t -> t.attributes.Name=elt.attributes.(type));
    if Length(matches) = 0 then
      pos:=OriginalPositionDocument(doc[2],elt.start);
      Print( pos[1], ":", pos[2], " : no match for ", type , ":=", elt.attributes.(type), "\n" );
      referrcount:=referrcount+1;
      continue;
    elif Length(matches) = 1 then
      match := matches[1];
    elif IsBound(elt.attributes.Label) then
      matches := Filtered( matches, t -> IsBound(t.attributes.Label));
      matches := Filtered( matches, t -> t.attributes.Label=elt.attributes.Label);
      if Length(matches) > 1 then
        Error("Multiple labels - this should not happen!");
      fi;
      match := matches[1];
    else
      matches2 := Filtered( matches, t -> not IsBound(t.attributes.Label));
      if Length(matches2)=0 then
        pos:=OriginalPositionDocument(doc[2],elt.start);
        Print( pos[1], ":", pos[2], " : no match (wrong type or missing label?) for ", type , ":=", elt.attributes.(type), "\n" );
        Print("  Suggestions: \n");
        matches := Filtered( matches, t -> IsBound(t.attributes.Label));
        for t in matches do
          Print( "Use ", t.name, " with Label:=\"", t.attributes.Label, "\" (for Arg:=\"", t.attributes.Arg, "\")\n");
        od;
        
        referrcount:=referrcount+1;
        continue;
      elif Length(matches2) > 1 then
        Error("Multiple labels - this should not happen!");
      else
        match := matches2[1];
      fi;
    fi;
    if match.name <> type then
      pos:=OriginalPositionDocument(doc[2],elt.start);
      Print( pos[1], ":", pos[2], " : Ref to ", elt.attributes.(type), " uses ", type, " instead of ", match.name, "\n" );
      Print( "./fixconsistency.sh '",  ReplacedString(elt.attributes.(type), """\""", """\\\"""), "' ", type, " ", match.name, " ", pos[1], " ", pos[2], "\n" );
      warncount:=warncount+1;
    fi;
  fi;
od;

Print( "****************************************************************\n" );
stats:=Collected(List(x, elt -> elt.name));
Print("Selected ", Length(x), " ManSections of the following types:\n");
for s in stats do
  Print( s[1], " - ", s[2], "\n");
od;
Print( "Found ", errcount, " errors in ManSection types \n");

Print( "Selected ", Length(y), " Ref elements referring to ManSections \n" );
Print( "Found ", referrcount, " errors and ", warncount, " warnings in Ref elements \n");

Print( "****************************************************************\n" );
return referrcount=0 and warncount=0;
end;


CheckDocCoverage := function( doc )
local r, x, with, without, mansect, pos, y;
r := ParseTreeXMLString(doc[1]);
CheckAndCleanGapDocTree(r);
x:=XMLElements( r, ["ManSection"] );;
with:=0;
without:=0;
Print( "****************************************************************\n" );
Print( "*** Looking for ManSections having no examples \n" );
Print( "****************************************************************\n" );
for mansect in x do
  pos:=OriginalPositionDocument(doc[2],mansect.start);
  y := XMLElements( mansect, ["Example"] );
  if Length(y)=0 then
    if IsBound(mansect.content[1].attributes) and
      IsBound(mansect.content[1].attributes.Name) then
      Print( pos[1], ":", pos[2], " : ", mansect.content[1].attributes.Name );
    elif IsBound(mansect.content[2].attributes) and
      IsBound(mansect.content[2].attributes.Name) then
      Print( pos[1], ":", pos[2], " : ", mansect.content[2].attributes.Name );
    else
      Print( pos[1], ":", pos[2], " : ", mansect.content[1].content[1].content );
    fi;
    without := without + 1;
    Print("\n");
  else
    with := with + 1;
  fi;
od;
Print( "****************************************************************\n" );
Print( "*** Doc coverage report \n");
Print( "****************************************************************\n" );
Print( Length(x), " mansections \n");
Print( with, " with examples \n");
Print( without , " without examples \n");
end;

# Uncomment next line to add ManSections without examples to the test log
# CheckDocCoverage(doc);
QUIT_GAP( CheckManSectionTypes(doc) );


