#############################################################################
####
##
#W  anupqios.gi            ANUPQ package                          Greg Gamble
##
##  This file installs core functions used with iostreams.
##    
#Y  Copyright (C) 2001  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
##

#############################################################################
##
#F  PQ_START( <workspace>, <setupfile> ) . . . open a stream for a pq process
##
##  ensures the images file written by the `pq' binary when in  the  Standard
##  Presentation menu is empty, opens an io stream  to  a  `pq'  process  (if
##  <setupfile> is `fail') or a file stream for a setup file (if  <setupfile>
##  is a filename i.e. a string) and returns  a  record  with  fields  `menu'
##  (current menu for the `pq' binary), `opts' (the runtime switches used  by
##  the `pq' process), `workspace' (the value of <workspace> which should  be
##  a positive integer), and `stream' (the io or file stream opened).
##
InstallGlobalFunction(PQ_START, function( workspace, setupfile )
local opts, iorec, topqlogfile;
  PrintTo(ANUPQData.SPimages, ""); #to ensure it's empty
  if setupfile = fail then
    opts := [ "-G" ];
  else
    opts := [ "-i", "-k", "-g" ];
  fi;
  if workspace <> 10000000 then
    Append( opts, [ "-s", String(workspace) ] );
  fi;
  iorec := rec( menu := "SP", 
                opts := opts,
                workspace := workspace );
  if setupfile = fail then
    iorec.stream := InputOutputLocalProcess( ANUPQData.tmpdir, 
                                             ANUPQData.binary, 
                                             opts );
    if iorec.stream = fail then
      Error( "failed to launch child process" );
    fi;
    # menus are flushed at InfoANUPQ level 6, prompts at level 5
    FLUSH_PQ_STREAM_UNTIL(iorec.stream, 6, 5, PQ_READ_NEXT_LINE, IS_PQ_PROMPT);
  else
    iorec.stream := OutputTextFile(setupfile, false);
    iorec.setupfile := setupfile;
    ToPQk(iorec, [], [ "#call pq with flags: '",
                       JoinStringsWithSeparator(opts, " "),
                       "'" ]);
  fi;
  return iorec;
end );

#############################################################################
##
#F  PqStart(<G>,<workspace> : <options>) . Initiate interactive ANUPQ session
#F  PqStart(<G> : <options>)
#F  PqStart(<workspace> : <options>)
#F  PqStart( : <options>)
##
##  activate an iostream for an interactive {\ANUPQ} process (i.e.  `PqStart'
##  starts up a `pq' binary process and opens a {\GAP} iostream  to  ``talk''
##  to that process) and returns an integer <i> that can be used to  identify
##  that process. The argument <G>, if given, should be an *fp group* or  *pc
##  group* that the user  intends  to  manipute  using  interactive  {\ANUPQ}
##  functions. If `PqStart' is given an integer argument <workspace> then the
##  `pq' binary is started up with a workspace (an  integer  array)  of  size
##  <workspace> (i.e. $4 \times <workspace>$ bytes in a 32-bit  environment);
##  otherwise, the `pq' binary sets a default workspace of $10000000$.
##
##  The only <options> currently recognised  by  `PqStart'  are  `Prime'  and
##  `Exponent' (see~"Pq" for details) and if provided  they  are  essentially
##  global for the interactive {\ANUPQ} process, except that any  interactive
##  function interacting with the process and passing new  values  for  these
##  options will over-ride the global values.
##
InstallGlobalFunction(PqStart, function(arg)
local opts, iorec, procId, G, workspace, optname;

  if 2 < Length(arg) then
    Error("at most two arguments expected.\n");
  fi;

  if not IsEmpty(arg) and IsGroup( arg[1] ) then
    G := arg[1];
    if not( IsFpGroup(G) or IsPcGroup(G) ) then
      Error( "argument <G> should be an fp group or a pc group\n" );
    fi;
    arg := arg{[2 .. Length(arg)]};
  fi;

  if not IsEmpty(arg) then
    workspace := arg[1];
    if not IsPosInt(workspace) then
      Error("argument <workspace> should be a positive integer.\n");
    fi;
  else
    workspace := 10000000;
  fi;

  iorec := PQ_START( workspace, fail );
  if IsBound( G ) then
    iorec.group := G;
  fi;
  iorec.calltype := "interactive";
  for optname in ANUPQGlobalOptions do
    VALUE_PQ_OPTION(optname, iorec);
  od;

  procId := Length(ANUPQData.io) + 1;
  iorec.procId := procId;
  ANUPQData.io[ procId ] := iorec;
  return procId;
end);

#############################################################################
##
#F  PqQuit( <i> )  . . . . . . . . . . . . Close an interactive ANUPQ session
#F  PqQuit()
##
##  closes the stream of the <i>th or default  interactive  {\ANUPQ}  process
##  and unbinds its `ANUPQData.io' record.
##
InstallGlobalFunction(PqQuit, function(arg)
local ioIndex;

  ioIndex := ANUPQ_IOINDEX(arg);
  # No need to bother about descending through the menus.
  CloseStream(ANUPQData.io[ioIndex].stream);
  Unbind(ANUPQData.io[ioIndex]);
end);

#############################################################################
##
#F  PqQuitAll() . . . . . . . . . . . .  Close all interactive ANUPQ sessions
##
##  closes the streams of all active interactive {\ANUPQ} process and unbinds
##  their `ANUPQData.io' records.
##
InstallGlobalFunction(PqQuitAll, function()
local ioIndex;

  for ioIndex in [1 .. Length(ANUPQData.io)] do
    if IsBound(ANUPQData.io[ioIndex]) then
      CloseStream(ANUPQData.io[ioIndex].stream);
      Unbind(ANUPQData.io[ioIndex]);
    fi;
  od;
end);

#############################################################################
##
#F  ANUPQ_IOINDEX . . . . the number identifying an interactive ANUPQ session
##
##  returns the index of the record in the `ANUPQData.io' list  corresponding
##  to an interactive {\ANUPQ} session. With  no  argument  the  first  bound
##  index in `ANUPQData.io' is returned. With integer (first)  argument  <i>,
##  <i> is returned if `ANUPQData.io[<i>]' is bound.
##
InstallGlobalFunction(ANUPQ_IOINDEX, function(arglist)
local ioIndex;

  if IsEmpty(arglist) then
    # Find the first bound ioIndex
    ioIndex := 1;
    while not(IsBound(ANUPQData.io[ioIndex])) and 
          ioIndex < Length(ANUPQData.io) do
      ioIndex := ioIndex + 1;
    od;
    if IsBound(ANUPQData.io[ioIndex]) then
      return ioIndex;
    else
      Info(InfoANUPQ + InfoWarning, 1, 
           "No interactive ANUPQ sessions are currently active");
      return fail;
    fi;
  elif IsBound(ANUPQData.io[ arglist[1] ]) then
    return arglist[1];
  else
    Error("no such interactive ANUPQ session\n");
  fi;
end);

#############################################################################
##
#F  ANUPQ_IOINDEX_ARG_CHK .  Checks ANUPQ_IOINDEX has the right no. of arg'ts
##
InstallGlobalFunction(ANUPQ_IOINDEX_ARG_CHK, function(arglist)
  if Length(arglist) > 1 then
    Info(InfoANUPQ + InfoWarning, 1,
         "Expected 0 or 1 arguments, all but first argument ignored");
  fi;
end);

#############################################################################
##
#F  ANUPQDataRecord([<i>]) . . . . . . . returns the data record of a process
##
InstallGlobalFunction(ANUPQDataRecord, function( arg )
  if not IsEmpty(arg) and arg[1] = 0 and IsBound( ANUPQData.ni ) then
    return ANUPQData.ni;
  else
    return ANUPQData.io[ CallFuncList(PqProcessIndex, arg) ];
  fi;
end);

#############################################################################
##
#F  PqProcessIndex( <i> ) . . . . . . . . . . . User version of ANUPQ_IOINDEX
#F  PqProcessIndex()
##
##  If given (at least) one integer  argument  `PqProcessIndex'  returns  its
##  first argument if it corresponds to  an  active  interactive  process  or
##  raises an error; otherwise, with no arguments,  it  returns  the  default
##  active interactive process. If the user provides more than  one  argument
##  then all arguments other than the  first  argument  are  ignored  (and  a
##  warning is issued to `Info' at `InfoANUPQ' or `InfoWarning' level 1).
##
InstallGlobalFunction(PqProcessIndex, function(arg)
  ANUPQ_IOINDEX_ARG_CHK(arg);
  return ANUPQ_IOINDEX(arg);
end);

#############################################################################
##
#F  PqProcessIndices() . . . . the list of active interactive ANUPQ processes
##
##  returns the list of (integer) indices of all active interactive  {\ANUPQ}
##  processes.
##
InstallGlobalFunction(PqProcessIndices, function()
  return Filtered( [1..Length(ANUPQData.io)], i -> IsBound( ANUPQData.io[i] ) );
end);

#############################################################################
##
#F  IsPqProcessAlive( <i> ) . .  checks an interactive ANUPQ process iostream
#F  IsPqProcessAlive()
##
##  return  `true'  if  the  {\GAP}  iostream  of  the  <i>th  (or   default)
##  interactive {\ANUPQ} process is alive (i.e. can still be written to),  or
##  `false', otherwise.
##
InstallGlobalFunction(IsPqProcessAlive, function(arg)
  return not IsEndOfStream( ANUPQData.io[ PqProcessIndex(arg) ].stream );
end);

#############################################################################
##
#V  PQ_MENUS . . . . . . . . . . . data describing the menus of the pq binary
##
##  a record whose fields are abbreviated names of  the  menus  of  the  `pq'
##  binary and whose values are themselves records with fields:
##
##    name
##        long name of menu;
##    depth
##        the number of times 0 must be passed to the `pq' binary for  it  to
##        exit;
##    prev
##        the menu one gets to from the current menu via option 0 (or `""' in
##        the case of the menu `SP';
##    nextopt
##        a record whose fields are the new menus of greater depth  that  can
##        be reached by an option of the current menu, and whose  values  are 
##        the corresponding numbers of the options of the current menu needed
##        to get to the new menus.
##
InstallValue(PQ_MENUS, rec(
  SP  := rec( name  := "Standard Presentation Menu",
              depth := 1, prev  := "",   nextopt := rec( pQ := 7 ) ),
  pQ  := rec( name  := "(Main) p-Quotient Menu",
              depth := 2, prev  := "SP", nextopt := rec( pG  := 9, ApQ := 8 ) ),
  pG  := rec( name  := "(Main) p-Group Generation Menu",
              depth := 3, prev  := "pQ", nextopt := rec( ApG := 6 ) ),
  ApQ := rec( name  := "Advanced p-Quotient Menu",
              depth := 3, prev  := "pQ", nextopt := rec() ),
  ApG := rec( name  := "Advanced p-Group Gen'n Menu",
              depth := 4, prev  := "pG", nextopt := rec() )
  ) );

#############################################################################
##
#F  PQ_MENU( <datarec>, <newmenu> ) . . . . . . change/get menu of pq process
#F  PQ_MENU( <datarec> )
##
InstallGlobalFunction(PQ_MENU, function(arg)
local datarec, newmenu, nextmenu, tomenu, infolev;
  datarec := arg[1];
  if 2 = Length(arg) then
    newmenu := arg[2];
    if datarec.menu in ["SP", "pQ"] and newmenu in ["ApQ", "pG", "ApG"] then
      PQ_GRP_EXISTS_CHK( datarec ); #We try to avoid seg-faults!
    fi;
    while datarec.menu <> newmenu do
      if PQ_MENUS.(datarec.menu).depth >= PQ_MENUS.(newmenu).depth then
        datarec.menu := PQ_MENUS.(datarec.menu).prev;
        tomenu := PQ_MENUS.(datarec.menu).name;
        ToPQk(datarec, [ 0 ], [ "  #to ", tomenu]);
        infolev := 5;
      elif datarec.menu = "pQ" and newmenu = "ApQ" then
        datarec.menu := "ApQ";
        tomenu := PQ_MENUS.(datarec.menu).name;
        ToPQk(datarec, [ PQ_MENUS.pQ.nextopt.ApQ ], [ "  #to ", tomenu ]);
        infolev := 6;
      else
        nextmenu := RecNames( PQ_MENUS.(datarec.menu).nextopt )[1];
        tomenu := PQ_MENUS.(nextmenu).name;
        ToPQk(datarec, [ PQ_MENUS.(datarec.menu).nextopt.(nextmenu) ],
                       [ "  #to ", tomenu ]);
        datarec.menu := nextmenu;
        infolev := 6;
      fi;
      # menus are flushed at InfoANUPQ level 6, prompts at level 5
      if not IsBound(datarec.setupfile) then
        FLUSH_PQ_STREAM_UNTIL(datarec.stream, infolev, 5, PQ_READ_NEXT_LINE,
                              IS_PQ_PROMPT);
      fi;
    od;
  fi;
  return datarec.menu;
end);

#############################################################################
##
#F  IS_PQ_PROMPT( <line> ) . . . .  checks whether the line is a prompt of pq
##
##  returns `true' if the string  <line>  is  a  `pq'  prompt,  or  otherwise
##  returns `false'.
##
InstallGlobalFunction(IS_PQ_PROMPT,
  line -> IS_ALL_PQ_LINE(line) and ANUPQData.linetype = "prompt"
);

#############################################################################
##
#F  IS_ALL_PQ_LINE( <line> ) . checks whether line is a complete line from pq
##
##  returns `true' if the string <line> is a `pq' prompt or  a  request  from
##  `pq' to {\GAP} to compute stabilisers or simply ends  in  a  newline  and
##  sets `ANUPQData.linetype' to `"prompt"', `"request"'  or  `"hasnewline"',
##  accordingly; otherwise `ANUPQData.linetype' is  set  to  `"unknown"'  and
##  `false' is returned.
##
InstallGlobalFunction(IS_ALL_PQ_LINE, function( line )
local len;
  ANUPQData.linetype := "unknown";
  len := Length(line);
  if 0 < len then
    if line[len] = '\n' then
      if 4 < len  and line{[1 .. 3]} = "GAP" and line[len - 1] = '!' then
        ANUPQData.linetype := "request";
      elif 6 < len and line{[1 .. 6]} in ["Enter ", "Input "] then
        ANUPQData.linetype := "prompt";
      else
        ANUPQData.linetype := "hasnewline";
      fi;
    elif line = "Select option: " or
         1 < len and line{[len - 1 .. len]} = "? "  or
         8 < len and line{[len - 1 .. len]} = ": " and
                     line{[1 .. 6]} in ["Enter ", "Input ", "Add ne"] then
      ANUPQData.linetype := "prompt";
    fi;
  fi;
  return ANUPQData.linetype <> "unknown";
end);

#############################################################################
##
#F  PQ_READ_ALL_LINE( <iostream> ) .  read line from pq but poss. return fail
##
##  reads a complete line from <iostream> or return `fail'.
##
InstallGlobalFunction(PQ_READ_ALL_LINE, 
  iostream -> ReadAllLine(iostream, false, IS_ALL_PQ_LINE)
);

#############################################################################
##
#F  PQ_READ_NEXT_LINE( <iostream> ) . read line from pq but never return fail
##
##  Essentially, like `PQ_READ_ALL_LINE' but we know there is a complete line
##  to be got, so we wait for it, before returning.
##
InstallGlobalFunction(PQ_READ_NEXT_LINE, 
  iostream -> ReadAllLine(iostream, true, IS_ALL_PQ_LINE)
);

#############################################################################
##
#F  FLUSH_PQ_STREAM_UNTIL(<stream>,<infoLev>,<infoLevMy>,<readln>,<IsMyLine>)
##  . . .  . . . . . . . . . . . read lines from a stream until a wanted line
##
##  calls <readln> (which should be one of `ReadLine', `PQ_READ_NEXT_LINE' or
##  `PQ_READ_ALL_LINE') to read lines from a stream <stream> and `Info's each
##  line read at `InfoANUPQ' level <infoLev> until a line <line> is read  for
##  which `<IsMyLine>(<line>)' is `true'; <line> is `Info'-ed at  `InfoANUPQ'
##  level <infoLevMy> and returned. <IsMyLine>  should  be  a  boolean-valued
##  function that expects a string as its only argument,  and  <infoLev>  and
##  <infoLevMy> should be positive integers. An <infoLevMy> of 10 means  that
##  the  line  <line>  matched  by  `<IsMyLine>(<line>)'  should   never   be
##  `Info'-ed.
##
InstallGlobalFunction(FLUSH_PQ_STREAM_UNTIL, 
function(stream, infoLev, infoLevMy, readln, IsMyLine)
local line;
  line := readln(stream);
  while not IsMyLine(line) do
    Info(InfoANUPQ, infoLev, Chomp(line));
    line := readln(stream);
  od;
  if line <> fail and infoLevMy < 10 then
    Info(InfoANUPQ, infoLevMy, Chomp(line));
  fi;
  return line;
end);

#############################################################################
##
#V  PQ_ERROR_EXIT_MESSAGES . . . error messages emitted by the pq before exit
##
##  A list of the error messages the `pq' emits just before exiting.
##
InstallValue(PQ_ERROR_EXIT_MESSAGES,
  [ "Evaluation in compute_degree may cause integer overflow",
    "A relation is too long -- increase the value of MAXWORD",
    "Ran out of space during computation" ]);

#############################################################################
##
#F  FILTER_PQ_STREAM_UNTIL_PROMPT( <datarec> )
##
##  reads `pq' output from `<datarec>.stream' until a `pq' prompt and `Info's
##  any lines that are prompts, blank lines, menu exits  or  start  with  the
##  strings in the list `<datarec>.filter' (if bound) at `InfoANUPQ' level 5;
##  all  other  lines  are  either  `Info'-ed  at  `InfoANUPQ'  level  3   if
##  `datarec.nonuser' is set, or, more usually, are `Info'-ed at  `InfoANUPQ'
##  level 2 if  they  are  computation  times  or  at  `InfoANUPQ'  level  1,
##  otherwise.
##
InstallGlobalFunction(FILTER_PQ_STREAM_UNTIL_PROMPT, function( datarec )
local match, filter, lowlev, ctimelev;
  filter := ["Exiting", "pq,", "Now enter", 
             "Presentation listing images", "(use generators x1,x2"];
  if IsBound(datarec.match) then
    if datarec.match = true then
      match := ["Group:", "Group completed"];
    else
      match := [datarec.match];
    fi;
  fi;
  if IsBound(datarec.filter) then
    Append(filter, datarec.filter);
  fi;
  if ValueOption("nonuser") = true then
    lowlev := 3;
    ctimelev := 3;
  else
    ctimelev := 2;
    if not IsBound(datarec.OutputLevel) or datarec.OutputLevel = 0 then
      lowlev := 3;
    else
      lowlev := 1;
    fi;
  fi;
  repeat
    datarec.line := PQ_READ_NEXT_LINE(datarec.stream);
    if ANUPQData.linetype in ["prompt", "request"] then
      Info( InfoANUPQ, 5,        Chomp(datarec.line) );
      break;
    elif ForAny(["seconds", "Lused", "*** Final "], 
                s -> PositionSublist(datarec.line, s) <> fail) then
      Info( InfoANUPQ, ctimelev, Chomp(datarec.line) );
    elif datarec.line = "\n" or
         ForAny( filter, s -> IsMatchingSublist(datarec.line, s) ) then
      Info( InfoANUPQ, 5,        Chomp(datarec.line) );
    elif PositionSublist(datarec.line, " saved on file") <> fail then
      Info( InfoANUPQ, ctimelev, Chomp(datarec.line) );
    elif ForAny( PQ_ERROR_EXIT_MESSAGES,
                 s -> IsMatchingSublist(datarec.line, s) ) then
      Info( InfoANUPQ + InfoWarning, 1, Chomp(datarec.line) );
      Error( "pq program terminated, with error condition:\n  ", datarec.line );
    else
      Info( InfoANUPQ, lowlev,   Chomp(datarec.line) );
    fi;
    if IsBound(match) then
      if ForAny( match, s -> IsMatchingSublist(datarec.line, s) ) then
        datarec.matchedline := datarec.line;
        datarec.complete := IsBound(datarec.complete) and datarec.complete or
                            IsMatchingSublist(datarec.line, "Group completed");
      fi;
    elif IsBound(datarec.matchlist) and 
         ForAny( datarec.matchlist, 
                 s -> PositionSublist(datarec.line, s) <> fail ) then
      Add(datarec.matchedlines, datarec.line);
    fi;
  until false;
end);

#############################################################################
##
#F  ToPQk( <datarec>, <cmd>, <comment> ) . . . . . . .  writes to a pq stream
##
##  writes  <cmd>  (and  <comment>,   in   setup   file   case)   to   stream
##  `<datarec>.stream' and `Info's <cmd> and <comment> at `InfoANUPQ' level 3
##  after a ```ToPQ> ''' prompt, and returns `true' if successful and  `fail'
##  otherwise. The ``k'' at the end of the  function  name  is  mnemonic  for
##  ``keyword'' (for ``keyword'' inputs to the `pq' binary one never wants to
##  flush output).
##
InstallGlobalFunction(ToPQk, function(datarec, cmd, comment)
local ok, line, i, j, closed, fragment, sepchars, words, filterones;

  if not IsOutputTextStream(datarec.stream) and 
     IsEndOfStream(datarec.stream) then
    Error("sorry! Process stream has died!\n");
  fi;
  if cmd in ["gens", "rels"] then
    # these are done specially because of their potential to be enormously long
    if cmd = "gens" then
      line := "generators { ";
      sepchars := ", ";
    else
      line := "relators   { ";
      sepchars := "*^, ";
    fi;
    words := datarec.(cmd);
    filterones := cmd = "rels" and not IsBound(datarec.Relators) and
                  (IsFpGroup(datarec.group) or not IsPGroup(datarec.group));
    i := 1;
    while filterones and i <= Length(words) and IsOne(words[i]) do
      i := i + 1;
    od;
    if i <= Length(words) then
      Append(line, String(words[i]));
      i := i + 1;
    fi;
    ok := true;
    closed := false;
    repeat
      while filterones and i <= Length(words) and IsOne(words[i]) do
        i := i + 1;
      od;
      # i is the index of the next word to be added to line or > #words 
      if i <= Length(words) then
        # if number of non-trivial words is 0 or 1 no comma is ever added
        Append(line, ", ");
        Append(line, String(words[i]));
        i := i + 1;
      else
        Append(line, " }");
        if cmd = "rels" then
          Append(line, ";");
        fi;
        closed := true; # not quite equivalent to: i > Length(words)
      fi;
      while ok and (Length(line) >= 69 or (closed and Length(line) > 0)) do
        if Length(line) >= 69 then
          # find a nice break if we can
          j := 68;
          while j > 4 and not line[j] in sepchars do j := j - 1; od;
          # no nice break
          if j = 4 then
            j := 69;
            while j < Length(line) and not line[j] in sepchars do 
              j := j + 1;
            od;
          fi;
          fragment := line{[1 .. j]};
        else
          fragment := line;
          j := Length(line);
        fi;
        if j = Length(line) and closed then
          line := "";
        else
          line := Concatenation("  ", line{[j + 1 .. Length(line)]});
        fi;
        Info(InfoANUPQ, 4, "ToPQ> ", fragment);
        if IsBound( datarec.setupfile) then
          ok := WriteLine(datarec.stream, fragment);
        else
          ok := WriteLine(datarec.stream, fragment);
          if IsBound( ANUPQData.topqlogfile ) then
            WriteLine(ANUPQData.logstream, fragment);
          fi;
        fi;
      od;
    until closed or not ok;
  else
    # We add a null string in case <cmd> or <comment> is []
    # ... so that `Concatenation( List(., String) );' statements return strings
    Add(cmd, "");
    Add(comment, "");
    cmd     := Concatenation( List(cmd, String) );
    comment := Concatenation( List(comment, String) );
    Info(InfoANUPQ, 4, "ToPQ> ", cmd, comment);
    if IsBound( datarec.setupfile) then
      ok := WriteLine(datarec.stream, Concatenation(cmd, comment));
    else
      ok := WriteLine(datarec.stream, cmd);
      if IsBound( ANUPQData.topqlogfile ) then
        WriteLine(ANUPQData.logstream, Concatenation(cmd, comment));
      fi;
    fi;
  fi;
  if ok = fail then
    Error("write to stream failed\n");
  fi;
  return ok;
end);

#############################################################################
##
#F  ToPQ(<datarec>, <cmd>, <comment>) . .  write to pq (& for iostream flush)
##
##  calls `ToPQk' to write <cmd> (and  <comment>,  in  setup  file  case)  to
##  stream `<datarec>.stream' and `Info' <cmd> and <comment>  at  `InfoANUPQ'
##  level 3 after a ```ToPQ> ''' prompt, and then, if we are not just writing
##  a setup file (determined by  checking  whether  `<datarec>.setupfile'  is
##  bound), calls `FILTER_PQ_STREAM_UNTIL_PROMPT' to filter lines  to  `Info'
##  at the various `InfoANUPQ' levels. If we are not writing a setup file the
##  last line flushed is saved in `<datarec>.line'.
##
InstallGlobalFunction(ToPQ, function(datarec, cmd, comment)
  ToPQk(datarec, cmd, comment);
  if not IsBound( datarec.setupfile ) then
    FILTER_PQ_STREAM_UNTIL_PROMPT(datarec);
  
    while ANUPQData.linetype = "request" do
      HideGlobalVariables( "ANUPQglb", "F", "gens", "relativeOrders",
                           "ANUPQsize", "ANUPQagsize" );
      Read( Filename( ANUPQData.tmpdir, "GAP_input" ) );
      Read( Filename( ANUPQData.tmpdir, "GAP_rep" ) );
      UnhideGlobalVariables( "ANUPQglb", "F", "gens", "relativeOrders",
                             "ANUPQsize", "ANUPQagsize" );
      ToPQk( datarec, [ "pq, stabiliser is ready!" ], [] );
      FILTER_PQ_STREAM_UNTIL_PROMPT(datarec);
    od;
  fi;
end);

#############################################################################
##
#F  ToPQ_BOOL( <datarec>, <optval>, <comment> ) . . . .  pass a boolean to pq
##    
##  converts a {\GAP} boolean  <optval>  to  a  C  boolean  and  appends  the
##  appropriate adjustment to the string <comment> before calling `ToPQ'  (we
##  assume that <optval> is boolean ... `VALUE_PQ_OPTION' should already have
##  checked that).
##
InstallGlobalFunction( ToPQ_BOOL, function( datarec, optval, comment )
  if optval = true then
    ToPQ( datarec, [ 1 ], [ "  #do ", comment ] );
  else
    ToPQ( datarec, [ 0 ], [ "  #do not ", comment ] );
  fi;
end);

#############################################################################
##
#F  PqRead( <i> )  . . .  primitive read of a single line from ANUPQ iostream
#F  PqRead()
##
##  read a complete line of  {\ANUPQ}  output,  from  the  <i>th  or  default
##  interactive {\ANUPQ} process, if there is output to be read  and  returns
##  `fail' otherwise. When successful, the  line  is  returned  as  a  string
##  complete with trailing newline, colon, or question-mark character. Please
##  note that it is possible to be ``too  quick''  (i.e.~the  return  can  be
##  `fail' purely because the output from {\ANUPQ} is not there yet), but  if
##  `PqRead' finds any output at all, it waits for a complete line.  `PqRead'
##  also writes the line read via `Info' at `InfoANUPQ' level 2.  It  doesn't
##  try to distinguish banner and menu output from other output of  the  `pq'
##  binary.
##
InstallGlobalFunction(PqRead, function(arg)
local line;

  line := PQ_READ_ALL_LINE( ANUPQData.io[ PqProcessIndex(arg) ].stream );
  Info(InfoANUPQ, 2, Chomp(line));
  return line;
end);

#############################################################################
##
#F  PqReadAll( <i> ) . . . . . read all current output from an ANUPQ iostream
#F  PqReadAll()
##
##  read and return as many *complete* lines of  {\ANUPQ}  output,  from  the
##  <i>th or default interactive {\ANUPQ} process, as there are to  be  read,
##  *at the time of the call*,  as  a  list  of  strings  with  any  trailing
##  newlines removed and returns the empty list otherwise.  `PqReadAll'  also
##  writes each line read via `Info' at `InfoANUPQ' level 2. It  doesn't  try
##  to distinguish banner and menu output  from  other  output  of  the  `pq'
##  binary. Whenever `PqReadAll' finds only a partial line, it waits for  the
##  complete line, thus increasing the probability that it has  captured  all
##  the output to be had from {\ANUPQ}.
##
InstallGlobalFunction(PqReadAll, function(arg)
local lines, stream, line;

  stream := ANUPQData.io[ PqProcessIndex(arg) ].stream;
  lines := [];
  line := PQ_READ_ALL_LINE(stream);
  while line <> fail do
    line := Chomp(line);
    Info(InfoANUPQ, 2, line);
    Add(lines, line);
    line := PQ_READ_ALL_LINE(stream);
  od;
  return lines;
end);

#############################################################################
##
#F  PqReadUntil( <i>, <IsMyLine> ) .  read from ANUPQ iostream until a cond'n
#F  PqReadUntil( <IsMyLine> )
#F  PqReadUntil( <i>, <IsMyLine>, <Modify> )
#F  PqReadUntil( <IsMyLine>, <Modify> )
##
##  read complete lines  of  {\ANUPQ}  output,  from  the  <i>th  or  default
##  interactive {\ANUPQ} process, ``chomps'' them (i.e.~removes any  trailing
##  newline character), emits them to `Info' at `InfoANUPQ' level 2  (without
##  trying to distinguish banner and menu output from  other  output  of  the
##  `pq' binary), and applies the function <Modify> (where <Modify>  is  just
##  the identity map/function for the first two forms)  until  a  ``chomped''
##  line  <line>  for  which  `<IsMyLine>(  <Modify>(<line>)  )'   is   true.
##  `PqReadUntil' returns the list of <Modify>-ed ``chomped'' lines read.
##
InstallGlobalFunction(PqReadUntil, function(arg)
local idx1stfn, stream, IsMyLine, Modify, lines, line;

  idx1stfn := First([1..Length(arg)], i -> IsFunction(arg[i]));
  if idx1stfn = fail then
    Error("expected at least one function argument\n");
  elif Length(arg) > idx1stfn + 1 then
    Error("expected 1 or 2 function arguments, not ", 
          Length(arg) - idx1stfn + 1, "\n");
  elif idx1stfn > 2  then
    Error("expected 0 or 1 integer arguments, not ", idx1stfn - 1, "\n");
  else
    stream := ANUPQData.io[ ANUPQ_IOINDEX(arg{[1..idx1stfn - 1]}) ].stream;
    IsMyLine := arg[idx1stfn];
    if idx1stfn = Length(arg) then
      Modify := line -> line; # The identity function
    else
      Modify := arg[Length(arg)];
    fi;
    lines := [];
    repeat
      line := Chomp( PQ_READ_NEXT_LINE(stream) );
      Info(InfoANUPQ, 2, line);
      line := Modify(line);
      Add(lines, line);
    until IsMyLine(line);
    return lines;
  fi;
end);

#############################################################################
##
#F  PqWrite( <i>, <string> ) . . . . . . .  primitive write to ANUPQ iostream
#F  PqWrite( <string> )
##
##  write <string> to the <i>th  or  default  interactive  {\ANUPQ}  process;
##  <string> must be in exactly the form the {\ANUPQ} standalone expects. The
##  command is echoed via `Info' at `InfoANUPQ' level 3 (with a  ```ToPQ> '''
##  prompt); i.e.~do `SetInfoLevel(InfoANUPQ, 3);' to see what is transmitted
##  to the `pq' binary. `PqWrite' returns `true' if successful in writing  to
##  the stream of the interactive {\ANUPQ} process, and `fail' otherwise.
##
InstallGlobalFunction(PqWrite, function(arg)
local ioIndex, line;

  if Length(arg) in [1, 2] then
    ioIndex := ANUPQ_IOINDEX(arg{[1..Length(arg) - 1]});
    return ToPQk( ANUPQData.io[ioIndex], arg{[Length(arg)..Length(arg)]}, [] );
  else
    Error("expected 1 or 2 arguments ... not ", Length(arg), " arguments\n");
  fi;
end);

#############################################################################
##
#F  ANUPQ_ARG_CHK( <funcname>, <args> ) . . . . check args of int/non-int fns
##
##  checks the argument list <args> for a function that has both  interactive
##  and non-interactive versions, where <funcname> is the generic name of the
##  function. If <args> has length more than 1 then it contains  options  for
##  the function that have been passed in one of the {\GAP} 3-compatible ways
##  only available non-interactively. `ANUPQ_ARG_CHK' returns <datarec> which
##  is   either   `ANUPQData.ni'   in    the    non-interactive    case    or
##  `ANUPQData.io[<i>]' for some <i> in the interactive case,  after  setting
##  <datarec>.calltype' to one  of  `"interactive"',  `"non-interactive"'  or
##  `"GAP3compatible"'.
##
InstallGlobalFunction(ANUPQ_ARG_CHK, function(funcname, args)
local ioIndex, datarec, optrec, optnames;
  PQ_OTHER_OPTS_CHK( funcname, IsEmpty(args) or IsPosInt( args[1] ) );
  if IsEmpty(args) or IsPosInt( args[1] ) then
    datarec := ANUPQData.io[ CallFuncList( PqProcessIndex, args ) ];
    datarec.outfname := ANUPQData.outfile; # not always needed
    #datarec.calltype := "interactive";    # PqStart sets this
    if not IsBound(datarec.group) then
      Error( "huh! Interactive process has no group\n" );
    elif IsMatchingSublist(funcname, "PqDescendants") then
      if not IsPcGroup( datarec.group ) then
        Error( "group of process must be a pc group\n" );
      fi;
    else # Check for Prime, ClassBound if nec.
      PQ_OPTION_CHECK( funcname, datarec );
    fi;
  elif 1 = Length(args) then
    if not IsPcGroup( args[1] ) then
      if IsMatchingSublist(funcname, "PqDescendants") then
        Error( "first argument <args[1]> must be a pc group\n" );
      elif not IsFpGroup( args[1] ) then
        Error( "first argument <args[1]> must be a pc group or an fp group\n" );
      fi;
    fi;
    ANUPQData.ni := PQ_START( VALUE_PQ_OPTION( "PqWorkspace", 10000000 ),
                              VALUE_PQ_OPTION( "SetupFile" ) );
    datarec := ANUPQData.ni;
    datarec.group := args[1];
    datarec.calltype := "non-interactive";
    datarec.procId := 0;
    PQ_OPTION_CHECK( funcname, datarec ); # Check for Prime, ClassBound if nec.
    if IsBound( datarec.setupfile ) then
      datarec.outfname := "PQ_OUTPUT";
    else
      datarec.outfname := ANUPQData.outfile; # not always needed
    fi;
  else
    # GAP 3 way of passing options is supported in non-interactive use
    if funcname = "PqDescendantsTreeCoclassOne" then
      Error("GAP 3-compatible ways of passing options not supported");
    elif IsRecord(args[2]) then
      optrec := ShallowCopy(args[2]);
      optnames := Set( REC_NAMES(optrec) );
      SubtractSet( optnames, Set( ANUPQoptions.(funcname) ) );
      if not IsEmpty(optnames) then
        Error(ANUPQoptError( funcname, optnames ), "\n");
      fi;
    else
      optrec := ANUPQextractOptions(funcname, args{[2 .. Length(args)]});
    fi;
    PushOptions(optrec);
    PQ_FUNCTION.(funcname)( args{[1]} );
    PopOptions();
    datarec := ANUPQData.ni;
    datarec.calltype := "GAP3compatible";
    datarec.procId := 0;
  fi;
  return datarec;
end );

#############################################################################
##
#F  PQ_COMPLETE_NONINTERACTIVE_FUNC_CALL( <datarec> )
##
##  writes the final commands to the `pq' setup file so that the `pq'  binary
##  makes a clean exit, or just closes the stream to kill the `pq' process.
##
InstallGlobalFunction(PQ_COMPLETE_NONINTERACTIVE_FUNC_CALL, function(datarec)
  if IsBound( datarec.setupfile ) then
    PQ_MENU(datarec, "SP");
    ToPQk(datarec, [ 0 ], [ "  #exit program" ]);
  fi;
  CloseStream(datarec.stream);

  if IsBound( datarec.setupfile ) then
    Info(InfoANUPQ, 1, "Input file: '", datarec.setupfile, "' written.");
    Info(InfoANUPQ, 1, "Run `pq' with '", datarec.opts, "' flags.");
    Info(InfoANUPQ, 1, "The result will be saved in: '", 
                       datarec.outfname, "'.");
  fi;
end );

#############################################################################
##
#F  ToPQLog([<filename>]) . . . . . . log or stop logging pq commands to file
##
##  With string argument <filename>,  `ToPQLog'  opens  the  file  with  name
##  <filename> for logging; all commands written to the `pq' binary (that are
##  `Info'-ed behind a ```ToPQ> ''' prompt at `InfoANUPQ' level 4)  are  then
##  also written to that  file  (but  without  prompts).  With  no  argument,
##  `ToPQLog' stops logging to whatever file was being logged to. If  a  file
##  was already being logged to, that file is closed and the file  with  name
##  <filename> is opened for logging.
##
InstallGlobalFunction(ToPQLog, function(arg)
  if not( IsEmpty(arg) or IsString( arg[1] ) ) then
    Error( "expected no arguments or one string argument\n" );
  fi;
  if IsBound(ANUPQData.topqlogfile) then
    CloseStream(ANUPQData.logstream);
    PQ_UNBIND(ANUPQData, ["topqlogfile", "logstream"]);
  elif IsEmpty(arg) then
    Info(InfoANUPQ + InfoWarning, 1, "No file currently being logged to.");
    return;
  fi;
  if not( IsEmpty(arg) ) and IsString(arg[1]) then
    ANUPQData.topqlogfile := arg[1];
    ANUPQData.logstream := OutputTextFile(ANUPQData.topqlogfile, false);
  fi;
end);

#E  anupqios.gi . . . . . . . . . . . . . . . . . . . . . . . . . . ends here 
