#############################################################################
##
#W  test.gi              GAP 4 package `atlasrep'               Thomas Breuer
##
#H  @(#)$Id: test.gi,v 1.64 2004/03/23 09:33:46 gap Exp $
##
#Y  Copyright (C)  2001,  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
##
##  This file contains functions to test the data available in the
##  {\ATLAS} of Group Representations.
##
Revision.( "atlasrep/gap/test_gi" ) :=
    "@(#)$Id: test.gi,v 1.64 2004/03/23 09:33:46 gap Exp $";


#############################################################################
##
#F  AtlasOfGroupRepresentationsTestWords( <tocid> )
##
InstallGlobalFunction( AtlasOfGroupRepresentationsTestWords,
    function( tocid )
    local result, types, toc, name, r, type, entry;

    # Initialize the result.
    result:= true;

    # Check only straight line programs.
    types:= AGRDataTypes( "slp" );

    if tocid = "local" then
      if ReloadAtlasTableOfContents( "local" ) = fail then
        # No test is reasonable.
        return true;
      fi;
    fi;

    toc:= AtlasTableOfContents( tocid );
    if toc = fail then
      Print( "#E  omitting `AtlasOfGroupRepresentationsTestWords'\n" );
      return false;
    fi;

    for name in AtlasOfGroupRepresentationsInfo.GAPnames do
      if IsBound( toc.TableOfContents.( name[2] ) ) then
        r:= toc.TableOfContents.( name[2] );

        # Note that the ordering in the `and' statement must not be
        # changed, in order to execute all tests!
        for type in types do
          if IsBound( r.( type[1] ) ) then
            for entry in r.( type[1] ) do
              result:= type[2].TestWords( tocid, name[2],
                                   entry[ Length( entry ) ] ) and result;
            od;
          fi;
        od;

      fi;
    od;

    # Return the result.
    return result;
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsTestFileHeaders( [<tocid>[,<groupname>]] )
##
InstallGlobalFunction( AtlasOfGroupRepresentationsTestFileHeaders,
    function( arg )
    local result, toc, record, type, entry, test, triple;

    # Initialize the result.
    result:= true;

    if Length( arg ) = 2 then

      toc:= AtlasTableOfContents( arg[1] );
      if toc = fail then
        # No test is reasonable.
        return true;
      fi;
      toc:= toc.TableOfContents;
      if IsBound( toc.( arg[2] ) ) then
        record:= toc.( arg[2] );
        for type in AGRDataTypes( "rep" ) do
          if IsBound( record.( type[1] ) ) then
            for entry in record.( type[1] ) do
              test:= type[2].TestFileHeaders( arg[1], arg[2], entry );
              if not IsBool( test ) then
                Print( "#E  ", test, " for ", entry[ Length( entry ) ],
                       "\n" );
                test:= false;
              fi;
              result:= test and result;
            od;
          fi;
        od;
      fi;

    elif Length( arg ) = 1 then

      for triple in AtlasOfGroupRepresentationsInfo.groupnames do
        result:= AtlasOfGroupRepresentationsTestFileHeaders( arg[1],
                     triple[3] ) and result;
      od;

    elif Length( arg ) = 0 then
      result:= AtlasOfGroupRepresentationsTestFileHeaders( "local" );
    fi;

    # Return the result.
    return result;
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsTestStandardization()
#F  AtlasOfGroupRepresentationsTestStandardization( <gapname> )
##
InstallGlobalFunction( AtlasOfGroupRepresentationsTestStandardization,
    function( arg )
    local result, name, gapname, groupname, toc, orders, nr, gens, std, ords;

    # Initialize the result.
    result:= true;

    if Length( arg ) = 0 then

      for name in AtlasOfGroupRepresentationsInfo.GAPnames do
        result:= AtlasOfGroupRepresentationsTestStandardization( name[1] )
                     and result;
      od;

    elif Length( arg ) = 1 and IsString( arg[1] ) then

      gapname:= arg[1];
      groupname:= First( AtlasOfGroupRepresentationsInfo.GAPnames,
                         pair -> pair[1] = gapname );
      if groupname = fail then
        Print( "#E  AtlasOfGroupRepresentationsTestStandardization:\n",
               "#E  no group with GAP name `", gapname, "'\n" );
        return false;
      fi;

      toc:= AtlasTableOfContents( "local" ).TableOfContents;

      orders:= [];

      # Loop over the relevant representations.
      nr:= 1;
      gens:= AtlasGenerators( gapname, nr );
      while gens <> fail do
        std:= gens.standardization;
        if ForAll( gens.generators, x -> Inverse( x ) <> fail ) then
          ords:= List( gens.generators, Order );
        else
          ords:= [ fail ];
        fi;
        if not ForAll( ords, IsInt ) then
          Print( "#E  representation `", gens.identifier,
                 "': non-finite order\n" );
          result:= false;
        elif IsBound( orders[ std ] ) then
          if orders[ std ] <> ords then
            Print( "#E  ", Ordinal( nr ), " representation of `", gapname,
                   "': incompatible orders ", ords, " and ", orders[ std ],
                   "\n" );
            result:= false;
          fi;
        else
          orders[ std ]:= ords;
        fi;
        nr:= nr + 1;
        gens:= AtlasGenerators( gapname, nr );
      od;

    fi;

    # Return the result.
    return result;
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsMatalgInfo()
##
InstallGlobalFunction( AtlasOfGroupRepresentationsMatalgInfo, function()
    local toc, groupnames, filenames, groupname, datarec, fieldinfo, entry,
          pos, i, file, str;

    toc:= AtlasTableOfContents( "remote" );
    groupnames:= List( toc.groupnames, x -> x[3] );
    toc:= toc.TableOfContents;

    # List all files of the type 'matalg'.
    filenames:= [];
    for groupname in groupnames do
      datarec:= toc.( groupname );
      if IsBound( datarec.matalg ) then
        Append( filenames, List( datarec.matalg, x -> x[5] ) );
      fi;
      if IsBound( datarec.quat ) then
        Append( filenames, List( datarec.quat, x -> x[5] ) );
      fi;
    od;

    # Compare this list with the stored field info.
    # Prefer the stored description to the computed one.
    fieldinfo:= [];
    for entry in AtlasOfGroupRepresentationsInfo.fieldinfo do
      pos:= Position( filenames, Concatenation( entry[1], ".g" ) );
      if pos = fail then
        Info( InfoAtlasRep, 3,
              "`AtlasOfGroupRepresentationsMatalgInfo':\n",
              "#E  field info stored for `", entry[1],
              "' but representation not available" );
      else
        fieldinfo[ pos ]:= entry{ [ 1, 2 ] };
      fi;
    od;

    # Compute the missing descriptions.
    for i in [ 1 .. Length( filenames ) ] do
      if not IsBound( fieldinfo[i] ) then
        file:= Filename( DirectoriesPackageLibrary( "atlasrep", "datagens" ),
                         filenames[i] );
        if file = fail then
          Info( InfoAtlasRep, 3,
                "`AtlasOfGroupRepresentationsMatalgInfo':\n",
                "#I  file `", filenames[i],
                "' not available, fieldinfo missing" );
        else
          fieldinfo[i]:= AtlasStringOfFieldOfMatrixEntries( file )[2];
          Info( InfoAtlasRep, 3,
                "`AtlasOfGroupRepresentationsMatalgInfo':\n",
                "#I  new description `", fieldinfo[ pos ], "' added" );
        fi;
      fi;
    od;

    # Compose the result string.
    str:= "";
    for entry in Set( fieldinfo ) do
      Append( str, Concatenation( "AGRFLD(\"", entry[1], "\",\"", entry[2],
                                  "\");\n" ) );
    od;

    # Return the result.
    return str;
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsTestTableOfContentsContainment()
##
InstallGlobalFunction(
    AtlasOfGroupRepresentationsTestTableOfContentsContainment, function()
    local toc, badnames, lnames, rnames, name, groupname, entry, list;

    if ReloadAtlasTableOfContents( "local", true ) = fail then
      # No test is reasonable.
      return [];
    fi;
    toc:= AtlasOfGroupRepresentationsInfo.TableOfContents;
    if not ( IsBound( toc.( "local" ) ) and IsBound( toc.remote ) ) then
      Print( "#I  omitting ",
          "`AtlasOfGroupRepresentationsTestTableOfContentsContainment'\n" );
      return false;
    fi;
    badnames:= [];

    # Check whether there are ``illegal'' group names.
    lnames:= RecNames( toc.( "local" ) );
    rnames:= RecNames( toc.remote );
    for name in Difference( lnames, rnames ) do
      if not name in [ "data", "otherfiles", "lastupdated" ] then
        Add( badnames, Concatenation( name, "*" ) );
        Print( "#I  remove all local files `", name, "*'\n" );
      fi;
    od;

    # Loop over the ``legal'' group names.
    for groupname in Intersection( lnames, rnames ) do
      if not groupname in [ "data", "otherfiles", "lastupdated" ] then

        # Check whether there are ``illegal'' components for this group.
        lnames:= RecNames( toc.( "local" ).( groupname ) );
        rnames:= RecNames( toc.remote.( groupname ) );
        for name in Difference( lnames, rnames ) do
          if not IsEmpty( toc.( "local" ).( groupname ).( name ) ) then
            for entry in toc.( "local" ).( groupname ).( name ) do
              if IsString( entry[ Length( entry ) ] ) then
                Add( badnames, entry[ Length( entry ) ] );
              else
                Append( badnames, entry[ Length( entry ) ] );
              fi;
            od;
            Print( "#I  remove all local files for group `", groupname,
                   "' corresp. to the component `", name, "'\n" );
          fi;
        od;

        # Check the files for the ``legal'' components.
        for name in Intersection( lnames, rnames ) do
          for entry in Difference( toc.( "local" ).( groupname ).( name ),
                                   toc.remote.( groupname ).( name ) ) do
            if IsString( entry[ Length( entry ) ] ) then
              Add( badnames, entry[ Length( entry ) ] );
            else
              Append( badnames, entry[ Length( entry ) ] );
            fi;
            Print( "#I  remove the local file(s) given by `",
                   entry[ Length( entry ) ], "'\n" );
          od;
        od;

      fi;
    od;

    # Return the result.
    return badnames;
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsCompareListings( <left>, <right> )
##
##  <left> and <right> must be two lists of length two each,
##  the entry at the first position being a list of file names and the entry
##  at the second position being the corresponding list of strings denoting
##  the time of the last change.
##  `AtlasOfGroupRepresentationsCompareListings' returns a list
##  $[ l1, l2, l3 ]$ such that $l_i$ is a list of triples
##  `[ <name>, <ldate>, <rdate> ]' where <name> is a name in <left>[1] and
##  <ldate> and <rdate> are the corresponding dates in `<left>[2]' and
##  `<right>[2]', respectively.
##
##  In $l1$, all names <name> occur that are contained in `<left>[1]' but not
##  in `<right>[1]'.
##  In $l2$, all names <name> occur that are contained in both `<left>[1]'
##  and `<right>[1]', and whose date in `<left>[2]' is older than the date in
##  `<right>[2]'.
##  In $l3$, all names <name> occur that are contained in both `<left>[1]'
##  and `<right>[1]', and whose date in `<left>[2]' is newer than or equalt
##  to the date in `<right>[2]'.
##
BindGlobal( "AtlasOfGroupRepresentationsCompareListings",
    function( left, right )
    local isless, lnames, ldata, rnames, rdata, len, updates, i, name, pos;

    # This local function compares date info lists that are obtained
    # by splitting output lines of `ls -l'.
    # More precisely, `date1' and `date2' are assumed to be of the form
    # `[ "-rw-r--r--", "1", "sam", "sam", "4749", "Jan", "19", "19:06" ]' or
    # `[ "-rw-r--r--", "1", "sam", "sam", "75873", "Aug", "19", "2000" ]'.
    # (The second version contains the year instead of the time,
    # this case is assumed to occur exactly if the file in question is
    # at least 6 months old.)
    # The return value is `true' if `date1' describes an earlier time than
    # `date2', and `false' otherwise.
    isless:= function( date1, date2 )
      local months, str, currmonth, pos;

      # If one of the dates shows a year and the other a time,
      # or if both dates show different years then the relation is clear.
      if   ':' in date1[8] and not ':' in date2[8] then
        return false;
      elif not ':' in date1[8] and ':' in date2[8] then
        return true;
      elif not ':' in date1[8] and not ':' in date2[8]
           and date1[8] <> date2[8] then
        return Int( date1[8] ) < Int( date2[8] );
      fi;

      # For equal years or two dates with time, compare the months.
      months:= [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
      if ':' in date1[8] and ':' in date2[8] then
        # (If the two dates contain a time component then we order the months
        # w.r.t. the current month.)
        str:= CurrentDateTimeString( [] );
        if str <> "unknown" then
          str:= SplitString( str, " ", "" );
          currmonth:= str[2];
          pos:= Position( months, currmonth );
          months:= Concatenation( months{ [ pos+1 .. 12 ] },
                                  months{ [ 1 .. pos ] } );
        fi;
      fi;
      if Position( months, date1[6] ) <> Position( months, date2[6] ) then
        return Position( months, date1[6] ) < Position( months, date2[6] );
      fi;

      # For equal years and months, compare the days.
      if Int( date1[7] ) <> Int( date2[7] ) then
        return Int( date1[7] ) < Int( date2[7] );
      fi;

      # For equal years, months, and days, compare the times.
      if ':' in date1[8] then
        return    date1[8]{ [ 1, 2 ] } < date2[8]{ [ 1, 2 ] }
               or ( date1[8]{ [ 1, 2 ] } = date2[8]{ [ 1, 2 ] } and
                    date1[8]{ [ 3, 4 ] } < date2[8]{ [ 3, 4 ] } );
      fi;

      # Year, month, and date are equal.
      return false;
    end;

    lnames := left[1];
    ldata  := left[2];
    rnames := right[1];
    rdata  := right[2];
    len:= Length( lnames );
    updates:= [ [], [], [] ];
    for i in [ 1 .. len ] do
      name:= lnames[i];
      pos:= Position( rnames, name );
      if pos = fail then
        Add( updates[1], [ name, JoinStringsWithSeparator( ldata[i], " " ),
                                 "--" ] );
      elif isless( ldata[i], rdata[ pos ] ) then
        Add( updates[2], [ name,
                           JoinStringsWithSeparator( ldata[i], " " ),
                           JoinStringsWithSeparator( rdata[ pos ], " " ) ] );
      else
        Add( updates[3], [ name,
                           JoinStringsWithSeparator( ldata[i], " " ),
                           JoinStringsWithSeparator( rdata[ pos ], " " ) ] );
      fi;
    od;
    return updates;
    end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsTestTableOfContentsRemoteDiff()
##
BindGlobal(
    "AtlasOfGroupRepresentationsTestTableOfContentsRemoteDiff", function()
    local toc, newtoc, string, comp, old, admincomp, updates, name;

    # If not yet available then read the local and remote table of contents,
    # with `ls -l' info.
    toc:= AtlasOfGroupRepresentationsInfo.TableOfContents;
    newtoc:= rec();
    for string in [ "local", "remote" ] do

      if not IsBound( toc.( string ) ) then
        comp:= AtlasTableOfContents( string, true );
        if comp <> fail then
          newtoc.( string ):= comp.TableOfContents;
        fi;
      elif not IsBound( toc.( string ).data ) then
        old:= toc.( string );
        Unbind( toc.( string ) );
        comp:= AtlasTableOfContents( string, true );
        if comp <> fail then
          newtoc.( string ):= comp.TableOfContents;
        fi;
        toc.( string ):= old;
      else
        newtoc.( string ):= toc.( string );
      fi;

      # If the local data directories are empty then nothing is to check.
      admincomp:= [ "data", "lastupdated", "otherfiles" ];
      if     string = "local"
         and IsSubset( admincomp, RecNames( newtoc.( string ) ) ) then
        return [];
      fi;

    od;

    # Check that this was successful.
    if not IsBound( newtoc.( "local" ) ) or not IsBound( newtoc.remote ) then
      Print( "#I  could not load the two tables of contents\n" );
      return [];
    fi;

    # Check that the creation dates of the local files are not older than
    # the creation dates of the corresponding remote files.
    updates:= AtlasOfGroupRepresentationsCompareListings(
                  newtoc.( "local" ).data, newtoc.( "remote" ).data );
    for name in updates[2] do
      Print( "#I  update local file `", name[1], "'\n",
             "#I  (local date is ", name[2], ",\n",
             "#I  server date is ", name[3], ")\n" );
    od;

    # Return the result.
    return updates[2];
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsTestTableOfContentsRemoteUpdates()
##
InstallGlobalFunction(
    AtlasOfGroupRepresentationsTestTableOfContentsRemoteUpdates, function()
    local updates;

    updates:= AtlasOfGroupRepresentationsTestTableOfContentsRemoteDiff();
    return List( updates, triple -> triple[1] );
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsToDoInMakeTOC( <flags> )
##
InstallGlobalFunction( AtlasOfGroupRepresentationsToDoInMakeTOC,
    function( flags )
    local test, dirs, tocfile, archfile, intermed, toc, pos, data, bad,
          mv, diff, str, out;

    if "-v" in flags then
      SetInfoLevel( InfoAtlasRep, 3 );
    fi;
    test:= ( "-test" in flags );

    # The filenames of the current table of contents, of the archive file,
    # and of the intermediate file.
    dirs:= DirectoriesPackageLibrary( "atlasrep", "gap" );
    tocfile  := Filename( dirs, "atlasprm.g"   );
    archfile := Filename( dirs[1], "atlasprm.old" );
    intermed := Filename( dirs[1], "atlasprm.new" );

    # Copy the header of the old file with the remote table of contents.
    toc:= StringFile( tocfile );
    pos:= PositionSublist( toc, "do not edit" );
    pos:= Position( toc, '\n', pos );
    PrintTo( intermed, toc{ [ 1 .. pos ] }, "##\n\n" );

    # Create the current remote table of contents, using the server data.
    ReloadAtlasTableOfContents( "remote", test );
    if test then
      data:= AtlasOfGroupRepresentationsInfo.TableOfContents.remote.data;
    fi;

    # Add the info about the end of the file.
    AppendTo( intermed,
        StringOfAtlasTableOfContents( "remote" ),
        "##################################################################",
        "###########\n##\n#E\n\n" );

    # Remove the information we are going to add by reading the file.
    AtlasOfGroupRepresentationsInfo.GAPnames := [];
    AtlasOfGroupRepresentationsInfo.groupnames := [];
    AtlasOfGroupRepresentationsInfo.fieldinfo := [];
    AtlasOfGroupRepresentationsInfo.private := [];
    AtlasOfGroupRepresentationsInfo.TableOfContents.remote := rec();

    # Replace the old version by the new one.
    Reread( intermed );

    # Perform the tests.
    bad:= [];
    if test then
      AtlasOfGroupRepresentationsInfo.TableOfContents.remote.data:= data;
      bad[1]:= AtlasOfGroupRepresentationsTestTableOfContentsContainment();
      bad[2]:= AtlasOfGroupRepresentationsTestTableOfContentsRemoteDiff();
    fi;

    # Save the old version of the file, and replace it by the new version.
    mv:= Filename( DirectoriesSystemPrograms(), "mv" );
    diff:= Filename( DirectoriesSystemPrograms(), "diff" );

    Process( DirectoryCurrent(), mv, InputTextNone(), OutputTextNone(),
             [ tocfile, archfile ] );
    Process( DirectoryCurrent(), mv, InputTextNone(), OutputTextNone(),
             [ intermed, tocfile ] );

    # Print the differences between old and new version.
    str:= "";
    out:= OutputTextString( str, true );
    Process( DirectoryCurrent(), diff, InputTextNone(), out,
             [ archfile, tocfile ] );
    CloseStream( out );
    Info( InfoAtlasRep, 2,
          str );

    return bad;
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsTestFiles( [<tocid>[, <groupname>]] )
##
InstallGlobalFunction( AtlasOfGroupRepresentationsTestFiles, function( arg )
    local result, toc, record, type, entry, triple;

    # Initialize the result.
    result:= true;

    if Length( arg ) = 2 then
      toc:= AtlasTableOfContents( arg[1] );
      if toc = fail then
        return false;
      fi;
      toc:= toc.TableOfContents;
      if IsBound( toc.( arg[2] ) ) then
        record:= toc.( arg[2] );
        for type in AGRDataTypes( "rep" ) do
          if IsBound( record.( type[1] ) ) then
            for entry in record.( type[1] ) do
              result:= type[2].TestFiles( arg[1], arg[2], entry ) and result;
            od;
          fi;
        od;
      fi;
    elif Length( arg ) = 1 then
      for triple in AtlasOfGroupRepresentationsInfo.groupnames do
        result:= AtlasOfGroupRepresentationsTestFiles( arg[1], triple[3] )
                 and result;
      od;
    elif IsEmpty( arg ) then
      result:= AtlasOfGroupRepresentationsTestFiles( "local" ) and result;
    fi;

    # Return the result.
    return result;
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsTestClassScripts()
#F  AtlasOfGroupRepresentationsTestClassScripts( <groupname> )
##
InstallGlobalFunction( AtlasOfGroupRepresentationsTestClassScripts,
    function( arg )
    local badnames, result, groupname, gapname, toc, record, std, name,
          prg, tbl, outputs, ident, classnames, map, gens, roots, grp, reps,
          orders1, orders2, cents1, cents2, triple, pos, pos2, cycscript;

    # The following groups are (currently) too large for checking
    # centralizer orders in a concrete representation.
    badnames:= [ "B", "F24", "HN", "J4", "Ly", "M", "ON", "Th", "L37d2" ];

    # Initialize the result.
    result:= true;

    if Length( arg ) = 1 and IsString( arg[1] ) then

      groupname:= arg[1];
      gapname:= First( AtlasOfGroupRepresentationsInfo.GAPnames,
                       pair -> pair[2] = groupname );
      if gapname = fail then
        Print( "#E  no group with name `", groupname, "'\n" );
        return false;
      fi;
      gapname:= gapname[1];
      toc:= AtlasTableOfContents( "local" );
      if toc = fail then
        return false;
      fi;
      toc:= toc.TableOfContents;
#T admit also private tables of contents!
      if IsBound( toc.( groupname ) ) then
        record:= toc.( groupname );
        for name in [ "cyclic", "classes", "cyc2ccl" ] do
          if IsBound( record.( name ) ) then
            for std in Set( List( record.( name ), x -> x[1] ) ) do

              prg:= AtlasStraightLineProgram( gapname, std, name );
              if prg = fail then
                Print( "#E  inconsistent program `", name, "' for `",
                       gapname, "'\n" );
                result:= false;
              else

                # Fetch the character table of the group.
                # (No further tests are possible if it is not available.)
                tbl:= CharacterTable( gapname );
                if tbl <> fail then

                  ident:= prg.identifier[2];
                  classnames:= AtlasClassNames( tbl );
                  if IsBound( prg.outputs ) then
                    outputs:= prg.outputs;
                    map:= List( outputs, x -> Position( classnames, x ) );
                  else
                    Print( "#E  no component `outputs' in `", name,
                           "' for `", gapname, "'\n" );
                    result:= false;
                    outputs:= [ "-" ];
                    map:= [ fail ];
                  fi;
                  prg:= prg.program;

                  # (If `-' signs occur then we cannot test the names,
                  # but the number of outputs can be checked.)
                  roots:= ClassRoots( tbl );
                  roots:= Filtered( [ 1 .. Length( roots ) ],
                                    i -> IsEmpty( roots[i] ) );
                  roots:= Set( List( roots, x -> ClassOrbit( tbl, x ) ) );

                  if ForAll( outputs, x -> not '-' in x ) then

                    # Check the class names.
                    if fail in map then
                      Print( "#E  strange class names ",
                             Difference( outputs, classnames ),
                             " for `dataword/", ident, "'\n" );
                      result:= false;
                    fi;
                    if     name in [ "classes", "cyc2ccl" ]
                       and Set( classnames ) <> Set( outputs ) then
                      Print( "#E  class names ",
                             Difference( classnames, outputs ),
                             " not hit for `dataword/", ident, "'\n" );
                      result:= false;
                    fi;
                    if name = "cyclic" then
                      # Check whether all maximally cyclic subgroups
                      # are covered.
                      roots:= Filtered( roots,
                                 list -> IsEmpty( Intersection( outputs,
                                             classnames{ list } ) ) );
                      if not IsEmpty( roots ) then
                        Print( "#E  maximally cyclic subgroups ",
                               List( roots, x -> classnames{ x } ),
                               " not hit for `dataword/", ident, "'\n" );
                        result:= false;
                      fi;
                    fi;

                  elif name = "cyclic" and
                       Length( outputs ) <> Length( roots ) then
                    Print( "#E  no. of outputs and cyclic subgroups differ",
                           " for `dataword/", ident, "'\n" );
                  fi;

                  if not ( fail in map or groupname in badnames ) then
#T replace badnames by the check for a suff. small permutation representation!

                    # Compute the representatives in a representation.
                    # (No further tests are possible if none is available.)
                    gens:= OneAtlasGeneratingSetInfo( gapname, std,
                                                      IsPermGroup, true );
                    if gens <> fail then

                      gens:= AtlasGenerators( gens.identifier ).generators;
                      if fail in gens then
                        gens:= fail;
                      fi;

                      if not name in [ "cyclic", "classes" ] then

                        # The input consists of the images of the standard
                        # generators under the `cyc' script.
                        pos:= Position( ident, '-' ) - 1;
                        pos2:= pos;
                        while ident[ pos2 ] <> 'W' do
                          pos2:= pos2 - 1;
                        od;
                        cycscript:= Concatenation( groupname, "G",
                                        String( std ), "-cycW",
                                        ident{ [ pos2+1 .. pos ] } );
                        cycscript:= AtlasStraightLineProgram(
                            [ gapname, cycscript, std ] );
                        if cycscript = fail then
                          gens:= fail;
                          Print( "#E  no script `", cycscript,
                                 "' available\n" );
                          result:= false;
                        else
                          gens:= ResultOfStraightLineProgram(
                                     cycscript.program, gens );
                        fi;
                      fi;

                    fi;

                    if gens <> fail then

                      grp:= Group( gens );
                      reps:= ResultOfStraightLineProgram( prg, gens );

                      if Length( reps ) <> Length( outputs ) then

                        Print( "#E  inconsistent output numbers for ",
                               "`dataword/", ident, "'\n" );
                        result:= false;

                      else

                        # Check element orders and centralizer orders.
                        orders1:= OrdersClassRepresentatives( tbl ){ map };
                        orders2:= List( reps, Order );
                        if orders1 <> orders2 then
                          Print( "#E  element orders of ",
                              outputs{ Filtered( [ 1 .. Length( outputs ) ],
                                           i -> orders1[i] <> orders2[i] ) },
                              " differ for `dataword/", ident, "'\n" );
                          result:= false;
                        fi;
                        cents1:= SizesCentralizers( tbl ){ map };
                        cents2:= List( reps, x -> Size( Centralizer(grp,x) ) );
                        if    cents1 <> cents2 then
                          Print( "#E  centralizer orders of ",
                              outputs{ Filtered( [ 1 .. Length( outputs ) ],
                                           i -> cents1[i] <> cents2[i] ) },
                              " differ for `dataword/", ident, "'\n" );
                          result:= false;
                        fi;

                      fi;

                    fi;

                  fi;

                fi;
              fi;

            od;
          fi;
        od;
      fi;

    elif IsEmpty( arg ) then
      for triple in AtlasOfGroupRepresentationsInfo.groupnames do
        result:= AtlasOfGroupRepresentationsTestClassScripts( triple[3] )
                 and result;
      od;
    fi;

    # Return the result.
    return result;
end );


#############################################################################
##
#F  AtlasOfGroupRepresentationsTestCycToCcls()
#F  AtlasOfGroupRepresentationsTestCycToCcls( <groupname> )
##
InstallGlobalFunction( AtlasOfGroupRepresentationsTestCycToCcls,
    function( arg )
    local result, groupname, gapname, toc, tbl, record, datadirs, entry,
          tomatch, cyc2ccl, str, prg, triple;

    # Initialize the result.
    result:= true;

    if Length( arg ) = 1 and IsString( arg[1] ) then

      groupname:= arg[1];
      gapname:= First( AtlasOfGroupRepresentationsInfo.GAPnames,
                       pair -> pair[2] = groupname );
      if gapname = fail then
        Print( "#E  no group with name `", groupname, "'\n" );
        return false;
      fi;
      gapname:= gapname[1];
      toc:= AtlasTableOfContents( "local" );
      if toc = fail then
        return false;
      fi;
      toc:= toc.TableOfContents;

      # Fetch the character table of the group.
      # (No test is possible if it is not available.)
      tbl:= CharacterTable( gapname );
      if   tbl = fail then
        Print( "#I  no character table of `", gapname, "' is available\n" );
        return true;
      elif not IsBound( toc.( groupname ) ) then
        return true;
      fi;

      record:= toc.( groupname );
      if IsBound( record.cyclic ) then
        if IsBound( record.cyc2ccl ) then
          cyc2ccl:= List( record.cyc2ccl, x -> SplitString( x[2], "-" ) );
        else
          cyc2ccl:= [];
        fi;

        datadirs:= DirectoriesPackageLibrary( "atlasrep", "dataword" );

        for entry in record.cyclic do

          # Check the `cyc2ccl' scripts available.
          tomatch:= Filtered( entry[2], x -> x <> '-' );
          cyc2ccl:= Filtered( cyc2ccl, x -> x[1] = tomatch );
          if IsEmpty( cyc2ccl ) then

            # There is no `cyc2ccl' script but perhaps we can create it.
            str:= StringOfAtlasProgramCycToCcls( Filename( datadirs, entry[2] ),
                     tbl, "names" );
            if str <> fail then
              prg:= ScanStraightLineProgram( str, "string" );
              if prg = fail then
                Print( "#E  automatically created script for `", tomatch,
                       "-cclsW1' would be incorrect" );
              fi;
              prg:= prg.program;
#T check the composition?
              Print( "#I  add the following script, in the new file `",
                     tomatch, "-cclsW1':\n",
                     str, "\n" );
              result:= false;
            fi;

          fi;
        od;
      fi;

    elif IsEmpty( arg ) then
      for triple in AtlasOfGroupRepresentationsInfo.groupnames do
        result:= AtlasOfGroupRepresentationsTestCycToCcls( triple[3] )
                 and result;
      od;
    fi;

    # Return the result.
    return result;
end );


#############################################################################
##
#E

