#############################################################################
##  
#W  helpbase.gi                 GAP Library                      Frank Lübeck
##  
#H  @(#)$Id: helpbase.gi,v 1.15.2.3 2005/05/05 09:26:12 gap Exp $
##  
#Y  Copyright (C)  2001,  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
#Y  (C) 2001 School Math and Comp. Sci., University of St.  Andrews, Scotland
#Y  Copyright (C) 2002 The GAP Group
##  
## The files helpbase.g{d,i} contain the interface between GAP's online help
## and the actual help books.
##  
Revision.helpbase_gi := 
  "@(#)$Id: helpbase.gi,v 1.15.2.3 2005/05/05 09:26:12 gap Exp $";

#############################################################################
##  
#F  # # # # # internal utility functions dealing with strings  # # # # # # #
##  

#############################################################################
##  
#F  StringStreamInputTextFile( <filename> ) . . . . . . .  
##                 content of file as string stream, all '\r' are removed
##  
##  This is useful for text files with text to display, because the files
##  can come with UNIX or DOS/Win line breaks.
##  If this turns out to be of general interest, it can be officially
##  documented.
##  
InstallGlobalFunction(StringStreamInputTextFile, function(fname)
  local s;
  s := StringFile(fname);
  if s = fail then
    return s;
  fi;
  RemoveCharacters(s,"\r");
  return InputTextString(s);
end);

#############################################################################
##  
#F  IsDocumentedVariable( <varname> ) . . . . . . .  check documentation for
#F  name of global variable
##  
##  Returns `true' if <varname> is a string which is the name of a bound global
##  variable and if there exists a help section with this name as index entry
##  (up to case).
##  
##  This utility will first be used in some debug tools showing what is newly
##  installed by loading a package. Can be documented if desired.
##  
BindGlobal("IsDocumentedVariable",  varname -> 
      IsBoundGlobal( varname ) and 
      not IsEmpty( HELP_GET_MATCHES( 
                 HELP_KNOWN_BOOKS[1], SIMPLE_STRING( varname ), true )[1] )
);

#############################################################################
##  
#F  MATCH_BEGIN( <a>, <b> )
##  
##  tries to match beginning of words, where words are separated by single
##  spaces; return `true' or `false'.
##  
##  No form of  normalization is applied to  <a> or <b>, so this  should be done
##  before calling MATCH_BEGIN.
##  
InstallGlobalFunction(MATCH_BEGIN, function( a, b ) local p,q;

    if Length(a)=0 and Length(b)=0 then
      return true;
    fi;

##      if 0 = Length(b) or Length(a) < Length(b)  then
    if Length(a) < Length(b)  then
        return false;
    fi;

    p:=Position(b,' ');
    if p=fail then
      return a{[1..Length(b)]} = b;
    else
      q:=Position(a,' ');
      if q=fail then
        q:=Length(a)+1;
      fi;
      # cope with blanks
      return MATCH_BEGIN(a{[1..q-1]},b{[1..p-1]}) and
             MATCH_BEGIN(a{[q+1..Length(a)]},b{[p+1..Length(b)]});
    fi;

end);

# Slight variant: returns -1 on false and number of exact matching
# words on true (>=0). Can be used to rank some matches higher.
InstallGlobalFunction(MATCH_BEGIN_COUNT, function( a, b )
  local p, q, r;

  if Length(a)=0 and Length(b)=0 then
    return 0;
  fi;

  if Length(a) < Length(b)  then
      return -1;
  fi;

  p:=Position(b,' ');
  if p=fail then
    p:=Position(a,' ');
    if p<>fail then
      a:=a{[1..p-1]};
    fi;
    if Length(b)<=Length(a) and a{[1..Length(b)]} = b then
      if Length(a)= Length(b) then
        return 1;
      else
        return 0;
      fi;
    else
      return -1;
    fi;
  else
    q:=Position(a,' ');
    if q=fail then
      q:=Length(a)+1;
    fi;
    # cope with blanks
    if MATCH_BEGIN(a{[1..q-1]},b{[1..p-1]}) then
      r := MATCH_BEGIN_COUNT(a{[q+1..Length(a)]},b{[p+1..Length(b)]}); 
      if r >= 0 then
        if p = q then
          return 1+r;
        else
          return 0;
        fi;
      else
        return -1;
      fi;
    else
      return -1;
    fi; 
  fi;
end);


#############################################################################
##  
#F  FILLED_LINE( <left>, <right>, <fill> )
##  
##  return string starting with string <left>, a number of characters <fill>
##  and ending with string <right> 6 characters before end of screen.
##  
InstallGlobalFunction(FILLED_LINE, function( l, r, f )
    local   w,  n;

    w := SizeScreen()[1] - 8;
    if w < 8  then
        return "";
    fi;
    if w-7 < Length(l)  then
        l := Concatenation( l{[1..w-7]}, "..." );
    fi;
    if w-7 < Length(r)  then
        r := Concatenation( r{[1..w-7]}, "..." );
    fi;
    if w-7 < Length(l) + Length(r)  then
        r := Concatenation( r{[1..w-7-Length(l)]}, "..." );
    fi;
 
    w := w - Length(l) - Length(r);
    n := ShallowCopy(l);
    Add( n, ' ' );
    while 0 < w  do
        Add( n, f );
        w := w - 1;
    od;
    Add( n, ' ' );
    Append( n, r );

    return n;

end);

InstallGlobalFunction(SIMPLE_STRING, function(str)
  local trans;
  # we simply list here in Position i how character i-1 should be translated
  trans :=Concatenation(
"\000\>\<\c\004\005\006\007\b\t\n\013\014\r\016\017\020\021\022\023\024\025",
"\026\027\030\031\032\033\034\035\036\037  \000   &\000   + -. ",
"0123456789: < >? abcd",
"efghijklmnopqrstuvwxyz \000  _\000abcdefghijklmnopqrstuvwxyz    ",
"\177\200\201\202",
"\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225",
"\226\227\230\231\232\233\234\235\236\237\238",
"¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿aaaaaa",
"aceeeeiiiidnooooo×ouuuuypsaaaaaaaceeeeiiiidnooooo÷ouuuuypy");

  CONV_STRING(str);
  str := trans{List(str, INT_CHAR) + 1};
  # we throw away zero characters (and so backslashes and quotes)
  str := Filtered(str,x->x<>'\000');
  NormalizeWhitespace(str);
  return str;
end);


#############################################################################
##  
##  Each book for GAP's help system  has to be initialized by entries in
##  HELP_KNOWN_BOOKS. These contain a short name (a single word), a long
##  name and the directory of the documentation.
##  
##  For  the   main  books   of  the  GAP   library  this   is  included
##  here,   for    packages   these   initializations   are    done   by
##  `LoadPackageDocumentation'.
##  
##  In the  path for a  help book  there must be   a file `manual.six'. It
##  contains the  indexing information used for  the search of  a topic in
##  the GAP help. The  format of the file is  not prescribed. But if it is
##  different from the current GAP  library documentation format then  the
##  first line must be
##  
##  #SIXFORMAT myownformat
##  
##  Then a  function HELP_BOOK_HANDLER.myownformat.ReadSix is used to read
##  the rest of the file. (See HELP_BOOK_HANDLER below.)
##  
# in first list: normalized names of books
# in second list: for each book a list 
#                 [short name, long name, 
#                  directory containing the manual.six file] 
InstallValue(HELP_KNOWN_BOOKS, [[],[]]);

# if book with normalized name is already installed, we overwrite, if dir
# is the same (so short and long can be changed), else we raise an error
# dir can be given as string relative to GAP's home or as directory object
InstallGlobalFunction(HELP_ADD_BOOK, function( short, long, dir )
  local sortfun, str, hnb, pos;
  # we sort books with main books first and packages alphabetically,
  # (looks a bit lengthy)
  sortfun := function(a, b)
    local main, pa, pb;
    main := ["tutorial", "reference", "extending", "prg tutorial", 
             "new features"];
    pa := Position(main, a);
    pb := Position(main, b);
    if pa <> fail then
      if pb = fail then
        return true;
      else
        return pa <= pb;
      fi;
    else
      if pb <> fail then
        return false;
      else
        return a < b;
      fi;
    fi;
  end;
  str := SIMPLE_STRING(short);
  hnb := HELP_KNOWN_BOOKS;
  # check if we reinstall a known book (with possibly other names)
  pos := First([1..Length(hnb[2])], i-> dir = hnb[2][i][3]);
  if not (Position(hnb[1], str) in [fail, pos]) then
    Error("Help book with normalized name ", str, " already installed!\n");
  fi;
  if pos = fail then 
    pos := Length(hnb[1]) + 1;
  elif IsBound(HELP_BOOKS_INFO.(hnb[1][pos])) then
    # rename help book info if already loaded
    HELP_BOOKS_INFO.(str) := HELP_BOOKS_INFO.(hnb[1][pos]);
    # adjust .bookname
    HELP_BOOKS_INFO.(str).bookname := short;
    Unbind(HELP_BOOKS_INFO.(hnb[1][pos]));
  fi;
  hnb[1][pos] := str;
  hnb[2][pos] := [short, long, dir];
  SortParallel(hnb[1], hnb[2], sortfun);
end);

#############################################################################
##  
#V  HELP_BOOK_HANDLER
##  
##  We use a record to store handler for different tasks with a help book.
##  The handler  for  the current  library books  is called "default".   A
##  handler is a record with some  functions as components, at least there
##  must be:
##  
##  - ReadSix          # reading a BOOK_INFO from a manual.six stream
##  - ShowChapters     # returns text or lines with chapter headers
##  - ShowSections     # same for section headers
##  - SearchMatches    # returns list of numbers referring to entries in 
##                     # BOOK_INFO's .entries list
##  - MatchPrevChap    # number of match for "<<" (last in HELP_LAST.BOOK
##  - MatchNextChap    # number of match for ">>"  and HELP_LAST.MATCH)
##  - MatchPrev        # number of match for "<"
##  - MatchNext        # number of match for ">"
##  - HelpData         # returns for given number of entry in .entries the
##                     # corresponding help data for a given format
##                     # (a special format is "ref" for cross references,
##                     # see HELP_BOOK_HANDLER.HelpDataRef below for
##                     # details)
##  The `default' handler functions will be assigned helpdef.g, see there for
##  more details on the interfaces of each of these functions.
##  
InstallValue(HELP_BOOK_HANDLER, rec(default:=rec()));

#############################################################################
##  
#V  HELP_BOOKS_INFO . . . . . . . . . . . . .  collected info about the books
##
##  The record <HELP_BOOKS_INFO>  contains for each loaded  help book an
##  entry  describing the  information found  in the  "manual.six" file.
##  This information is  stored in a record with at  least the following
##  components, which are used by this generic interface to the help system:
##
##  bookname:	    
##  
##    The short name of the book, e.g. "ref", "matrix", "EDIM".
##  
##  entries: 
##  
##    List of entries for the  search, each entry must  be a list. In  the
##    first position there must be a string which is shown for this match,
##    in case several matches for a topic where found.
##  
##  formats: (not necessary ???)
##    
##    List of output formats available for this book (like ["text", "url", ..]),
##    this must contain at least "text".
##    
##  The  remaining positions  in    the  .entries lists and/or     further
##  components  in  this help  book record  depend  on  the format  of the
##  documentation and the corresponding handler functions.
##  
InstallValue(HELP_BOOKS_INFO, rec());

#############################################################################
##  
#F  HELP_BOOK_INFO( <book> )  . . . . . . . . . . . . . get info about a book
##  
##  Returns  the  corresponding HELP_BOOKS_INFO  entry  or  reads  in  the
##  corresponding manual.six file, if not yet done.
##  
##  <book> must be a record, which is just returned, or the short name of a 
##  known book.
##  
InstallGlobalFunction(HELP_BOOK_INFO, function( book )
  local pos, bnam, nnam, path, dirs, six, stream, line, handler;

  # if this is already a record return it
  if IsRecord(book)  then
    return book;
  fi;
  
  book := LowercaseString(book);
  pos := Position(HELP_KNOWN_BOOKS[1], book);
  if pos = fail  then
    # try to match beginning 
    pos := Filtered(HELP_KNOWN_BOOKS[1], bn-> MATCH_BEGIN(bn, book));
    if Length(pos) = 0 then
      # give up
      return fail;
    else
      pos := Position(HELP_KNOWN_BOOKS[1], pos[1]);
    fi;
  fi;
  # now we have the (short) name of the book
  bnam := HELP_KNOWN_BOOKS[1][pos];
  
  if IsBound(HELP_BOOKS_INFO.(bnam)) then
    # done
    return HELP_BOOKS_INFO.(bnam);
  fi;

  # get the filename of the "manual.six" file
  path := HELP_KNOWN_BOOKS[2][pos][3];
  if IsDirectory(path) then
    dirs := [path];
  else
    dirs := DirectoriesLibrary( path );
  fi;

  six  := Filename( dirs, "manual.six" );
  if six = fail  then
    # give up
    return fail;
  fi;

  # read the manual.six file
  # read the first non-empty line to find out the handler for the corresponding
  # manual format (no explicit format implies the "default" handler)
  stream := InputTextFile(six);
  line := "";
  while Length(line) = 0 do
    line := ReadLine(stream);
    if line=fail then
      CloseStream(stream);
      return fail;
    fi;
    line := NormalizedWhitespace(line);
  od;
  if Length(line)>10 and line{[1..10]}="#SIXFORMAT" then
    handler := line{[12..Length(line)]};
    NormalizeWhitespace(handler);
  else
    handler := "default";
    RewindStream(stream);
  fi;
  # give up if handler functions are not (yet) loaded
  if not IsBound(HELP_BOOK_HANDLER.(handler)) then
    Print("\n#W WARNING: No handler for help book `",
          HELP_KNOWN_BOOKS[2][pos][1],
          "' available,\n#W removing this book.\n");
    if handler = "GapDocGAP" then
      Print("#W HINT: Install and load the GAPDoc package, see\n",
            "#W http://www.math.rwth-aachen.de/~Frank.Luebeck/GAPDoc\n");
    fi;
    HELP_KNOWN_BOOKS[1][pos] := Concatenation("XXXX ", bnam, ": THROWN OUT");
    HELP_KNOWN_BOOKS[2][pos][2] := "NOT AVAILABLE (no handler)";
    return fail;
  fi;
  HELP_BOOKS_INFO.(bnam) := HELP_BOOK_HANDLER.(handler).ReadSix(stream);
  
  # adjust some entries used on the interface level
  HELP_BOOKS_INFO.(bnam).handler := handler;
  HELP_BOOKS_INFO.(bnam).bookname := HELP_KNOWN_BOOKS[2][pos][1];

  # done
  return HELP_BOOKS_INFO.(bnam);
end);

#############################################################################
##  
##  
#F  # # # # # # # # # # generic show functions  # # # # # # # # # # # # # # #
##  

#############################################################################
##  
##  The central  function for the help  system is, of course,  `HELP' below.
##  Depending on  the search  string it may  trigger different  actions. The
##  functions for these actions are defined first. Many of them delegate the
##  actual work to the handler functions for the available books.
##  



#############################################################################
##  
#F  HELP_SHOW_BOOKS( ignored... ) . . . . . . . . . . .  show available books
##  
InstallGlobalFunction(HELP_SHOW_BOOKS, function( arg )
  local books;

  books := ["             Table of currently available help books",
            FILLED_LINE( "short name for ? commands", "Description", '_')];
  Append(books, List(HELP_KNOWN_BOOKS[2], a-> FILLED_LINE(a[1], a[2], ' ')));
  Pager(books);
  return true;
  
end);

#############################################################################
##
#F  HELP_SHOW_CHAPTERS( <book> )  . . . . . . . . . . . . . show all chapters
##
InstallGlobalFunction(HELP_SHOW_CHAPTERS, function(book)
  local   info;
  # delegate to handler 
  info := HELP_BOOK_INFO(book);
  if info = fail then
    Print("#W Help: Book ", book, " not found.\n");
  else
    Pager(HELP_BOOK_HANDLER.(info.handler).ShowChapters(info));
  fi;
  return true;  
end);

#############################################################################
##
#F  HELP_SHOW_SECTIONS( <book> )  . . . . . . . . . . . . . show all sections
##
InstallGlobalFunction(HELP_SHOW_SECTIONS, function(book)
  local   info;
  # delegate to handler 
  info := HELP_BOOK_INFO(book);
  if info = fail then
    Print("#W Help: Book ", book, " not found.\n");
  else
    Pager(HELP_BOOK_HANDLER.(info.handler).ShowSections(info));
  fi;
  return true;  
end);

#############################################################################
##  
#F  HELP_PRINT_MATCH( <match> ) . . . . . . the core function which finally
##  gets the data for displaying the help and displays it
##  
##  <match> is [book, entrynr]
##  
if not IsBound(HELP_VIEWER) then HELP_VIEWER := ["screen"]; fi;
InstallGlobalFunction(HELP_PRINT_MATCH, function(match)
  local   book,  entrynr,  viewer,  type,  data, hv;
  book := HELP_BOOK_INFO(match[1]);
  entrynr := match[2];
  if HELP_LAST.NEXT_VIEWER = false then
    hv := HELP_VIEWER;
  else
    hv := HELP_VIEWER{[Position(HELP_VIEWER,
                            HELP_LAST.VIEWER)+1..Length(HELP_VIEWER)]};
    if Length(hv) = 0 then
      hv := [HELP_LAST.VIEWER];
    fi;
    HELP_LAST.NEXT_VIEWER := false;
  fi;
  for viewer in hv do
    # type of data we need now depends on help viewer 
    type := HELP_VIEWER_INFO.(viewer).type;
    # get the data via appropriate handler
    data := HELP_BOOK_HANDLER.(book.handler).HelpData(book, entrynr, type);
    if data <> fail then
      # show the data
      HELP_VIEWER_INFO.(viewer).show(data);
      break;
    fi;
    HELP_LAST.VIEWER := viewer;
  od;
  HELP_LAST.BOOK := book;
  HELP_LAST.MATCH := entrynr;
  HELP_LAST.VIEWER := viewer;
  return true;
end);

#############################################################################
##  
#F  HELP_SHOW_PREV_CHAPTER( <book> ) . . . . . . . . show chapter introduction
##  
InstallGlobalFunction(HELP_SHOW_PREV_CHAPTER, function( arg )
  local   info,  match;
  info := HELP_BOOK_INFO(HELP_LAST.BOOK);
   match := HELP_BOOK_HANDLER.(info.handler).MatchPrevChap(info, 
                   HELP_LAST.MATCH);
  if match[2] = fail then
    Print("Help:  no match found.\n");
  else
    HELP_PRINT_MATCH(match);
    HELP_LAST.MATCH := match[2];
    return true;
  fi;
end);

#############################################################################
##
#F  HELP_SHOW_NEXT_CHAPTER( <book> )  . . . . . . . . . . . show next chapter
##
InstallGlobalFunction(HELP_SHOW_NEXT_CHAPTER, function( arg )
  local   info,  match;
  info := HELP_BOOK_INFO(HELP_LAST.BOOK);
  match := HELP_BOOK_HANDLER.(info.handler).MatchNextChap(info, 
                   HELP_LAST.MATCH);
  if match[2] = fail then
    Print("Help:  no match found.\n");
  else
    HELP_PRINT_MATCH(match);
    HELP_LAST.MATCH := match[2];
    return true;
  fi;
end);

#############################################################################
##
#F  HELP_SHOW_PREV( <book> )  . . . . . . . . . . . . . show previous section
##
InstallGlobalFunction(HELP_SHOW_PREV, function( arg )
  local   info,  match;
  info := HELP_BOOK_INFO(HELP_LAST.BOOK);
  match := HELP_BOOK_HANDLER.(info.handler).MatchPrev(info, 
                   HELP_LAST.MATCH);
  if match[2] = fail then
    Print("Help:  no match found.\n");
  else
    HELP_PRINT_MATCH(match);
    HELP_LAST.MATCH := match[2];
    return true;
  fi;
end);

#############################################################################
##
#F  HELP_SHOW_NEXT( <book> )  . . . . . . . . . . . . . . . show next section
##
InstallGlobalFunction(HELP_SHOW_NEXT, function( arg )
  local   info,  match;
  info := HELP_BOOK_INFO(HELP_LAST.BOOK);
  match := HELP_BOOK_HANDLER.(info.handler).MatchNext(info, 
                   HELP_LAST.MATCH);
  if match[2] = fail then
    Print("Help:  no match found.\n");
  else
    HELP_PRINT_MATCH(match);
    HELP_LAST.MATCH := match[2];
    return true;
  fi;
end);

#############################################################################
##
#F  HELP_SHOW_WELCOME( <book> ) . . . . . . . . . . . .  show welcome message
##
InstallGlobalFunction(HELP_SHOW_WELCOME, function( book )
    local   lines;

    lines := [
"    Welcome to GAP 4\n",
" Try '?tutorial: The Help system' (without quotes) for an introduction to",
" the help system.\n",
" '?chapters' and '?sections' will display tables of contents."
    ];
    Pager(lines);
    return true;
end);
  
#############################################################################
##
#F  HELP_GET_MATCHES( <book>, <topic>, <frombegin> )  . . .  search through 
#F  the books
##  
##  This function returns a list of two lists [exact, match] and these lists
##  consist of  pairs [book,  entrynumber], where  book is  a help  book and
##  entrynumber is the number of a  match in book.entries. As the names say,
##  the  first list  "exact"  contains  the exact  matches  and "match"  the
##  remaining ones.
##
InstallGlobalFunction(HELP_GET_MATCHES, function( books, topic, frombegin )
  local exact, match, em, b, x;
  
  # <exact> and <match> contain the topics matching
  exact := [];
  match := [];

  if IsString(books) or IsRecord(books) then
    books := [books];
  fi;
  
  # collect the matches (by number)
  books := List(books, HELP_BOOK_INFO);
  for b in books do
    # now delegate the work to the handler functions
    if b<>fail then
      em := HELP_BOOK_HANDLER.(b.handler).SearchMatches(b, topic, frombegin);
      for x in em[1] do 
	Add(exact, [b, x]);
      od;
      for x in em[2] do
	Add(match, [b, x]);
      od;
    fi;
  od;
  
  # in case of substring search the only special handling of exact matches
  # is that they are shown first
  if frombegin = false then
    match := Concatenation(exact, match);
    exact := [];
  fi;

  return [exact, match];
end);

#############################################################################
##
#F  HELP_SHOW_MATCHES( <book>, <topic>, <frombegin> )  . . .  show list of
#F  matches or single match directly
##  
InstallGlobalFunction(HELP_SHOW_MATCHES, function( books, topic, frombegin )
  local   exact,  match,  x,  lines,  cnt,  i,  str,  n;

  # first get lists of exact and other matches
  x := HELP_GET_MATCHES( books, topic, frombegin );
  exact := x[1];
  match := x[2];
  
  # no topic found
  if 0 = Length(match) and 0 = Length(exact)  then
    Print( "Help: no matching entry found\n" );
    return false;
    
  # one exact or together one topic found
  elif 1 = Length(exact) or (0 = Length(exact) and 1 = Length(match)) then
    if Length(exact) = 0 then exact := match; fi;
    i := exact[1];
    str := Concatenation("Help: Showing `", i[1].bookname,": ", 
                                               i[1].entries[i[2]][1], "'\n");
    # to avoid line breaking when str contains escape sequences:
    n := 0;
    while n < Length(str) do
      Print(str{[n+1..Minimum(Length(str), 
                                    n + QuoInt(SizeScreen()[1] ,2))]}, "\c");
      n := n + QuoInt(SizeScreen()[1] ,2);
    od;
    HELP_PRINT_MATCH(i);
    return true;

  # more than one topic found, show overview in pager
  else
    lines := 
        ["Help: several entries match this topic - type ?2 to get match [2]\n"];
    HELP_LAST.TOPICS:=[];
    cnt := 0;
    # show exact matches first
    match := Concatenation(exact, match);
    for i  in match  do
      cnt := cnt+1;
      topic := Concatenation(i[1].bookname,": ",i[1].entries[i[2]][1]);
      Add(HELP_LAST.TOPICS, i);
      Add(lines,Concatenation("[",String(cnt),"] ",topic));
    od;
    Pager(rec(lines := lines, formatted := true));
    return true;
  fi;
end);

# choosing one of last shown  list of matches
InstallGlobalFunction(HELP_SHOW_FROM_LAST_TOPICS, function(nr)
  if nr = 0 or Length(HELP_LAST.TOPICS) < nr then
    Print("Help:  No such topic.\n");
    return false;
  fi;
  HELP_PRINT_MATCH(HELP_LAST.TOPICS[nr]);
  return true;
end);

##  A generic function for HELP_BOOK_HANDLER.(handler).HelpData(b, e, "ref")
##  This can be used to resolve cross references between books. The function 
##  returns a list r with six entries:
##  
##    - r[1]    search string including book name       "ref: Xyz"
##    - r[2]    (sub)section number as string           "5.14.2" or "3.1"
##    - r[3]    name of dvi-file (or fail)              "/doc/path/manual.dvi"
##    - r[4]    name of pdf-file (or fail)              "/doc/path/manual.pdf"
##    - r[5]    page number for r[3], r[4] (or fail)    37
##    - r[6]    URL (or fail)                           "/doc/htm/ch3.html#s4"
##    - r[7]    [chnr, secnr, subsecnr]                 [5,14,2] or [3,1,0]
##  
HELP_BOOK_HANDLER.HelpDataRef := function(book, entrynr)
  local    info,  handler,  entry,  chnr,  secnr,  pos,  res,  r;

  info := HELP_BOOK_INFO(book);
  handler := info.handler;
  entry := info.entries[entrynr];
 
  # the search and reference string
  res := [ Concatenation(info.bookname, ": ", entry[1]) ];
  # the section number string
  secnr := HELP_BOOK_HANDLER.(handler).HelpData(info, entrynr, "secnr");
  Add(res, secnr[2]);
  # dvi-file and page number
  r := HELP_BOOK_HANDLER.(handler).HelpData(info, entrynr, "dvi");
  if r = fail then
    Add(res, fail);
  else
    Add(res, r.file);
    res[5] := r.page;
  fi;
  # pdf-file and page number
  r := HELP_BOOK_HANDLER.(handler).HelpData(info, entrynr, "pdf");
  if r = fail then
    res[4] := fail;
  else
    res[4] := r.file;
    if not IsBound(res[5]) then
      res[5] := r.page;
    fi; 
  fi;
  if not IsBound(res[5]) then
    res[5] := fail;
  fi;      
  # URL
  r := HELP_BOOK_HANDLER.(handler).HelpData(info, entrynr, "url");
  if r = fail then
    Add(res, fail);
  else
    Add(res, r);
  fi;
  # [chnr, secnr, subsecnr] as list
  Add(res, secnr[1]);

  return res;
end;

##  From this info we generate a manual.lab file for a given book.
##  Note that the gapmacro format has subsection information in manual.lab
##  which is not available in manual.six! So, one cannot properly regenerate
##  a manual.lab file for those books.
InstallGlobalFunction(HELP_LAB_FILE, function(file, book)
  local fun, str, i;
  book := HELP_BOOK_INFO(book);
  fun := function(i)
    local r;
    r := HELP_BOOK_HANDLER.HelpDataRef(book, i);
    return Concatenation("\\makelabel {",
            SIMPLE_STRING(r[1]), "}{", r[2], "}\n");
  end;
  str := "";
  for i in [1..Length(book.entries)] do
    Append(str, fun(i));
  od;
  PrintTo(file, str);
end);


#############################################################################
##  
#F  HELP( <string> )  . . . . . . . . . . . . . . .  deal with a help request
##  
# here we store the last 16 requests 
HELP_RING_IDX :=  0;
HELP_RING_SIZE := 16;
InstallValue(HELP_BOOK_RING, ListWithIdenticalEntries( HELP_RING_SIZE, 
                                             ["tutorial"] ));
InstallValue(HELP_TOPIC_RING, ListWithIdenticalEntries( HELP_RING_SIZE, 
                                             "welcome to gap" ));
# here we store the last shown topic, initialized with 0 (leading to
# show "Tutorial: Help", see below)
InstallValue(HELP_LAST, rec(MATCH := 0, BOOK := 0, 
             NEXT_VIEWER := false, TOPICS := []));
NAMES_SYSTEM_GVARS:= "to be defined in init.g";

InstallGlobalFunction(HELP, function( str )
  local origstr, p, book, books, move, add, b;

  origstr := ShallowCopy(str);
  
  # extract the book
  p := Position( str, ':' );
  if p <> fail  then
      book := str{[1..p-1]};
      str  := str{[p+1..Length(str)]};
  else
      book := "";
  fi;
  
  # normalizing for search
  book := SIMPLE_STRING(book);
  str := SIMPLE_STRING(str);
  
  # we check if `book' MATCH_BEGINs some of the available books
  books := Filtered(HELP_KNOWN_BOOKS[1], bn-> MATCH_BEGIN(bn, book));
  if Length(book) > 0 and Length(books) = 0 then
    Print("Help: None of the available books matches (try: '?books').\n");
    return;
  fi;
  
  # function to add a topic to the ring
  move := false;
  add  := function( books, topic )
      if not move  then
          HELP_RING_IDX := (HELP_RING_IDX+1) mod HELP_RING_SIZE;
          HELP_BOOK_RING[HELP_RING_IDX+1]  := books;
          HELP_TOPIC_RING[HELP_RING_IDX+1] := topic;
      fi;
  end;
  
  # if the topic is empty show the last shown one again 
  if  book = "" and str = ""  then
       if HELP_LAST.BOOK = 0 then
         HELP("Tutorial: Help");
       else
         HELP_PRINT_MATCH( [HELP_LAST.BOOK, HELP_LAST.MATCH] );
       fi;
       return;

  # if topic is "&" show last topic again, but with next viewer in viewer
  # list, or with last viewer again if there is no next one
  elif book = "" and str = "&" then
       if HELP_LAST.BOOK = 0 then
         HELP("Tutorial: The Help System");
       else
         HELP_LAST.NEXT_VIEWER := true;
         HELP_PRINT_MATCH( [HELP_LAST.BOOK, HELP_LAST.MATCH] );
       fi;
       return;
  
  # if the topic is '-' we are interested in the previous search again
  elif book = "" and str = "-"  then
      HELP_RING_IDX := (HELP_RING_IDX-1) mod HELP_RING_SIZE;
      books := HELP_BOOK_RING[HELP_RING_IDX+1];
      str  := HELP_TOPIC_RING[HELP_RING_IDX+1];
      move := true;

  # if the topic is '+' we are interested in the last section again
  elif book = "" and str = "+"  then
      HELP_RING_IDX := (HELP_RING_IDX+1) mod HELP_RING_SIZE;
      books := HELP_BOOK_RING[HELP_RING_IDX+1];
      str  := HELP_TOPIC_RING[HELP_RING_IDX+1];
      move := true;
  fi;
  
  # number means topic from HELP_LAST.TOPICS list
  if book = "" and ForAll(str, a-> a in "0123456789") then
      HELP_SHOW_FROM_LAST_TOPICS(Int(str));
    
  # if the topic is '<' we are interested in the one before 'LastTopic'
  elif book = "" and str = "<"  then
      HELP_SHOW_PREV();

  # if the topic is '>' we are interested in the one after 'LastTopic'
  elif book = "" and str = ">"  then
      HELP_SHOW_NEXT();

  # if the topic is '<<' we are interested in the previous chapter intro
  elif book = "" and str = "<<"  then
      HELP_SHOW_PREV_CHAPTER();

  # if the topic is '>>' we are interested in the next chapter intro
  elif book = "" and str = ">>"  then
      HELP_SHOW_NEXT_CHAPTER();

  # if the subject is 'Welcome to GAP' display a welcome message
  elif book = "" and str = "welcome to gap"  then
      if HELP_SHOW_WELCOME(book)  then
          add( books, "Welcome to GAP" );
      fi;

  # if the topic is 'books' display the table of books
  elif book = "" and str = "books"  then
      if HELP_SHOW_BOOKS()  then
          add( books, "books" );
      fi;

  # if the topic is 'chapters' display the table of chapters
  elif str = "chapters"  or  book <> "" and str = "" then
      if ForAll(books, b->  HELP_SHOW_CHAPTERS(b)) then
        add( books, "chapters" );
      fi;

  # if the topic is 'sections' display the table of sections
  elif str = "sections"  then
      if ForAll(books, b-> HELP_SHOW_SECTIONS(b)) then
        add(books, "sections");
      fi;

  # if the topic is '?<string>' search the index for any entries for
  # which <string> is a substring (as opposed to an abbreviation)
  elif Length(str) > 0 and str[1] = '?'  then
      str := str{[2..Length(str)]};    
      NormalizeWhitespace(str);
      if HELP_SHOW_MATCHES( books, str, false)  then
          add( books, str );
      fi;

  # search for this topic
  elif HELP_SHOW_MATCHES( books, str, true )  then
      add( books, str );
  elif origstr in NAMES_SYSTEM_GVARS then
      Print( "Help: '", origstr, "' is currently undocumented.\n",
             "      For details, try ?Undocumented Variables\n" );
  elif book = "" and 
                 ForAny(HELP_KNOWN_BOOKS[1], bk -> MATCH_BEGIN(bk, str)) then
      Print( "Help: Are you looking for a certain book? (Trying '?", origstr, 
             ":' ...\n");
      HELP( Concatenation(origstr, ":") );
  else
     # seems unnecessary, since some message is already printed in all
     # cases above (?):
     # Print( "Help: Sorry, could not find a match for '", origstr, "'.\n");
  fi;
end);

