#############################################################################
##
#W  ctadmin.tbi         GAP character table library             Thomas Breuer
#W                                                               Ute Schiffer
##
#H  @(#)$Id: ctadmin.tbi,v 4.51 2001/08/28 16:26:14 gap Exp $
##
#Y  Copyright (C)  1997,  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
##
##  This file contains the implementation part of the data of the {\GAP}
##  character table library that is not automatically produced from the
##  library files.
##
##  1. Representations of library tables
##  2. Functions used in the library files
##  3. Functions to construct library tables
##  4. Functions used as `construction' component of library tables
##  5. Selection functions for the table library
##  6. Functions to produce tables in library format
##
##  Note that in all construction functions, the table under construction is
##  a plain record, *not* a table object.
##
Revision.( "ctbllib/gap/ctadmin_tbi" ) :=
    "@(#)$Id: ctadmin.tbi,v 4.51 2001/08/28 16:26:14 gap Exp $";


#############################################################################
##
#F  ReadTbl( <name> ) . . . . . . . . . . . . . . . .  character tables files
#F  RereadTbl( <name> ) . . . . . . . . . . . . . . .  character tables files
##
##  This function is used to read data files of the character table library.
##
BindGlobal( "ReadTbl",
    name -> DoReadPkg( Concatenation( "ctbllib/data/", name ),
                       "character tables" ) );

BindGlobal( "RereadTbl",
    name -> DoRereadPkg( Concatenation( "ctbllib/data/", name ),
                         "character tables" ) );


#############################################################################
##
#v  LIBLIST
##
##  is a record with information about admissible names, fusion sources
##  etc. of all library tables;
##  the components are mainly computed by the `awk' script `maketbl',
##  and they are stored in the file `ctprimar.tbl',
##
#v  TOM_TBL_INFO
##
##  is a list of length 2, the first containing identifiers of library
##  character tables, the second containing the corresponding identifiers
##  of tables of marks.
##
##  (We delete the information in `TOM_TBL_INFO' if not both the library of
##  character tables and the library of tables of marks are installed.
##  In order to avoid an error message, we also define a dummy filter
##  `IsLibTomRep' here.)
##
if TestPackageAvailability( "tomlib", "1.0" ) = fail then
  AUTO( ReadTbl, "ctprimar.tbl", "LIBLIST" );
  BindGlobal( "TOM_TBL_INFO", [ [], [] ] );
  DeclareFilter( "IsLibTomRep" );
else
  AUTO( ReadTbl, "ctprimar.tbl", "LIBLIST", "TOM_TBL_INFO" );
fi;


#############################################################################
##
#F  SET_TABLEFILENAME( <filename> )
##
InstallGlobalFunction( SET_TABLEFILENAME, function( filename )
    LIBTABLE.TABLEFILENAME:= filename;
    LIBTABLE.( filename ):= rec();
end );


#############################################################################
##
#F  GALOIS( <chars>, <list> )
#F  TENSOR( <chars>, <list> )
#F  EvalChars( <chars> )
##
InstallGlobalFunction( GALOIS, function( chars, li )
    return List( chars[ li[1] ], x -> GaloisCyc( x, li[2] ) );
end );

InstallGlobalFunction( TENSOR, function( chars, list )
    local i, chi, psi, result;
    chi:= chars[ list[1] ];
    psi:= chars[ list[2] ];
    result:= [];
    for i in [ 1 .. Length( chi ) ] do result[i]:= chi[i] * psi[i]; od;
    return result;
end );

InstallGlobalFunction( EvalChars, function( chars )
    local i;
    for i in [ 1 .. Length( chars ) ] do
      if IsFunction( chars[i][1] ) then
        chars[i]:= chars[i][1]( chars, chars[i][2] );
      fi;
    od;
end );


#############################################################################
##
#F  ALF( <from>, <to>, <map>[, <text>] ) . . . . .  add library table fusions
##
InstallGlobalFunction( ALF, function( arg )

    local pos, text;

    if ALN <> Ignore then

      # A file is read that does not belong to the official library.
      # Check that the names are valid.
      pos:= Position( LIBLIST.firstnames, arg[2] );
      if not arg[1] in RecNames( LIBTABLE.( LIBTABLE.TABLEFILENAME ) ) then
        Error( "source `", arg[1], "' is not stored in `LIBTABLE.",
               LIBTABLE.TABLEFILENAME, "'" );
      elif pos = fail then
        Error( "destination `", arg[2], "' is not a valid first name" );
      fi;

      # Check whether there was already such a fusion.
      if not arg[1] in LIBLIST.fusionsource[ pos ] then

        # Store the fusion source.
        LIBLIST.fusionsource:= ShallowCopy( LIBLIST.fusionsource );
        LIBLIST.fusionsource[ pos ]:= Immutable( Concatenation(
            LIBLIST.fusionsource[ pos ], [ arg[1] ] ) );
        MakeImmutable( LIBLIST.fusionsource );

      fi;

    fi;

    if Length( arg ) = 4 then
      text:= Concatenation( arg[4] );
      ConvertToStringRep( text );
      Add( LIBTABLE.( LIBTABLE.TABLEFILENAME ).(
               arg[1] ).ComputedClassFusions,
           rec( name:= arg[2], map:= arg[3], text:= text ) );
    else
      Add( LIBTABLE.( LIBTABLE.TABLEFILENAME ).(
               arg[1] ).ComputedClassFusions,
           rec( name:= arg[2], map:= arg[3] ) );
    fi;
end );


#############################################################################
##
#F  ACM( <spec>, <dim>, <val> ) . . . . . . . . . . . . . add Clifford matrix
##
InstallGlobalFunction( ACM, function( spec, dim, val )
    spec:= LIBTABLE.( Concatenation( "clm", spec ) );
    if not IsBound( spec[ dim ] ) then
      spec[ dim ]:= [];
    fi;
    Add( spec[ dim ], val );
end );


#############################################################################
##
#F  ARC( <name>, <comp>, <val> ) . . . . . . . add component of library table
##
InstallGlobalFunction( ARC, function( name, comp, val )
    LIBTABLE.( LIBTABLE.TABLEFILENAME ).( name ).( comp ):= val;
end );


#############################################################################
##
#F  NotifyNameOfCharacterTable( <firstname>, <newnames> )
##
##  notifies the new names in the list <newnames> for the library table with
##  first name <firstname>, if there is no other table yet for that some of
##  these names are admissible.
##
InstallGlobalFunction( NotifyNameOfCharacterTable,
    function( firstname, newnames )

    local lower,
          pos,
          pos2,
          name,
          j;

    if not ( IsString( firstname )
             and IsList( newnames ) and ForAll( newnames, IsString ) ) then
      Error( "<firstname> and entries in list <newnames> must be strings" );
    elif ForAny( [ 1 .. Length( firstname ) - 2 ],
               x -> firstname{ [ x .. x+2 ] } = "mod" ) then
      Error( "Brauer tables must not have explicitly given `othernames'" );
    fi;

    pos:= Position( LIBLIST.firstnames, firstname );
    if pos = fail then
      Error( "no GAP library table with first name `", firstname, "'" );
    fi;
    lower:= List( newnames, LowercaseString );
    if ForAny( lower, x -> x in LIBLIST.allnames ) then
      Error( "<newnames> must contain only new names" );
    fi;

    # Change `LIBLIST'.
    LIBLIST.allnames:= Concatenation(
        LIBLIST.allnames, lower );
    LIBLIST.position:= Concatenation(
        LIBLIST.position, List( lower, x -> pos ) );
    SortParallel( LIBLIST.allnames, LIBLIST.position );
    LIBLIST.allnames := Immutable( LIBLIST.allnames );
    LIBLIST.position := Immutable( LIBLIST.position );
end );


#############################################################################
##
#F  NotifyCharacterTable( <firstname>, <filename>, <othernames> )
##
##  notifies a new ordinary table to the library.
##  This table has `Identifier' value <firstname>,
##  it is contained in the file with name <filename>, and
##  it is known to have also the names contained in the list <othernames>.
##
##  `NotifyCharacterTable' modifies the global variable `LIBLIST' after
##  having checked that there is no other table yet with admissible name
##  equal to <firstname> or contained in <othernames>.
##
InstallGlobalFunction( NotifyCharacterTable,
    function( firstname, filename, othernames )

    local len, pos;

    if not ( IsString( firstname ) and IsString( filename )
                                   and IsList( othernames ) ) then
      Error( "<firstname>, <filename> must be strings, ",
             "<othernames> must be a list" );
    fi;

    if LowercaseString( firstname ) in LIBLIST.allnames then
      Error( "'", firstname, "' is already a valid name" );
    fi;

    # Change `LIBLIST'.
    LIBLIST.firstnames:= Immutable( Concatenation(
        LIBLIST.firstnames, [ firstname ] ) );
    if not filename in LIBLIST.files then
      LIBLIST.files:= Immutable( Concatenation(
          LIBLIST.files, [ filename ] ) );
    fi;
    len:= Length( LIBLIST.firstnames );
    LIBLIST.filenames:= ShallowCopy( LIBLIST.filenames );
    LIBLIST.filenames[ len ]:=
        Position( LIBLIST.files, filename );
    LIBLIST.filenames:= Immutable( LIBLIST.filenames );
    LIBLIST.fusionsource:= ShallowCopy(
        LIBLIST.fusionsource );
    LIBLIST.fusionsource[ len ]:= [];
    LIBLIST.fusionsource:= Immutable(
        LIBLIST.fusionsource );
    NotifyNameOfCharacterTable( firstname, [ firstname ] );
    NotifyNameOfCharacterTable( firstname, othernames );

    # Allow natural names.
#T !!
end );


#############################################################################
##
#F  MBT( <arg> )
##
InstallGlobalFunction( MBT, function( arg )

    local i, record;

    record:= rec(
                  InfoText                 := arg[ 3],
                  UnderlyingCharacteristic := arg[ 2],
                  block                    := arg[ 4],
                  defect                   := arg[ 5],
                  basicset                 := arg[ 6],
                  brauertree               := arg[ 7],
                  decinv                   := arg[ 8],
                  factorblocks             := arg[ 9],
                  AutomorphismsOfTable     := arg[10],
                  indicator                := arg[11]
                 );

    for i in RecNames( record ) do
      if record.(i) = 0 then
        Unbind( record.(i) );
      fi;
    od;
    if Length( arg ) = 12 then
      for i in RecNames( arg[12] ) do
        record.(i):= arg[12].(i);
      od;
    fi;
    LIBTABLE.( LIBTABLE.TABLEFILENAME ).(
                 Concatenation( arg[1], "mod", String( arg[2] ) ) ):= record;
end );


#############################################################################
##
#F  MOT( <arg> )
##
InstallGlobalFunction( MOT, function( arg )

    local record, i;

    # Construct the record.
    record:= rec(
                  Identifier               := arg[1],
                  InfoText                 := arg[2],
                  UnderlyingCharacteristic := 0,
                  SizesCentralizers        := arg[3],
                  ComputedPowerMaps        := arg[4],
                  ComputedClassFusions     := [],
                  Irr                      := arg[5],
                  AutomorphismsOfTable     := arg[6]
                 );

    for i in [ "InfoText", "SizesCentralizers", "ComputedPowerMaps",
               "ComputedClassFusions", "Irr", "AutomorphismsOfTable" ] do
      if record.(i) = 0 then
        Unbind( record.(i) );
      fi;
    od;
    if IsBound( arg[7] ) then
      record.construction:= arg[7];
    fi;

    # Store the table record.
    LIBTABLE.( LIBTABLE.TABLEFILENAME ).( arg[1] ):= record;
end );


#############################################################################
##
#V  GEN_Q_P
##
#F  PrimeBase( <q> )
##
InstallFlushableValue( GEN_Q_P, [] );

InstallGlobalFunction( PrimeBase, function( q )
    if not IsBound( GEN_Q_P[q] ) then
      GEN_Q_P[q]:= FactorsInt( q )[1];
    fi;
    return GEN_Q_P[q];
end );


#############################################################################
##
#F  LibInfoCharacterTable( <tblname> )
##
InstallGlobalFunction( LibInfoCharacterTable, function( tblname )

    local i, ordinfo, obj, pos;

    # Is `tblname' the name of a Brauer table,
    # i.e., does it have the structure `<ordname>mod<prime>' ?
    # If so, return `<firstordname>mod<prime>' where
    # `<firstordname> = LibInfoCharacterTable( <ordname> ).firstName'.

    tblname:= LowercaseString( tblname );
    for i in [ 1 .. Length( tblname ) - 2 ] do
      if tblname{ [ i .. i+2 ] } = "mod" then
        ordinfo:= LibInfoCharacterTable( tblname{ [ 1 .. i-1 ] } );
        if ordinfo <> fail then
          ordinfo.firstName:= Concatenation( ordinfo.firstName,
                                  tblname{ [ i .. Length( tblname ) ] } );
          ConvertToStringRep( ordinfo.firstName );
          ordinfo.fileName:= ShallowCopy( ordinfo.fileName );
          ordinfo.fileName[3]:= 'b';
          ConvertToStringRep( ordinfo.fileName );
        fi;
        return ordinfo;
      fi;
    od;

    # The name might belong to an ordinary table.
    pos:= Position( LIBLIST.allnames, tblname );
    if pos <> fail then
      pos:= LIBLIST.position[ pos ];
      if pos <> fail then
        return rec( firstName := LIBLIST.firstnames[ pos ],
                    fileName  := LIBLIST.files[
                                     LIBLIST.filenames[ pos ] ] );
      fi;
      return fail;
    fi;

    # The name might belong to a generic table.
    if tblname in LIBLIST.GENERIC.allnames then
      return rec( firstName := LIBLIST.GENERIC.firstnames[
                            Position( LIBLIST.GENERIC.allnames,
                                      tblname ) ],
                  fileName  := "ctgeneri" );
    fi;

    return fail;
end );


#############################################################################
##
#F  LibraryTables( <filename> )
##
InstallGlobalFunction( LibraryTables, function( filename )

    local file;

    if not IsBound( LIBTABLE.LOADSTATUS.( filename ) )
       or LIBTABLE.LOADSTATUS.( filename ) = "unloaded" then

      # It is necessary to read a library file.
      # First unload all files which are not `"userloaded"', except that
      # with the ordinary resp. Brauer tables corresponding to those in
      # the file `filename'
      for file in RecNames( LIBTABLE.LOADSTATUS ) do
        if LIBTABLE.LOADSTATUS.( file ) <> "userloaded" and
           filename{ [ 4 .. Length( filename ) ] }
            <> file{ [ 4 .. Length( file ) ] } then
          LIBTABLE.( file ):= rec();
          LIBTABLE.LOADSTATUS.( file ):= "unloaded";
        fi;
      od;

      # Try to read the file.
      LIBTABLE.( filename ):= rec();
#T      LIBTABLE.TABLEFILENAME:= filename;
#T allow to read files in other directories if the tables were notified there!
      if not ReadTbl( Concatenation( filename, ".tbl" ) ) then
        Info( InfoCharacterTable, 1,
              "no file with name `", filename, "' in the GAP table library" );
        return fail;
      fi;

      # Reset the load status from `"userloaded"' to `"loaded"'.
      LIBTABLE.LOADSTATUS.( filename ):= "loaded";

    fi;

    return LIBTABLE.( filename );
end );


#############################################################################
##
#F  CharacterTableFromLibrary( <tblname> )
#F  CharacterTableFromLibrary( <series>, <param1>[, <param2>] )
##
InstallGlobalFunction( CharacterTableFromLibrary, function( arg )

    local i,
          j,
          tblname,
          firstname,
          filename,
          libtbl,
          librarytables,
          file,
          newirredinfo,
          info,
          pos,
          fld,
          name,
          fus;

    if IsEmpty( arg ) or not IsString( arg[1] ) then

      Error( "usage: CharacterTableFromLibrary( <tblname> )\n",
             " resp. CharacterTableFromLibrary( <series>, <parameters> )" );

    elif Length( arg ) = 1 then

      # `CharacterTableFromLibrary( tblname )'
      tblname:= arg[1];
      firstname:= LibInfoCharacterTable( tblname );
      if firstname = fail then
        Info( InfoCharacterTable, 1,
              "No library table with name `", tblname, "'" );
        return fail;
      fi;
      filename  := firstname.fileName;
      firstname := firstname.firstName;

      if filename{ [ 1 .. 3 ] } = "ctb" then

        # Brauer table, call `BrauerTable'
        # (First get the ordinary table.)
        name:= PartsBrauerTableName( firstname );
        return BrauerTableFromLibrary(
                   CharacterTableFromLibrary( name.ordname ),
                   name.prime );

      fi;

      # ordinary or generic table

      librarytables:= LibraryTables( filename );

      if    librarytables = fail
         or not IsBound( librarytables.( firstname ) ) then
        Info( InfoCharacterTable, 1,
              "No library table with name `", tblname, "'" );
        return fail;
      fi;

      libtbl:= librarytables.( firstname );

      # If the table has not yet been converted to an object,
      # we must do this now.
      if IsRecord( libtbl ) then

        # If the table is a generic table then simply return it.
        if IsBound( libtbl.isGenericTable )
           and libtbl.isGenericTable = true then
          return libtbl;
        fi;

        # Concatenate the lines of the `InfoText' component.
        if IsBound( libtbl.InfoText ) then
          libtbl.InfoText:= Concatenation( libtbl.InfoText );
          ConvertToStringRep( libtbl.InfoText );
        fi;

        # Store the fusion sources.
        pos:= Position( LIBLIST.firstnames, firstname );
        libtbl.NamesOfFusionSources:=
            ShallowCopy( LIBLIST.fusionsource[ pos ] );

        # Evaluate characters encoded as `[GALOIS,[i,j]]'
        # or `[TENSOR,[i,j]]'.
        if IsBound( libtbl.projectives ) then
          fld:= libtbl.projectives;
          libtbl.projectives:= [];
          for i in [ 1, 3 .. Length( fld ) - 1 ] do
            EvalChars( fld[i+1] );
            for fus in LIBLIST.projections do
              if fus[2] = firstname and fus[1] = fld[i] then
                Add( libtbl.projectives, rec(
                                              name  := fld[i],
                                              chars := fld[i+1],
                                              map   := fus[3]
                                             ) );
              fi;
            od;
          od;
        fi;

        # Obey the construction component.
        if IsBound( libtbl.construction ) then
          libtbl.construction( libtbl );
        fi;

        # initialize some components
        if IsBound( libtbl.orders ) then
          libtbl.OrdersClassRepresentatives:= libtbl.orders;
        elif   IsBound( libtbl.ComputedPowerMaps )
           and not IsEmpty( libtbl.ComputedPowerMaps )
           and not IsBound( libtbl.OrdersClassRepresentatives ) then
          libtbl.OrdersClassRepresentatives:=
                       ElementOrdersPowerMap( libtbl.ComputedPowerMaps );
          if not ForAll( libtbl.OrdersClassRepresentatives, IsPosInt ) then
            Info( InfoWarning, 1,
                  "representative orders of library table ", tblname,
                  " not uniquely determined" );
            Unbind( libtbl.OrdersClassRepresentatives );
          fi;
        fi;

        if IsBound( libtbl.AutomorphismsOfTable ) then
          libtbl.AutomorphismsOfTable:= GroupByGenerators(
                     libtbl.AutomorphismsOfTable, () );
        fi;

        if IsBound( libtbl.maxes ) then
          libtbl.Maxes:= libtbl.maxes;
          Unbind( libtbl.maxes );
        fi;

        if IsBound( libtbl.isSimple ) then
          libtbl.IsSimpleCharacterTable:= libtbl.isSimple;
        fi;

        if IsBound( libtbl.CAS ) then
          libtbl.CASInfo:= libtbl.CAS;
          Unbind( libtbl.CAS );
        fi;
        if IsBound( libtbl.CASInfo ) then
          # For tables constructed from others,
          # the value may be copied from an attribute value
          # and hence may be immutable.
#T mutability problem:
#T if the following comment signs are removed then GAP runs into an error!
#         if not IsMutable( libtbl.CASInfo ) then
            libtbl.CASInfo:= List( libtbl.CASInfo, ShallowCopy );
#         fi;
          for i in libtbl.CASInfo do
            if IsBound( i.text ) and ForAll( i.text, IsString ) then
              i.text:= Concatenation( i.text );
              ConvertToStringRep( i.text );
            fi;
          od;
        fi;

        # Evaluate characters encoded as `[GALOIS,[i,j]]', `[TENSOR,[i,j]]'.
        EvalChars( libtbl.Irr );

        # Make the table object, and store it for the next call.
        ConvertToLibraryCharacterTableNC( libtbl );
        librarytables.( firstname ):= libtbl;

      fi;

      # Return the library table.
      return libtbl;

    else

      if arg[1] = "Quaternionic" and Length( arg ) = 2
         and IsInt( arg[2] ) then
        return CharacterTableQuaternionic( arg[2] );

      elif arg[1] = "GL" and Length( arg ) = 3
           and IsInt( arg[2] ) and IsInt( arg[3] ) then

        # `CharacterTable( GL, 2, q )'
        if arg[2] = 2 then
          return CharacterTableSpecialized(
                     CharacterTableFromLibrary( "GL2" ), arg[3] );
        else
          Info( InfoCharacterTable, 1,
                "Table of GL(", arg[2], ",q) not yet implemented" );
          return fail;
        fi;

      elif arg[1] = "SL" and Length( arg ) = 3
           and IsInt( arg[2] ) and IsInt( arg[3] ) then

        # CharacterTable( SL, 2, q )
        if arg[2] = 2 then
          if arg[3] mod 2 = 0 then
            return CharacterTableSpecialized(
                       CharacterTableFromLibrary( "SL2even" ),
                       arg[3] );
          else
            return CharacterTableSpecialized(
                       CharacterTableFromLibrary( "SL2odd" ),
                       arg[3] );
          fi;
        else
          Info( InfoCharacterTable, 1,
                "Table of SL(", arg[2], ",q) not yet implemented" );
          return fail;
        fi;

      elif arg[1] = "PSL" and Length( arg ) = 3
           and IsInt( arg[2] ) and IsInt( arg[3] ) then

        # PSL( 2, q )
        if arg[2] = 2 then
          if arg[3] mod 2 = 0 then
            return CharacterTableSpecialized(
                       CharacterTableFromLibrary( "SL2even" ),
                       arg[3] );
          elif ( arg[3] - 1 ) mod 4 = 0 then
            return CharacterTableSpecialized(
                       CharacterTableFromLibrary( "PSL2even" ),
                       arg[3] );
          else
            return CharacterTableSpecialized(
                       CharacterTableFromLibrary( "PSL2odd" ),
                       arg[3] );
          fi;
        else
          Info( InfoCharacterTable, 1,
                "Table of PSL(", arg[2], ",q) not yet implemented" );
          return fail;
        fi;

      elif arg[1] = "GU" and Length( arg ) = 3
           and IsInt( arg[2] ) and IsInt( arg[3] ) then

        # GU( 3, q )
        if arg[2] = 3 then
          return CharacterTableSpecialized(
                     CharacterTableFromLibrary( "GU3" ), arg[3] );
        else
          Info( InfoCharacterTable, 1,
                "Table of GU(", arg[2], ",q) not yet implemented" );
          return fail;
        fi;

      elif arg[1] = "SU" and Length( arg ) = 3
           and IsInt( arg[2] ) and IsInt( arg[3] ) then

        # SU( 3, q )
        if arg[2] = 3 then
          return CharacterTableSpecialized(
                     CharacterTableFromLibrary( "SU3" ),
                     arg[3] );
        else
          Info( InfoCharacterTable, 1,
                "Table of SU(", arg[2], ",q) not yet implemented" );
          return fail;
        fi;

      elif arg[1] = "Suzuki" and Length( arg ) = 2
           and IsInt( arg[2] ) then
        if not Set( FactorsInt( arg[2] ) ) = [ 2 ] then
          Info( InfoCharacterTable, 1,
                "CharacterTable(\"Suzuki\",q): q must be a power of 2");
          return fail;
        fi;
        return CharacterTableSpecialized(
                   CharacterTableFromLibrary( "Suzuki" ),
                   [ arg[2],
                     2^((Length(FactorsInt(arg[2]))+1)/2) ] );

      else
        return CharacterTableSpecialized(
                   CharacterTableFromLibrary( arg[1] ), arg[2] );
      fi;
    fi;
end );


#############################################################################
##
#F  CharTableLibrary( <arglist> )
##
##  for compatibility with GAP 3 ...
##
BindGlobal( "CharTableLibrary", function( arglist )
    return CallFuncList( CharacterTableFromLibrary, arglist );
end );


#############################################################################
##
#F  PartsBrauerTableName( <modname> )
##
InstallGlobalFunction( PartsBrauerTableName, function( modname )

    local i, primestring, ordname, prime, digits;

    primestring:= 0;
    for i in [ 1 .. Length( modname ) - 2 ] do
      if modname{ [ i .. i + 2 ] } = "mod" then
        primestring:= modname{ [ i + 3 .. Length( modname ) ] };
        ordname:= modname{ [ 1 .. i-1 ] };
      fi;
    od;
    if primestring = 0 then
      Print( "#I PartsBrauerTableName: ", modname,
             " is no valid name\n",
             "#I      for a Brauer table\n" );
      return fail;
    fi;

    # Convert the string back to a number.
    digits:= "0123456789";
    primestring:= List( primestring, x -> Position( digits, x ) );
    if fail in primestring then
      Print( "#I PartsBrauerTableName: ", modname,
             " is no valid name\n",
             "#I      for a Brauer table\n" );
      return fail;
    fi;
    prime:= 0;
    for i in [ 1 .. Length( primestring ) ] do
      prime:= 10 * prime + ( primestring[i] - 1 );
    od;

    return rec( ordname:= ordname, prime:= prime );
end );


#############################################################################
##
#F  BasicSetBrauerTree( <brauertree> )
##
InstallGlobalFunction( BasicSetBrauerTree, function( brauertree )

    local i,
          degrees,
          basicset,
          edge,
          elm;

    brauertree:= Set( brauertree );
    basicset:= [];

    # degrees of the vertices
    degrees:= [];
    for edge in brauertree do
      for i in edge do
        if not IsBound( degrees[i] ) then
          degrees[i]:= 1;
        else
          degrees[i]:= degrees[i] + 1;
        fi;
      od;
    od;

    while brauertree <> [] do

      # take a vertex of degree 1, remove its edge, adjust `degrees'
      elm:= Position( degrees, 1 );
      AddSet( basicset, elm );
      edge:= First( brauertree, x -> elm in x );
      RemoveSet( brauertree, edge );
      for i in edge do
        degrees[i]:= degrees[i] - 1;
      od;
    od;

    return basicset;
end );


#############################################################################
##
#F  DecMatBrauerTree( <brauertree> )
##
InstallGlobalFunction( DecMatBrauerTree, function( brauertree )

    local i,
          j,
          max,
          decmat;

    max:= 1;
    for i in brauertree do
      max:= Maximum( max, Maximum(i) );
    od;
    decmat:= NullMat( max, Length( brauertree ) );
    for i in [ 1 .. Length( brauertree ) ] do
      for j in brauertree[i] do
        decmat[j][i]:= 1;
      od;
    od;
    return decmat;
end );


#############################################################################
##
#F  BrauerTree( <decmat> )
##
InstallGlobalFunction( BrauerTree, function( decmat )

    local i, j, brauertree, edge, len;

    if not ( IsMatrix( decmat )
             and ForAll( decmat, x -> ForAll( x, y -> y=0 or y=1 ) ) ) then
      Print( "#I BrauerTree: <decmat> is not decomposition matrix\n",
             "#I     of a block of cyclic defect\n");
      return fail;
    fi;

    if decmat = [ [ 1 ] ] then return []; fi;

    brauertree:= [];
    for i in [ 1 .. Length( decmat[1] ) ] do

      # find the entries 1 in column `i'
      edge:= [];
      for j in [ 1 .. Length( decmat ) ] do
        if decmat[j][i] = 1 then Add( edge, j ); fi;
      od;
      len:= Length( edge );

      # If `len = 2', we have an ordinary edge of the tree; else this may
      # concern an exceptional character.

      if len = 2 then
        Add( brauertree, edge );
      else
        if Length( Set( decmat{ edge } ) ) <= 2 then

          # all or all but one ordinary irreducibles restrict identically
          Add( brauertree, edge );

        else
          Print( "#I BrauerTree: <decmat> is not decomposition",
                 " matrix\n",
                 "#I     of a block of cyclic defect\n");
          return fail;
        fi;
      fi;
    od;
    return brauertree;
end );


#############################################################################
##
#F  BrauerTableFromLibrary( <ordtbl>, <prime> )
##
InstallGlobalFunction( BrauerTableFromLibrary, function( ordtbl, prime )

    local filename,      # name of the file containing the Brauer table
          fld,           # library tables of the whole library file
          libtbl,        # record with data of the desired table
          reg,           # Brauer table, result
          nsg,           # normal subgroups of the ordinary table
          op,            # largest normal $p$-subgroup
          classes,       # class lengths in `ordtbl'
          size,          # size of normal $p$-subgroup
          orders,        # representative orders in `ordtbl'
          nccl,          # no. of classes in `ordtbl'
          entry,         # loop over stored fusions
          fusion,        # one fusion map
          result_blocks,
          i, j,
          ord,
          pow,
          ordblocks,
          modblocks,
          defect,
          name,
          irreducibles,
          restricted,
          block,
          basicset,
          class,
          images,
          chi,
          gal,
          newimages,
          pos,
          im,
          decmat,
          brauertree,
          facttbl,
          mfacttbl,
          pbl,
          info,
          factinfo,
          ordchars,
          offset,
          decinv;

    # Get the library file of the Brauer table if possible.
    name:= Concatenation( Identifier( ordtbl ), "mod", String( prime ) );
    filename:= LibInfoCharacterTable( name );
    if IsRecord( filename ) then
      filename:= LibInfoCharacterTable( name ).fileName;
      fld:= LibraryTables( filename );
    else
      fld:= fail;
    fi;

    if fld = fail or not IsBound( fld.( name ) ) then

      # Maybe we have to factor out a normal $p$-subgroup before
      # we find the table (name) in the library.
      nsg:= ClassPositionsOfNormalSubgroups( ordtbl );
      op:= false;
      j:= 0;
      classes:= SizesConjugacyClasses( ordtbl );
      for i in nsg do
        size:= Collected( Factors( Sum( classes{ i }, 0 ) ) );
        if Length( size ) = 1 and size[1][1] = prime
                              and j < size[1][2] then
          op:= i;
          j:= size[1][2];
        fi;
      od;

      if j = 0 then
        Info( InfoCharacterTable, 1,
              "No library table with name `", name, "'" );
        return fail;
      fi;

      orders:= OrdersClassRepresentatives( ordtbl );
      nccl:= NrConjugacyClasses( ordtbl );
      for entry in ComputedClassFusions( ordtbl ) do
        fusion:= entry.map;
        if Filtered( [ 1 .. nccl ], i -> fusion[i] = 1 ) = op then

          # We found the ordinary factor for which the Brauer characters
          # are equal to the ones we need.
          facttbl:= CharacterTableFromLibrary( entry.name );
          if facttbl = fail then
            return fail;
          fi;
          mfacttbl:= BrauerTable( facttbl, prime );
          if mfacttbl = fail then
            return fail;
          fi;

          # Now we set up a *new* Brauer table since the ordinary table
          # as well as the blocks information for the factor group is
          # different from the one for the extension.
          reg:= CharacterTableRegular( ordtbl, prime );

          # Set the irreducibles.
          # Note that the ordering of classes is in general *not* the same,
          # so we must translate with the help of fusion maps.
          fusion:= CompositionMaps(
                    InverseMap( GetFusionMap( mfacttbl, facttbl ) ),
                    CompositionMaps( GetFusionMap( ordtbl, facttbl ),
                                     GetFusionMap( reg, ordtbl ) ) );
          SetIrr( reg, List( Irr( mfacttbl ),
              chi -> Character( reg,
                  ValuesOfClassFunction( chi ){ fusion } ) ) );

          # Set known attribute values that can be copied from `mfacttbl'.
          if HasAutomorphismsOfTable( mfacttbl ) then
            SetAutomorphismsOfTable( reg, AutomorphismsOfTable( mfacttbl ) );
          fi;
          if HasInfoText( mfacttbl ) then
            SetInfoText( reg, InfoText( mfacttbl ) );
          fi;
          if HasComputedIndicators( mfacttbl ) then
            SetComputedIndicators( reg, ComputedIndicators( mfacttbl ) );
          fi;

          # Return the table.
          return reg;

        fi;
      od;

      Info( InfoCharacterTable, 1,
            "No library table of the factor by O_p" );
      return fail;

    fi;


    libtbl:= fld.( name );

    # If the table was already constructed simply return it.
    if IsBrauerTable( libtbl ) then
      return libtbl;
    fi;

    # Otherwise we have to work.
    reg:= CharacterTableRegular( ordtbl, prime );

#T just a hack ...
    reg!.defect:= libtbl.defect;
    reg!.block:= libtbl.block;
    if IsBound( libtbl.decinv ) then
      reg!.decinv:= libtbl.decinv;
    fi;
    if IsBound( libtbl.basicset ) then
      reg!.basicset:= libtbl.basicset;
    fi;
    if IsBound( libtbl.brauertree ) then
      reg!.brauertree:= libtbl.brauertree;
    fi;
#T end of the hack ...

    # Concatenate the lines of the `InfoText' component if necessary.
    if IsString( libtbl.InfoText ) then
      SetInfoText( reg, libtbl.InfoText );
    else
      SetInfoText( reg, Concatenation( libtbl.InfoText ) );
    fi;

    # If automorphisms are known (list of generators), convert to a group.
    if IsBound( libtbl.AutomorphismsOfTable ) then
      SetAutomorphismsOfTable( reg,
          GroupByGenerators( libtbl.AutomorphismsOfTable, () ) );
    fi;

    # Initialize some components.
    if not IsBound( libtbl.decinv ) then
      libtbl.decinv:= [];
    fi;

    block:= [];
    defect:= [];
    basicset:= [];
    brauertree:= [];
    decinv:= [];

    # If the distribution to blocks is stored on the table
    # then use it, otherwise compute it.
    ordblocks:= PrimeBlocks( ordtbl, prime ).block;
    ordblocks:= InverseMap( ordblocks );

    # Get the blocks of factor groups if necessary;
    # `factorblocks' is a list of pairs containing the names of the
    # tables that hold the blocks and the offset of basic set characters.
    if IsBound( libtbl.factorblocks ) then

      for i in libtbl.factorblocks do
        facttbl:= LIBTABLE.( filename ).( Concatenation(
                                            i[1], "mod", String( prime ) ) );
        if block = [] then
          offset:= 0;
        else
          offset:= Maximum( block ) + 1 - Minimum( facttbl!.block );
        fi;
        pos:= Length( defect );
        Append( defect, facttbl!.defect );
        Append( block, offset + facttbl!.block );
        for j in [ 1 .. Length( facttbl!.defect ) ] do
          if facttbl!.defect[j] <> 0 then
            if IsBound( facttbl!.decinv ) and
               IsBound( facttbl!.decinv[j] ) then
              if IsInt( facttbl!.decinv[j] ) then
                decinv[ pos + j ]:= facttbl!.decinv[ facttbl!.decinv[j] ];
              else
                decinv[ pos + j ]:= facttbl!.decinv[j];
              fi;
              brauertree[ pos + j ]:= fail;
              basicset[ pos + j ]:= i[2] + facttbl!.basicset[j];
            else
              if IsInt( facttbl!.brauertree[j] ) then
                brauertree[ pos + j ]:=
                    facttbl!.brauertree[ facttbl!.brauertree[j] ];
              else
                brauertree[ pos + j ]:= facttbl!.brauertree[j];
              fi;
              basicset[ pos + j ]:= ordblocks[ pos + j ]{
                            BasicSetBrauerTree( brauertree[ pos + j ] ) };
            fi;
          fi;
        od;
      od;

    fi;

    pos:= Length( defect );
    Append( defect, libtbl.defect );
    Append( block, libtbl.block );
    for j in [ 1 .. Length( libtbl.defect ) ] do
      if libtbl.defect[j] <> 0 then
        if IsBound( libtbl.decinv[j] ) then
          if IsInt( libtbl.decinv[j] ) then
            decinv[ pos + j ]:= libtbl.decinv[ libtbl.decinv[j] ];
          else
            decinv[ pos + j ]:= libtbl.decinv[j];
          fi;
          brauertree[ pos + j ]:= fail;
          basicset[ pos + j ]:= libtbl.basicset[j];
        else
          if IsInt( libtbl.brauertree[j] ) then
            brauertree[ pos + j ]:=
                libtbl.brauertree[ libtbl.brauertree[j] ];
          else
            brauertree[ pos + j ]:= libtbl.brauertree[j];
          fi;
          basicset[ pos + j ]:= ordblocks[ pos + j ]{
                            BasicSetBrauerTree( brauertree[ pos + j ] ) };
        fi;
      fi;
    od;

    # compute the blocks and the irreducibles of each block,
    # and assign them to the right positions;
    # assign the known decomposition matrices and Brauer trees;
    # ignore defect 0 blocks
    irreducibles:= [];
    restricted:= RestrictedClassFunctions( Irr( ordtbl ), reg );

    modblocks := InverseMap( block );
    result_blocks:= [];

    for i in [ 1 .. Length( ordblocks ) ] do

      if IsInt( ordblocks[i] ) then ordblocks[i]:= [ ordblocks[i] ]; fi;
      if IsInt( modblocks[i] ) then modblocks[i]:= [ modblocks[i] ]; fi;

      if defect[i] = 0 then

        irreducibles[ modblocks[i][1] ]:= restricted[ ordblocks[i][1] ];
        decinv[i]:= [ [1] ];
        basicset[i]:= ordblocks[i];

      else

        if IsBound( basicset[i] ) then
          if IsBound( brauertree[i] ) and brauertree[i] <> fail then
            decinv[i]:= DecMatBrauerTree( brauertree[i]){
                             Filtered( [ 1 .. Length( ordblocks[i] ) ],
                                       x -> ordblocks[i][x] in basicset[i] )
                            }^(-1) ;
          fi;
          if IsBound( decinv[i] ) then
            irreducibles{ modblocks[i] }:=
                List( decinv[i] * List( restricted{ basicset[i] },
                                        ValuesOfClassFunction ),
                      vals -> Character( reg, vals ) );
          else
            Error( "at least one of the components <decinv>, <brauertree> ",
                   "must be bound at pos. ", i );
          fi;
        else
          Print( "#E BrauerTable: no basic set for block ", i, "\n" );
        fi;
      fi;

      result_blocks[i]:= rec( defect    := defect[i],
                              ordchars  := ordblocks[i],
                              modchars  := modblocks[i],
                              decinv    := decinv[i],
                              basicset  := basicset[i]   );
      if IsBound( brauertree[i] ) and brauertree[i] <> fail then
        result_blocks[i].brauertree:= brauertree[i];
      fi;

    od;

    # instead of calling `Immutable' for the entries in the loop ...
    MakeImmutable( ordblocks );
    MakeImmutable( modblocks );
    MakeImmutable( decinv );
    MakeImmutable( basicset );
    MakeImmutable( brauertree );

    SetBlocksInfo( reg, result_blocks );
    SetIrr( reg, irreducibles );

    # decode the `IrredInfo' value
    # (contains 2nd indicator if the prime is 2, else nothing)
    if IsBound( libtbl.indicator ) then
      SetComputedIndicators( reg, [ , libtbl.indicator ] );
    fi;

#T BAD HACK until incomplete tables disappeared ...
#T only file ctborth2 ...
    if IsBound( libtbl.warning ) then
      Print( "#W warning for table of `", Identifier( reg ), "':\n",
             libtbl.warning, "\n" );
    fi;

    # Store the Brauer table for the next call.
    fld.( name ):= reg;

    # Return the Brauer table.
    return reg;
end );


#############################################################################
##
#M  BrauerTableOp( <tbl>, <p> ) . . . . . . . . . . <p>-modular library table
##
##  If <tbl> is an {\ATLAS} library table then take the Brauer table from the
##  library if possible,
##  and fall back to the generic method otherwise.
##
InstallMethod( BrauerTableOp,
    "for a library character table and a positive integer",
    [ IsOrdinaryTable and IsLibraryCharacterTableRep, IsPosInt ], SUM_FLAGS,
    function( tbl, p )
    local modtbl;
    modtbl:= BrauerTableFromLibrary( tbl, p );
    if modtbl = fail then
      TryNextMethod();
    fi;
    return modtbl;
    end );


#############################################################################
##
#F  CharacterTableSpecialized( <generic_table>, <q> )  . . . . specialise <q>
##
InstallGlobalFunction( CharacterTableSpecialized, function( gtab, q )

    local taf,         # record of the specialized table, result
          genclass,    #
          classparam,  #
          genchar,     #
          charparam,   #
          parm,        #
          i, k,        #
          class;       #

    # Check if the argument is valid.
    if not ( IsRecord( gtab ) and IsBound( gtab.isGenericTable ) ) then
      Error( "this is not a generic character table" );
    elif IsBound( gtab!.domain ) and not gtab!.domain( q ) then
      Error( q, " is not a valid parameter for this generic table" );
    fi;

    # A generic character table must contain at least functions to compute
    # the parametrisation of classes and characters.

    if IsBound( gtab!.wholetable ) then

      # If the generic table has a component `wholetable'
      # (a function which takes the generic table and `q' as arguments),
      # use this function to construct the whole table.
      taf:= gtab!.wholetable( gtab, q );

    else

      taf := rec();

      # Get the parametrisation of classes and characters.
      # `genclass' stores for each class of the specialized character table
      # the number of the class of the generic table it stems from.
      # `classparam' stores the parameter of the special class.
      # `genchar' and `charparam' do the same for characters.

      if    not IsBound( gtab!.classparam )
         or not IsBound( gtab!.charparam ) then
        Error( "components `classparam' and `charparam' are missing" );
      fi;

      genclass   := [];
      classparam := [];

      for i in [ 1 .. Length( gtab!.classparam ) ] do
        parm := gtab!.classparam[i](q);
        Append( classparam, parm );
        Append( genclass, List( parm, j -> i ) );
      od;

      genchar   := [];
      charparam := [];

      for i in [ 1 .. Length( gtab!.charparam ) ] do
        parm := gtab!.charparam[i](q);
        Append( charparam, parm );
        Append( genchar, List( parm, j -> i ) );
      od;

      # Compute the name of the table.
      if IsBound( gtab!.specializedname ) then
        taf.Identifier:= gtab!.specializedname( q );
        ConvertToStringRep( taf.Identifier );
      fi;

      # Compute the group order.
      if IsBound( gtab!.size ) then
        taf.Size := gtab!.size(q);
      fi;

      # Compute centralizer and representative orders.
      if IsBound( gtab!.centralizers ) then
        taf.SizesCentralizers := List( [ 1 .. Length( classparam ) ],
                j -> gtab!.centralizers[ genclass[j] ]( q, classparam[j] ) );
      fi;

      if IsBound( gtab!.orders ) then
        taf.OrdersClassRepresentatives :=
                List( [ 1 .. Length( classparam ) ],
                      j -> gtab!.orders[ genclass[j] ]( q, classparam[j] ) );
      fi;

      # Compute the power maps.
      taf.ComputedPowerMaps := [];
      if IsBound( gtab!.powermap ) and IsBound( taf.Size ) then
        for i in Reversed( Set( Factors( taf.Size ) ) ) do
          taf.ComputedPowerMaps[i] := [];
          for class in Reversed( [1 .. Length( classparam ) ] ) do
            parm := gtab!.powermap[genclass[class]](q, classparam[class],i);
            k := 1;
            while genclass[k] <> parm[1] or classparam[k] <> parm[2] do
              k := k+1;
            od;
            taf.ComputedPowerMaps[i][class] := k;
          od;
        od;
      fi;

      # Perform some initialisations, if the necessary data are present.
      if IsBound( gtab!.classtext ) then
        taf.classtext := List( [ 1 .. Length( classparam ) ],
                   j -> gtab!.classtext[ genclass[j] ]( q, classparam[j] ) );
      fi;

      # Compute the character values.
      if IsBound( gtab!.matrix ) then
        taf.Irr := gtab!.matrix( q );
      elif IsBound( gtab!.irreducibles ) then
        taf.Irr := List( [ 1 .. Length( charparam ) ],
                  i -> List( [1..Length(classparam)],
                             j -> gtab!.irreducibles[genchar[i]][genclass[j]]
                                  ( q, charparam[i], classparam[j] ) ) );
      fi;

      taf.ClassParameters:= List( [ 1 .. Length( classparam ) ],
                                  i -> [ genclass[i], classparam[i] ] );
      taf.CharacterParameters:= List( [ 1 .. Length( charparam ) ],
                                      i -> [ genchar[i], charparam[i] ] );

      if IsBound( gtab!.text ) then
        taf.InfoText:= Concatenation( "computed using ", gtab!.text );
      fi;

      if IsBound( gtab!.UnderlyingCharacteristic ) then
        taf.UnderlyingCharacteristic:= gtab!.UnderlyingCharacteristic;
      else
        taf.UnderlyingCharacteristic:= 0;
      fi;

    fi;

    # Objectify and return the table.
    ConvertToLibraryCharacterTableNC( taf );
    return taf;
end );


#############################################################################
##
#F  TransferComponentsToLibraryTableRecord( <t>, <tbl> )
##
InstallGlobalFunction( TransferComponentsToLibraryTableRecord,
    function( t, tbl )

    local names, i, fld;

    names:= ShallowCopy( RecNames( tbl ) );
    Add( names, "Irr" );

    # Set the supported attribute values.
    for i in [ 1, 4 .. Length( SupportedCharacterTableInfo ) - 2 ] do
      if     not SupportedCharacterTableInfo[ i+1 ] in names
         and Tester( SupportedCharacterTableInfo[i] )( t ) then
        tbl.( SupportedCharacterTableInfo[ i+1 ] ):=
            SupportedCharacterTableInfo[i]( t );
      fi;
    od;

    # Set the supported library table components.
    for fld in Difference( SupportedLibraryTableComponents, names ) do
      if IsBound( t!.( fld ) ) then
        tbl.( fld ):= t!.( fld );
      fi;
    od;

    # Set the irreducibles if necessary.
    if HasIrr( t ) and not IsBound( tbl!.Irr ) then
      tbl.Irr:= List( Irr( t ), ValuesOfClassFunction );
    fi;
end );


#############################################################################
##
#F  ConstructMixed( <tbl>, <subname>, <factname>, <plan>, <perm> )
##
InstallGlobalFunction( ConstructMixed,
    function( tbl, subname, factname, plan, perm )

    local factfus,  # factor fusion from `tbl' to `fact'
          subfus,   # subgroup fusion from `sub' to `tbl'
          proj,     # projection map of `subfus'
          irreds,   # list of irreducibles
          zero;     # list of zeros to be appended to the characters

    factfus  := First( tbl.ComputedClassFusions,
                       fus -> fus.name = factname ).map;
    factname := CharacterTableFromLibrary( factname );
    subname  := CharacterTableFromLibrary( subname );
    subfus   := First( ComputedClassFusions( subname ),
                       fus -> fus.name = tbl.Identifier ).map;
    proj    := ProjectionMap( subfus );
    irreds  := List( Irr( factname ),
                     x -> ValuesOfClassFunction( x ){ factfus } );
    zero    := Zero( [ Maximum( subfus ) + 1
                       .. Length( tbl.SizesCentralizers ) ] );
    Append( irreds, List( plan, entry ->
         Concatenation( Sum( Irr( subname ){ entry } ){ proj }, zero ) ) );
    tbl.Irr:= Permuted( irreds, perm );
end );


#############################################################################
##
#F  ConstructMixedInfo( <tblmGa>, <tblmG>, <tblGa> )
##
InstallGlobalFunction( ConstructMixedInfo,
    function( tblmGa, tblmG, tblGa )

    local factfus,
          subfus,
          kernel,
          nccl,
          irr,
          plan,
          chi,
          rest,
          nonfaith,
          zero,
          proj,
          faithful,
          perm;

    factfus:= GetFusionMap( tblmGa, tblGa );
    subfus:= GetFusionMap( tblmG, tblmGa );
    if factfus = fail or subfus = fail then
      Error( "fusions <tblmG> -> <tblmGa> -> <tblGa> must be stored" );
    fi;

    kernel:= ClassPositionsOfKernel( factfus );

    nccl:= NrConjugacyClasses( tblmG );
    irr:= Irr( tblmG );
    plan:= [];
    for chi in Irr( tblmGa ) do
      if not IsSubset( ClassPositionsOfKernel( chi ), kernel ) then
        rest:= chi{ subfus };
        Add( plan, Filtered( [ 1 .. nccl ],
                       i -> ScalarProduct( tblmG, rest, irr[i] ) <> 0 ) );
      fi;
    od;

    nonfaith:= List( Irr( tblGa ), chi -> chi{ factfus } );
    zero:= Zero( [ Maximum( subfus ) + 1 .. NrConjugacyClasses( tblmGa ) ] );
    proj:= ProjectionMap( subfus );
    faithful:= List( plan,
        entry -> Concatenation( Sum( irr{ entry } ){ proj }, zero ) );
    perm:= Sortex( Concatenation( nonfaith, faithful ) ) /
           Sortex( ShallowCopy( Irr( tblmGa ) ) );

    return rec( subname:= Identifier( tblmG ),
                factname:= Identifier( tblGa ),
                plan:= plan,
                perm:= perm );
end );


#############################################################################
##
#F  ConstructProj( <tbl> )
##
InstallGlobalFunction( ConstructProj, function( tbl )

    local i, j, factor, fus, mult, irreds, linear, omegasquare, I,
          d, name, factfus, proj, adjust, Adjust,
          ext, lin, chi, faith, nccl, partner, divs, prox, foll,
          vals;

    nccl:= Length( tbl.SizesCentralizers );
    factor:= CharacterTableFromLibrary( tbl.Irr[1][1] );
    fus:= First( tbl.ComputedClassFusions,
                 fus -> fus.name = tbl.Irr[1][1] ).map;
    mult:= tbl.SizesCentralizers[1] / Size( factor );
    irreds:= List( Irr( factor ), x -> ValuesOfClassFunction( x ){ fus } );
    linear:= Filtered( irreds, x -> x[1] = 1 );
    linear:= linear{ [ 2 .. Length( linear ) ] };

    # some roots of unity
    omegasquare:= E(3)^2;
    I:= E(4);

    # Loop over the divisors of `mult' (a divisor of 12).
    # Note the succession for `mult = 12'!
    if mult <> 12 then
      divs:= Difference( DivisorsInt( mult ), [ 1 ] );
    else
      divs:= [ 2, 4, 3, 6, 12 ];
    fi;

    for d in divs do

      # Construct the faithful irreducibles for an extension by `d'.
      # For that, we split and adjust the portion of characters (stored
      # on the small table `factor') as if we would create this extension,
      # and then we blow up these characters to the whole table.

      name:= tbl.Irr[d][1];
      partner:= tbl.Irr[d][2];
      proj:= First( factor!.projectives, x -> x.name = name );
      faith:= List( proj.chars, y -> y{ fus } );
      proj:= ShallowCopy( proj.map );

      if name = tbl.Identifier then
        factfus:= [ 1 .. Length( tbl.SizesCentralizers ) ];
      else
        factfus:= First( tbl.ComputedClassFusions, x -> x.name = name ).map;
      fi;

      Add( proj, Length( factfus ) + 1 );    # for termination of loop
      adjust:= [];
      for i in [ 1 .. Length( proj ) - 1 ] do
        for j in [ proj[i] .. proj[i+1]-1 ] do
          adjust[ j ]:= proj[i];
        od;
      od;

      # Now we have to multiply the values on certain classes `j' with
      # roots of unity, dependent on the value of `d':
#T Note that we do not have the factor fusion from d.G to G available,
#T since the only tables we have are those of mult.G and G,
#T together with the projective characters for the various intermediate
#T tables!

      Adjust:= [];
      for i in [ 1 .. d-1 ] do
        Adjust[i]:= Filtered( [ 1 .. Length( factfus ) ],
                              x -> adjust[ factfus[x] ] = factfus[x] - i );
      od;
#T this means to adjust also in many zero columns;
#T if d = 6 and a class has only 2 or 3 preimages, the second preimage class
#T need not be adjusted for the faithful characters ...

      # d =  2: classes in `Adjust[1]' multiply with `-1'
      # d =  3: classes in `Adjust[x]' multiply
      #                     with `E(3)^x' for the proxy cohort,
      #                     with `E(3)^(2*x)' for the follower cohort
      # d =  4: classes in `Adjust[x]' multiply
      #                     with `E(4)^x' for the proxy cohort,
      #                     with `(-E(4))^x' for the follower cohort,
      # d =  6: classes in `Adjust[x]' multiply with `(-E(3))^x'
      # d = 12: classes in `Adjust[x]' multiply with `(E(12)^7)^x'
      #
      # (*Note* that follower cohorts of classes never occur in projective
      #  ATLAS tables ... )

      # Determine proxy classes and follower classes:
      if Length( linear ) in [ 2, 5 ] then  # out in [ 3, 6 ]
        prox:= [];
        foll:= [];
        chi:= irreds[ Length( linear ) ];
        for i in [ 1 .. nccl ] do
          if chi[i] = omegasquare then
            Add( foll, i );
          else
            Add( prox, i );
          fi;
        od;
      elif Length( linear ) = 3 then        # out = 4
        prox:= [];
        foll:= [];
        chi:= irreds[2];
        for i in [ 1 .. nccl ] do
          if chi[i] = -I then Add( foll, i ); else Add( prox, i ); fi;
        od;
      else
        prox:= [ 1 .. nccl ];
        foll:= [];
      fi;

      if d = 2 then
        # special case without Galois partners
        for chi in faith do
          for i in Adjust[1] do chi[i]:= - chi[i]; od;
          Add( irreds, chi );
          for lin in linear do
            ext:= List( [ 1 .. nccl ], x -> lin[x] * chi[x] );
            if not ext in irreds then Add( irreds, ext ); fi;
          od;
        od;
      elif d = 12 then
        # special case with three Galois partners and `lin = []'
        vals:= [ E(12)^7, - omegasquare, - I, E(3), E(12)^11, -1,
                 -E(12)^7, omegasquare, I, -E(3), -E(12)^11 ];
        for j in [ 1 .. Length( faith ) ] do
          chi:= faith[j];
          for i in [ 1 .. 11 ] do
            chi{ Adjust[i] }:= vals[i] * chi{ Adjust[i] };
          od;
          Add( irreds, chi );
          for i in partner[j] do
            Add( irreds, List( chi, x -> GaloisCyc( x, i ) ) );
          od;
        od;
      else

        if d = 3 then
          Adjust{ [ 1, 2 ] }:= [ Union( Intersection( Adjust[1], prox ),
                                        Intersection( Adjust[2], foll ) ),
                                 Union( Intersection( Adjust[2], prox ),
                                        Intersection( Adjust[1], foll ) ) ];
          vals:= [ E(3), E(3)^2 ];
        elif d = 4 then
          Adjust{ [ 1, 3 ] }:= [ Union( Intersection( Adjust[1], prox ),
                                        Intersection( Adjust[3], foll ) ),
                                 Union( Intersection( Adjust[3], prox ),
                                        Intersection( Adjust[1], foll ) ) ];
          vals:= [ I, -1, -I ];
        elif d = 6 then
          vals:= [ -E(3), omegasquare, -1, E(3), - omegasquare ];
        fi;

        for j in [ 1 .. Length( faith ) ] do
          chi:= faith[j];
          for i in [ 1 .. d-1 ] do
            chi{ Adjust[i] }:= vals[i] * chi{ Adjust[i] };
          od;
          Add( irreds, chi );
          for lin in linear do
            ext:= List( [ 1 .. nccl ], x -> lin[x] * chi[x] );
            if not ext in irreds then Add( irreds, ext ); fi;
          od;
          chi:= List( chi, x -> GaloisCyc( x, partner[j] ) );
          Add( irreds, chi );
          for lin in linear do
            ext:= List( [ 1 .. nccl ], x -> lin[x] * chi[x] );
            if not ext in irreds then Add( irreds, ext ); fi;
          od;
        od;

      fi;
    od;
    tbl.Irr:= irreds;
end );


#############################################################################
##
#F  ConstructProjInfo( <tbl>, <kernel> )
##
InstallGlobalFunction( ConstructProjInfo, function( tbl, kernel )

    local fusions,     # computed fusions of `tbl'
          fus,         # one fusion
          classes,     # class lengths of `tbl'
          orders,      # representative orders of `tbl'
          mult,        # order of the central subgroup `kernel'
          faithpos,    # position of a cyclic generator of the kernel
          nsg,         # class positions of subgroups of `kernel'
          faith,       # corresponding group orders
          names,       # names of factors by these subgroups
          fusrec,      # loop over fusions
          faithchars,  # faithful characters for each subgroup
          chi,         # loop over irreducibles of `tbl'
          ker,         # kernel of `chi'
          proj,
          nccl,
          linear,
          partners,
          i,
          new,
          gal,
          rest,
          projectives,
          info;

    # Get the factor fusions.
    fusions:= ComputedClassFusions( tbl );
    fus:= First( fusions, x -> ClassPositionsOfKernel( x.map ) = kernel );
    if fus = fail then
      Error( "factor fusion from <tbl> to <facttbl> must be stored" );
    fi;

    classes:= SizesConjugacyClasses( tbl );
    orders:= OrdersClassRepresentatives( tbl );

    mult:= Sum( classes{ kernel } );
    faithpos:= First( kernel, i -> orders[i] = mult );

    if    12 mod mult <> 0
       or faithpos = fail
       or Length( kernel ) <> mult then
      Error( "only cyclic central ext. by a group of order dividing 12" );
    fi;

    nsg:= Filtered( ClassPositionsOfNormalSubgroups( tbl ),
                    x -> IsSubset( kernel, x ) );
    faith:= List( nsg, l -> mult / Length( l ) );
    SortParallel( faith, nsg );
    if mult = 12 then
      nsg:= Permuted( nsg, (3,4) );
      faith:= Permuted( faith, (3,4) );
    fi;

    names:= [];
    for i in [ 1 .. Length( nsg )-1 ] do
      fusrec:= First( fusions,
                      r -> ClassPositionsOfKernel( r.map ) = nsg[i] );
      if fusrec = fail then
        Error( "factor fusion with kernel ", nsg[i], " not stored" );
      fi;
      names[i]:= fusrec.name;
    od;
    names[ Length( nsg ) ]:= Identifier( tbl );

    # Distribute the irreducibles according to their kernels.
    # Take only those irreducibles
    # of  $3.G$ with value `E(3)' times the degree on the first nonid. class,
    # of  $4.G$ with value `E(4)' times the degree on the first nonid. class,
    # of  $6.G$ with value `E(6)' times the degree on the first nonid. class,
    # of $12.G$ with value `E(12)^7' times the deg. on the first nonid. class,
    faithchars:= List( nsg, l -> [] );
    for chi in Irr( tbl ) do
      ker:= ClassPositionsOfKernel( chi );
      for i in [ 1 .. Length( nsg ) ] do
        if IsSubset( ker, nsg[i] ) then

          if      faith[i] <= 2
             or ( faith[i] =  3 and chi[ faithpos ] = E(3) * chi[1] )
             or ( faith[i] =  4 and chi[ faithpos ] = E(4) * chi[1] )
             or ( faith[i] =  6 and chi[ faithpos ] = E(6) * chi[1] )
             or ( faith[i] = 12 and chi[ faithpos ] = E(12)^7 * chi[1] ) then
            Add( faithchars[i], chi );
            break;
          fi;

        fi;
      od;
    od;

    # Remove characters obtained by multiplication with linear characters
    # of the factor group,
    # and create the result info.
    proj:= ProjectionMap( fus.map );
    nccl:= Length( proj );
    linear:= List( Filtered( faithchars[1], chi -> chi[1] = 1 ),
                   lambda -> lambda{ proj } );
    projectives:= [];
    info:= [ [ names[1], [] ] ];

    for i in [ 2 .. Length( nsg ) ] do

      new:= [];
      gal:= [];
      for chi in faithchars[i] do
        rest:= chi{ proj };
        if ForAll( linear, lambda -> not List( [ 1 .. nccl ],
               j -> lambda[j] * rest[j] ) in new ) then
          Add( new, rest );
          if 2 < faith[i] then
            partners:= GaloisPartnersOfIrreducibles( tbl, [ chi ], faith[i] );
            if faith[i] <> 12 then
              partners:= partners[1];
            fi;
            Append( gal, partners );
#T works for 12 ??
          fi;
        fi;
      od;
      info[ faith[i] ]:= [ names[i], gal ];
      Add( projectives, rec( name:= names[i], chars:= new ) );

    od;

    # Return the result.
    return rec( projectives := projectives,
                info        := info         );
end );


#############################################################################
##
#F  ConstructDirectProduct( <tbl> )
#F  ConstructDirectProduct( <tbl>, <permclasses>, <permchars> )
##
InstallGlobalFunction( ConstructDirectProduct, function( arg )

    local tbl, t, i;

    tbl:= arg[1];
    t:= CallFuncList( CharacterTableFromLibrary, tbl.factors[1] );
    for i in [ 2 .. Length( tbl.factors ) ] do
      t:= CharacterTableDirectProduct( t,
              CallFuncList( CharacterTableFromLibrary, tbl.factors[i] ) );
    od;
    if 1 < Length( arg ) then
      t:= CharacterTableWithSortedClasses( t, arg[2] );
      t:= CharacterTableWithSortedCharacters( t, arg[3] );
      if not IsBound( tbl.ClassPermutation ) then
        tbl.ClassPermutation:= ();
      fi;
    fi;
    TransferComponentsToLibraryTableRecord( t, tbl );
    if 1 < Length( tbl.factors ) then
      Append( tbl.ComputedClassFusions, ComputedClassFusions( t ) );
    fi;
end );


#############################################################################
##
#F  ConstructSubdirect( <tbl>, <factors>, <choice> )
##
InstallGlobalFunction( ConstructSubdirect, function( tbl, factors, choice  )

    local t, i;

    t:= CallFuncList( CharacterTableFromLibrary, factors[1] );
    for i in [ 2 .. Length( factors ) ] do
      t:= CharacterTableDirectProduct( t,
              CallFuncList( CharacterTableFromLibrary, factors[i] ) );
    od;
    t:= CharacterTableOfNormalSubgroup( t, choice );
    TransferComponentsToLibraryTableRecord( t, tbl );
end );


#############################################################################
##
#F  ConstructIsoclinic( <tbl> )
#F  ConstructIsoclinic( <tbl>, <nsg> )
##
InstallGlobalFunction( ConstructIsoclinic, function( arg )

    local tbl, t, i, fld;

    tbl:= arg[1];
    t:= CallFuncList( CharacterTableFromLibrary, tbl.factors[1] );
    for i in [ 2 .. Length( tbl.factors ) ] do
      t:= CharacterTableDirectProduct( t,
              CallFuncList( CharacterTableFromLibrary, tbl.factors[i] ) );
    od;
    if Length( arg ) = 1 then
      t:= CharacterTableIsoclinic( t );
    else
      t:= CharacterTableIsoclinic( t, arg[2] );
    fi;
    TransferComponentsToLibraryTableRecord( t, tbl );
end );


#############################################################################
##
#F  ConstructV4G( <tbl>, <facttbl>, <aut>[, <ker>] )
##
InstallGlobalFunction( ConstructV4G, function( arg )

    local tbl, facttbl, aut, ker, fus, chars;

    tbl:= arg[1];
    facttbl:= arg[2];
    aut:= arg[3];
    ker:= 2;
    if Length( arg ) = 4 then
      ker:= arg[4];
    fi;
    fus:= First( tbl.ComputedClassFusions, fus -> fus.name = facttbl ).map;
    ker:= Position( fus, ker );
    facttbl:= CharacterTableFromLibrary( facttbl );
    tbl.Irr:= List( Irr( facttbl ), x -> ValuesOfClassFunction( x ){ fus } );
    chars:= List( Filtered( tbl.Irr, x -> x[1] <> x[ ker ] ),
                  x -> Permuted( x, aut ) );
    Append( tbl.Irr, chars );
    Append( tbl.Irr, List( chars, x -> Permuted( x, aut ) ) );
end );


#############################################################################
##
#F  InducedLibraryCharacters( <subtbl>, <tblrec>, <chars>, <fusionmap> )
##
##  is the list of class function values lists
##
InstallGlobalFunction( InducedLibraryCharacters,
    function( subtbl, tblrec, chars, fusion )

    local j,              # loop variables
          centralizers,   # centralizer orders in hte supergroup
          nccl,           # number of conjugacy classes of the group
          subnccl,        # number of conjugacy classes of the subgroup
          suborder,       # order of the subgroup
          subclasses,     # class lengths in the subgroup
          induced,        # list of induced characters, result
          singleinduced,  # one induced character
          char;           # one character to be induced

    centralizers:= tblrec.SizesCentralizers;
    nccl:= Length( centralizers );
    suborder:= Size( subtbl );
    subclasses:= SizesConjugacyClasses( subtbl );
    subnccl:= Length( subclasses );

    induced:= [];

    for char in chars do

      # preset the character with zeros
      singleinduced:= ListWithIdenticalEntries( nccl, 0 );

      # add the contribution of each class of the subgroup
      for j in [ 1 .. subnccl ] do
        if char[j] <> 0 then
          singleinduced[ fusion[j] ]:= singleinduced[ fusion[j] ]
                                   + char[j] * subclasses[j];
        fi;
      od;

      # adjust the values by multiplication
      for j in [ 1 .. nccl ] do
        singleinduced[j]:= singleinduced[j] * centralizers[j] / suborder;
      od;

      Add( induced, singleinduced );

    od;

    # Return the list of values lists.
    return induced;
end );


#############################################################################
##
#F  ConstructGS3( <tbls3>, <tbl2>, <tbl3>, <ind2>, <ind3>, <ext>, <perm> )
##
InstallGlobalFunction( ConstructGS3,
    function( tbls3, tbl2, tbl3, ind2, ind3, ext, perm )

    local fus2,       # fusion map `tbl2' in `tbls3'
          fus3,       # fusion map `tbl3' in `tbls3'
          proj2,      # projection $G.S3$ to $G.2$
          pos,        # position in `proj2'
          proj2i,     # inner part of projection $G.S3$ to $G.2$
          proj2o,     # outer part of projection $G.S3$ to $G.2$
          proj3,      # projection $G.S3$ to $G.3$
          zeroon2,    # zeros for part of $G.2 \setminus G$ in $G.S_3$
          irr,        # irreducible characters of `tbls3'
          irr3,       # irreducible characters of `tbl3'
          irr2,       # irreducible characters of `tbl2'
          i,          # loop over `ind2'
          pair,       # loop over `ind3' and `ext'
          chi,        # character
          chii,       # inner part of character
          chio;       # outer part of character

    tbl2:= CharacterTableFromLibrary( tbl2 );
    tbl3:= CharacterTableFromLibrary( tbl3 );

    fus2:= First( ComputedClassFusions( tbl2 ),
                  fus -> fus.name = tbls3.Identifier ).map;
    fus3:= First( ComputedClassFusions( tbl3 ),
                  fus -> fus.name = tbls3.Identifier ).map;

    proj2:= ProjectionMap( fus2 );
    pos:= First( [ 1 .. Length( proj2 ) ], x -> not IsBound( proj2[x] ) );
    proj2i:= proj2{ [ 1 .. pos-1 ] };
    pos:= First( [ pos .. Length( proj2 ) ], x -> IsBound( proj2[x] ) );
    proj2o:= proj2{ [ pos .. Length( proj2 ) ] };
    proj3:= ProjectionMap( fus3 );

    zeroon2:= Zero( Difference( [ 1 .. Length( tbls3.SizesCentralizers ) ],
                    fus3 ) );

    # Induce the characters given by `ind2' from `tbl2'.
    irr:= InducedLibraryCharacters( tbl2, tbls3, Irr( tbl2 ){ ind2 }, fus2 );

    # Induce the characters given by `ind3' from `tbl3'.
    irr3:= List( Irr( tbl3 ), ValuesOfClassFunction );
    Append( irr, List( ind3,
        pair -> Concatenation( Sum( irr3{ pair } ){ proj3 }, zeroon2 ) ) );

    # Put the extensions from `tbl' together.
    irr2:= List( Irr( tbl2 ), ValuesOfClassFunction );
    for pair in ext do
      chii:= irr3[ pair[1] ]{ proj3 };
      chio:= irr2[ pair[2] ]{ proj2o };
      Add( irr, Concatenation( chii,  chio ) );
      Add( irr, Concatenation( chii, -chio ) );
    od;

    # Permute the characters with `perm'.
    irr:= Permuted( irr, perm );

    # Store the irreducibles.
    tbls3.Irr:= irr;
end );


#############################################################################
##
#F  ConstructGS3Info( <tbl2>, <tbl3>, <tbls3> )
##
InstallGlobalFunction( ConstructGS3Info, function( tbl2, tbl3, tbls3 )

    local irr2,        # irreducible characters of `tbl2'
          irr3,        # irreducible characters of `tbl3'
          irrs3,       # irreducible characters of `tbls3'
          ind,         # list of induced characters
          ind2,        # positions of irreducible characters of `tbl2'
                       # inducing irreducibly to `tbls3'
          oldind,      # auxiliary list
          i,           # loop over positions in `ind'
          pos,         # position in `ind' or `irr3'
          ind3,        # positions of pairs of irreducible characters of
                       # `tbl3' inducing irreducibly to `tbls3'
          ext,         # list of pairs corresponding to irreducibles of
                       # `tbls3' that are extensions from `tbl2' and `tbl3'
          chi,         # loop over `irrs3'
          pos2,        # position in `irr2'
          rest,        # one restricted character
          irr,
          fus3,
          proj3,
          zeroon2,
          proj2,
          proj2o,
          pair,
          chii,
          chio,
          perm;

    irr2  := Irr( tbl2 );
    irr3  := Irr( tbl3 );
    irrs3 := Irr( tbls3 );

    ind:= Induced( tbl2, tbls3, Irr( tbl2 ) );
    ind2:= Filtered( [ 1 .. Length( ind ) ],
                     i -> Position( ind, ind[i] ) = i and ind[i] in irrs3 );
    oldind:= ind;

    ind:= Induced( tbl3, tbls3, Irr( tbl3 ) );
    ind3:= [];
    for i in [ 1 .. Length( ind ) ] do
      if ind[i] in irrs3 and not ind[i] in oldind then
        pos:= Position( ind, ind[i] );
        if pos <> i then
          Add( ind3, [ pos, i ] );
        fi;
      fi;
    od;

    ext:= [];
    for chi in irrs3 do
      rest:= Restricted( tbls3, tbl3, [ chi ] )[1];
      pos:= Position( irr3, rest );
      if pos <> fail and ForAll( ext, x -> x[1] <> pos ) then
        rest:= Restricted( tbls3, tbl2, [ chi ] )[1];
        pos2:= Position( irr2, rest );
        if pos2 <> fail then
          Add( ext, [ pos, pos2 ] );
        fi;
      fi;
    od;

    # Put the characters together, for computing the necessary permutation.
    # (Use the same code as in `ConstructGS3'.
    irr:= Induced( tbl2, tbls3, Irr( tbl2 ){ind2} );
    fus3:= GetFusionMap( tbl3, tbls3 );
    proj3:= ProjectionMap( fus3 );
    zeroon2:= Zero( Difference( [ 1 .. NrConjugacyClasses( tbls3 ) ],
                                fus3 ) );
    proj2:= ProjectionMap( GetFusionMap( tbl2, tbls3 ) );
    pos:= First( [ 1 .. Length( proj2 ) ], x -> not IsBound( proj2[x] ) );
    pos:= First( [ pos .. Length( proj2 ) ], x -> IsBound( proj2[x] ) );
    proj2o:= proj2{ [ pos .. Length( proj2 ) ] };
    Append( irr, List( ind3,
        pair -> Concatenation( Sum( irr3{ pair } ){ proj3 }, zeroon2 ) ) );
    for pair in ext do
      chii := irr3[pair[1]]{proj3};
      chio := irr2[pair[2]]{proj2o};
      Add( irr, Concatenation( chii, chio ) );
      Add( irr, Concatenation( chii, - chio ) );
    od;
    perm := Sortex( irr ) / Sortex( ShallowCopy( Irr( tbls3 ) ) );

    # Return the result.
    return rec( ind2:= ind2, ind3:= ind3, ext:= ext, perm := perm );
    end );


#############################################################################
##
#F  ConstructPermuted( <tbl>, <libnam>, <prmclasses>, <prmchars> )
##
##  The library table <tbl> is completed with help of the library table with
##  name <libnam>, whose classes and characters must be permuted by the
##  permutations <prmclasses> and <prmchars>, respectively.
##
InstallGlobalFunction( ConstructPermuted,
    function( tbl, libnam, prmclasses, prmchars )

    local t;

    # There may be fusions into `tbl',
    # so we must guarantee a trivial class permutation.
    if not IsBound( tbl.ClassPermutation ) then
      tbl.ClassPermutation:= ();
    fi;

    # Get the permuted table.
    t := CallFuncList( CharacterTableFromLibrary, libnam );
    if prmclasses <> () then
      t:= CharacterTableWithSortedClasses( t, prmclasses );
    fi;
    if prmchars <> () then
      t:= CharacterTableWithSortedCharacters( t, prmchars );
    fi;

    # Store the components in `tbl'.
    TransferComponentsToLibraryTableRecord( t, tbl );
end );


#############################################################################
##
#F  ConstructFactor( <tbl>, <libnam>, <kernel> )
##
##  The library table <tbl> is completed with help of the library table with
##  name <libnam>, whose classes and characters must be permuted by the
##  permutations <prmclasses> and <prmchars>, respectively.
##
InstallGlobalFunction( ConstructFactor,
    function( tbl, libnam, kernel )
    local t;

    # Construct the required table of the factor group.
    t:= CharacterTableFactorGroup( CallFuncList( CharacterTableFromLibrary,
                                                 libnam ),
                                   kernel );

    # Store the components in `tbl'.
    TransferComponentsToLibraryTableRecord( t, tbl );
end );


#############################################################################
##
#F  ConstructClifford( <tbl> )
##
InstallGlobalFunction( ConstructClifford, function( tbl )

    local i, j, n,
          AnzTi,
          tables,
          ct,        # list of lists of relevant characters,
                     # one for each inertia factor group
          clmexp,
          clmat,
          matsize,
          grps,
          newct,     # the list of irreducibles of `tbl'
          rowct,     # actual row
          colct,     # actual column
          eintr,
          chars,
          linear,
          chi,       # loop over a character list
          lin,
          new;

    # Decode the `cliffordTable' component of `tbl'.
    tbl.cliffordTable:= rec( Ti:= rec( fusions:= tbl.cliffordTable[1],
                                        tables := tbl.cliffordTable[2] ),
                             cliffordrecords:= tbl.cliffordTable[3] );
    tbl.cliffordTable.Ti.ident:= StructuralCopy( tbl.cliffordTable.Ti.tables );
#T really ?

    # Get the character tables of the inertia groups,
    # and store the relevant list of characters.
    tables:= tbl.cliffordTable.Ti.tables;
    AnzTi:= Length( tables );
    ct:= [];
    for i in [ 1 .. AnzTi ] do
      if tables[i][1] = "projectives" then
        eintr:= CharacterTableFromLibrary( tables[i][2] );
      else
        eintr:= CallFuncList( CharacterTableFromLibrary, tables[i] );
      fi;
      if eintr = fail then
        Error( "table of inertia factor group `", tables[i],
               "' not in the library" );
      fi;
      if tables[i][1] = "projectives" then

        # We must multiply the stored projectives with all linear characters
        # of the factor group in order to get the full list.
        chars:= First( eintr!.projectives, x -> x.name = tables[i][3] ).chars;
        ct[i]:= [];
        linear:= List( Filtered( Irr( eintr ), x -> x[1] = 1 ),
                       ValuesOfClassFunction );
        n:= Length( Irr( eintr ) );
#T ?
        for chi in chars do
          for lin in linear do
            new:= List( [ 1 .. n ], x -> chi[x] * lin[x] );
            if not new in ct[i] then
              Add( ct[i], new );
            fi;
          od;
        od;

      else
        ct[i]:= List( Irr( eintr ), ValuesOfClassFunction );
      fi;
      tables[i]:= eintr;
    od;

    # Construct the matrix of irreducibles characters.
    newct := List( tbl.SizesCentralizers, x -> [] );
    colct := 0;

    for i in tbl.cliffordTable.cliffordrecords do

      # Get the necessary components of the `i'-th Clifford matrix,
      # and multiply it with the character tables of inertia factor groups.

      clmexp  := UnpackedCll( i );
      clmat   := clmexp.mat;
      matsize := Length( clmat );
      grps    := clmexp.inertiagrps;

      # Loop over the columns of the matrix.
      for n in [ 1 .. matsize ] do

        rowct := 0;
        colct := colct + 1;

        # Loop over the inertia factor groups.
        for j in [ 1 .. AnzTi ] do
          for chi in ct[j] do
            rowct:= rowct + 1;
            newct[rowct][colct]:= Sum( Filtered( [ 1 .. matsize ],
                                                 r -> grps[r] = j ),
               x -> clmat[x][n] * chi[ clmexp.fusionclasses[x] ]);
#T Eventually it should be possible to handle tables where not all
#T classes belonging to a Clifford matrix are expected to be
#T subsequent ...
#T (add an indirection by the fusion)
          od;
        od;

      od;

    od;

    tbl.Irr := newct;
end );


#############################################################################
##
#F  UnpackedCll( <cll> )
##
InstallGlobalFunction( UnpackedCll, function( cll )

    local l, clmlist,  # library list of the possible matrices
          clf,         # Clifford matrix record, result
          pi;          # permutation to sort library matrices

    # Initialize the Clifford matrix record.
    clf:= rec(
               inertiagrps   := cll[1],
               fusionclasses := cll[2]
              );

    if Length( cll[2] ) = 1 then

      clf.mat:= [ [ 1 ] ];

    elif Length( cll[3] ) = 2 then

      # is already unpacked, for example dimension 2
      clf.mat:= cll[3];

    else

      # Fetch the matrix from the library.
      cll:= cll[3];
      clf.libname:= cll;
      l:= cll[2];
      clmlist:= LibraryTables( Concatenation( "clm", cll[1] ) );
      if clmlist = fail or not IsBound( clmlist[l] ) then
        Error( "sorry, component <mat> not found in the library" );
      fi;

      clf.mat:= List( clmlist[l][ cll[3] ], ShallowCopy );

      # Sort the rows and columns of the Clifford matrix
      # w.r.t. the explicitly given permutations.
      if IsBound( cll[4] ) then
        clf.mat:= Permuted( clf.mat, cll[4] );
      fi;
      if IsBound( cll[5] ) then
        pi:= cll[5];
        clf.mat:= List( clf.mat, x -> Permuted( x, pi ) );
      fi;

    fi;

    return clf;
end );


#############################################################################
##
#F  CllToClf( <tbl>, <cll> )
##
InstallGlobalFunction( CllToClf, function( tbl, cll )

    local Ti,          #
          factor,      # character table of the factor group G/N
          classnames,
          i, nr,
          dim,         # dimension of the matrix
          clf,         # expanded record
          pos,
          map;

    Ti:= tbl!.cliffordTable.Ti;
    factor:= Ti.tables[1];
    classnames:= ClassNames( factor );

    nr:= cll[2][1];
    dim:= Length( cll[2] );

    # Decode `cll'.
    clf:= UnpackedCll( cll );

    # Fill the Clifford matrix record.
    clf.nr     := nr;
    clf.size   := dim;
    clf.order  := factor.orders[nr];
    clf.orders := [ factor.orders[nr] ];
    clf.elname := classnames[nr];
    clf.full   := true;

    # Compute the row weights $b_a = |C_{T_m/N}(gN)|$.
    clf.roww:= List( [ 1 .. dim ],
        i -> SizesCentralizers( Ti.tables[ cll[1][i] ] )[ cll[2][i] ] );

    # Compute the column weights $m_k = |Cl_{G/N}(gN)| / |Cl_G(g_k)|$.
    pos:= 0;
    for map in Ti.fusions do
      pos:= pos + Number( map, x -> x < nr );
    od;
    clf.colw:= List( [ 1 .. dim ],
                     i -> SizesConjugacyClasses( tbl )[ pos+i ] /
                          SizesConjugacyClasses( factor )[nr] );
#T !!

#     if dim = 1 then
#       if IsBound( cll[4] ) then
#         clf.colw := [cll[4][2]];
#       else
#         clf.colw := [1];
# #T ??
#       fi;
#     elif dim = 2 then
#
#         factor:= Ti.tables[ clf.inertiagrps[2] ];
#         if not IsCharacterTable( factor ) then
#           factor:= CallFuncList( CharacterTableFromLibrary, factor );
#         fi;
#
#         if IsBound( cll[4] )  then
#             if cll[4][1] = 0 then #not really splitted
#                 clf.colw := cll[4][2]*[1, clf.roww[1]/clf.roww[2]];
#                 clf.mat:= [[1,1],[clf.roww[1]/clf.roww[2],-1]];
#             else
#                 clf.colw := [ 1, cll[4][2]-1 ];
#                 clf.mat:= [[1,1],[cll[4][4]*clf.colw[2],-cll[4][4]]];
#             fi;
#         else
#             clf.colw := [1, clf.roww[1]/clf.roww[2]];
#             clf.mat:= [[1,1],[clf.colw[2],-1]];
# #T but this holds only for split cosets!
#         fi;
#     fi;

    # Handle the special case of extraspecial groups.
    if Length( cll ) = 4 then
      clf.splitinfos:= rec( classindex := cll[4][1],
                            p          := cll[4][2] );
      if IsBound( cll[4][3] ) then
        clf.splitinfos.numclasses:= cll[4][3];
      fi;
      if IsBound( cll[4][4] ) then
        clf.splitinfos.root:= cll[4][4];
      fi;
    fi;

    return clf;
end );


#############################################################################
##
#F  OfThose()
#F  IsSporadicSimple( <G> )
#F  SchurCover()
##
InstallGlobalFunction( OfThose,
    function( ) Error("this is just a dummy function" ); end );
InstallGlobalFunction( IsSporadicSimple,
    function(G) Error("this is just a dummy function" ); end );
# SchurCover now is a globally declared operation.
#InstallGlobalFunction( SchurCover,
#    function( ) Error("this is just a dummy function" ); end );


#############################################################################
##
#F  AllCharacterTableNames( {<func>, <val>} )
#F  AllCharacterTableNames( <func>, <val>, ...{, OfThose, <func>} )
##
InstallGlobalFunction( AllCharacterTableNames, function( arg )

    local sporsimp, list, pos, i, j, t, pp, oft, funcs, resul, name, ok,
          newlist, multinfo, autoinfo, simpinfo;

    if IsEmpty( arg ) then

      # all table names in the library
      return ShallowCopy( LIBLIST.firstnames );

    fi;

    # table names of sporadic simple groups
    # (sorted according to size)

    sporsimp:= LIBLIST.sporadicSimple;
    multinfo:= List( LIBLIST.simpleInfo, x -> x[1] );
    simpinfo:= List( LIBLIST.simpleInfo, x -> x[2] );
    autoinfo:= List( LIBLIST.simpleInfo, x -> x[3] );

    # initialize the names list;
    # supported special cases up to now are `IsSimple', `IsSporadicSimple'

    if     ( arg[1] = IsSimple or arg[1] = IsSimpleGroup
                               or arg[1] = IsSimpleCharacterTable )
       and arg[2] = true then
      list:= simpinfo;
#T careful:
#T in the case of user extensions, loop also over these files!!
    elif arg[1] = IsSporadicSimple and arg[2] = true then
      list:= ShallowCopy( sporsimp );
    elif arg[1] = OfThose then
      list:= LIBLIST.firstnames;
      pos:= 1;
    else
      list:= [];
      for i in LIBLIST.firstnames do
        t:= CharacterTableFromLibrary( i );
        resul:= arg[1]( t );
        if resul = arg[2] or ( IsList( arg[2] ) and resul in arg[2] ) then
          Add( list, t );
        fi;
      od;
    fi;

    # now there are two possibilities:
    # Either one filters the current list `list',
    # or we reach an `OfThose', so we replace each entry of `list' by
    # the list of images under the mapping instruction after `OfThose'
    if not IsBound( pos ) then
      pos:= 3;
    fi;
    while pos <= Length( arg ) do

      oft:= Position( arg, OfThose, pos - 1 );
      if oft = fail then
        oft:= Length( arg ) + 1;
      fi;

      # filter between each two `OfThose' mappings
      if pos <= oft - 2 then
        newlist:= [];
        for i in list do
          if IsCharacterTable( i ) then
            t:= i;
          else
            t:= CharacterTable( i );
          fi;
          ok:= true;
          for j in [ pos, pos + 2 .. oft - 2 ] do
            resul:= arg[j]( t );
            if not( resul = arg[ j+1 ]
                    or ( IsList( arg[ j+1 ] ) and resul in arg[ j+1 ] ) ) then
              ok:= false;
              break;
            fi;
          od;
          if ok then
            Add( newlist, t );
          fi;
        od;
        list:= newlist;
      fi;

      if Length( arg ) > oft then

        # mapping instruction `OfThose',
        # supported special cases are
        # `SchurCover', `AutomorphismGroup'.
        newlist:= [];

        if   arg[ oft + 1 ] = SchurCover then

          for i in list do

            if IsString( i ) then
              pp:= Position( simpinfo, i );
            else
              pp:= Position( simpinfo, Identifier( i ) );
            fi;
            if pp = fail then
              Error( "no info about Schur multiplier of `", i,
                     "' stored" );
            fi;
            if multinfo[ pp ] = "" then
              Add( newlist, simpinfo[ pp ] );
            else
              Add( newlist, Concatenation( multinfo[ pp ], ".",
                                           simpinfo[ pp ] ) );
            fi;

          od;

        elif arg[ oft + 1 ] = AutomorphismGroup then

          for i in list do

            if IsString( i ) then
              pp:= Position( simpinfo, i );
            else
              pp:= Position( simpinfo, Identifier( i ) );
            fi;
            if pp = fail then
              Error( "no info about automorphism group of `", i,
                     "' stored" );
            fi;
            if autoinfo[ pp ] = "" then
              Add( newlist, simpinfo[ pp ] );
            else
              Add( newlist, Concatenation( simpinfo[ pp ], ".",
                                           autoinfo[ pp ] ) );
            fi;

          od;

        else

          for i in list do
            if IsString( i ) then
              resul:= arg[ oft+1 ]( CharacterTable( i ) );
            else
              resul:= arg[ oft+1 ]( i );
            fi;
            if IsString( resul ) then
              AddSet( newlist, resul );
            elif ForAll( resul, IsString ) then
              UniteSet( newlist, resul );
            else
              Error( "<arg>[ oft+1 ] must return a (list of) strings" );
            fi;
          od;

        fi;

        list:= newlist;

      fi;

      pos:= oft + 2;

    od;

    for i in [ 1 .. Length( list ) ] do
      if IsCharacterTable( list[i] ) then
        list[i]:= Identifier( list[i] );
      fi;
    od;

    return list;
end );


#T #############################################################################
#T ##
#T #F  ShrinkClifford( <tbl> )
#T ##
#T InstallGlobalFunction( ShrinkClifford, function( tbl )
#T 
#T     local i, flds, cltbl;
#T 
#T     cltbl:= tbl!.cliffordTable;
#T     cltbl.Ti.tables := cltbl.Ti.ident;
#T 
#T     cltbl.cliffordrecords:= [];
#T 
#T     for i in  [1..cltbl.size] do
#T 
#T       cltbl.cliffordrecords[i]:= ClfToCll( cltbl.(i) );
#T       Unbind( cltbl.(i) );
#T 
#T     od;
#T 
#T     Unbind( tbl.irreducibles);
#T #T how to remove attributes ??
#T     Unbind( cltbl.Ti.ident );
#T     Unbind( cltbl.Ti.expN );
#T 
#T     for flds in [ "name", "grpname", "elements", "isDomain", "operations",
#T                   "charTable", "size", "expN" ] do
#T       Unbind( cltbl.(flds) );
#T     od;
#T end );


#############################################################################
##
#F  TextString( <text> )
##
InstallGlobalFunction( TextString, function( text )
    local str, start, stop, line, len, pos;
    str:=  "[\n\"";
    stop:= 1;
    len:= Length( text );
    while stop <= len do
      start:= stop;
      while stop <= len and text[stop] <> '\n' do
        stop:= stop + 1;
      od;
      line:= text{ [ start .. stop-1 ] };
      pos:= Position( line, '\"' );
      while pos <> fail do
        line:= Concatenation( line{ [ 1 .. pos-1 ] },
               "\\\"", line{ [ pos+1 .. Length( line ) ] } );
        pos:= Position( line, '\"', pos + 1 );
      od;
      Append( str, line );
      if stop <= len then
        Append( str, "\\n\",\n\"" );
        stop:= stop+1;     # skip the '\n'
      fi;
    od;
    Append( str, "\"\n]" );
    return str;
end );


#############################################################################
##
#F  ShrinkChars( <chars> )
##
InstallGlobalFunction( ShrinkChars, function( chars )
    local i, j, k, N, oldchars, linear, chi, fams, pos, ppos;

    linear:= Filtered( chars, x -> x[1] = 1 );
    fams:= GaloisMat( chars ).galoisfams;
    chars:=    ShallowCopy( chars );
    oldchars:= ShallowCopy( chars );

    if Length( linear ) > 1 then
      ppos:= List( linear, x -> Position( chars, x ) );
      for i in [ 1 .. Length( chars ) ] do
        chi:= chars[i];
        if not IsString( chi ) then
          for j in [ 1 .. Length( linear ) ] do
            pos:= Position( chars, Tensored( [ linear[j] ],[ chi ] )[1] );
            if pos <> fail and pos > i and pos > ppos[j] then
              chars[ pos ]:= Concatenation( "\n[TENSOR,[",
                                  String(i),",",String( ppos[j] ),"]]");
            fi;
          od;
        fi;
      od;
    fi;

    for i in [ 1 .. Length( chars ) ] do
      if IsList( fams[i] ) then
        for j in [ 2 .. Length( fams[i][1] ) ] do
          if fams[i][1][j] <= Length( chars ) then
            chi:= chars[ fams[i][1][j] ];
            if IsClassFunction( chi ) then
              chi:= ValuesOfClassFunction( chi );
            fi;
            if not IsString( chi ) then
              N:= Conductor( chi );
              k:= First( [ 2..N ], x -> chi = List( oldchars[i],
                                                    y -> GaloisCyc(y,x) ) );
              chars[ fams[i][1][j] ]:= Concatenation("\n[GALOIS,[",
                                               String(i),",",String(k),"]]");
            fi;
          fi;
        od;
      fi;
    od;

    return chars;
end );


#T #############################################################################
#T ##
#T #F  ClfToCll( <clf> )
#T ##
#T InstallGlobalFunction( ClfToCll, function( clf )
#T 
#T     local p,       # position of the Clifford matrix clm in CLM[*]
#T           cll,     # compressed record
#T           clm,     # the pure Clifford matrix consisting of "mat" and "colw"
#T           clmlist, # list of stored cliffordrecords
#T           l,
#T           lname,   # name of item in the library
#T           list,    #
#T           tr;
#T 
#T     # Check the input.
#T     if not IsRecord( clf ) or
#T        not IsBound( clf.inertiagrps ) or
#T        not IsBound( clf.fusionclasses ) or
#T        not IsBound( clf.mat ) then
#T       Error( "<clf> must be record with components `inertiagrps', `mat' ",
#T              "and `fusionclasses'" );
#T     fi;
#T 
#T     l:= Length( clf.mat[1] );
#T     cll:= [ clf.inertiagrps, clf.fusionclasses ];
#T 
#T     if IsBound( clf.splitinfos )  then
#T       lname := "exsp";
#T       cll[4]:= [ clf.splitinfos.classindex, clf.splitinfos.p ];
#T       if IsBound( clf.splitinfos.numclasses ) then
#T         cll[4][3]:= clf.splitinfos.numclasses;
#T       fi;
#T       if IsBound( clf.splitinfos.root ) then
#T         cll[4][4]:= clf.splitinfos.root;
#T       fi;
#T     else
#T       lname := "elab";
#T     fi;
#T 
#T     if l = 2  then
#T 
#T       # Store the full matrix.
#T       cll[3]:= clf.mat;
#T 
#T     elif 2 < l then
#T 
#T       clm:= clf.mat;
#T       cll[3]:= clm;
#T 
#T       # Try to find the matrix in the library of Clifford matrices.
#T       clmlist := LibraryTables( Concatenation( "clm", lname ) );
#T       if not IsList( clmlist ) then
#T         Error( "#E ClfToCll: can't find library of Clifford matrices.\n" );
#T       fi;
#T 
#T       if IsBound( clmlist[l] ) then
#T 
#T         list:= clmlist[l];
#T         p:= Position( list, clm );
#T         if p <> fail then
#T 
#T           # Just store the library code.
#T           cll[3]:= [ lname, l, p ];
#T           return cll;
#T 
#T         else
#T 
#T           # The matrix itself is not in the library.
#T           # Perhaps it is contained up to permutations of rows/columns,
#T           # in this case print an appropriate message.
#T           for p in [ 1 .. Length( list ) ] do
#T 
#T             tr:= TransformingPermutations( clm, list[p] );
#T             if tr <> fail then
#T 
#T               # The matrix can be permuted to a library matrix.
#T               cll[3]:= [ lname, l, p ];
#T               if tr.rows <> () then
#T                 cll[3][4]:= tr.rows^-1;
#T               fi;
#T               if tr.columns <> () then
#T                 cll[3][5]:= tr.columns^-1;
#T               fi;
#T               return cll;
#T 
#T             fi;
#T 
#T           od;
#T 
#T           Print( "#I Clifford matrix not found in the library\n" );
#T 
#T # `clm' not found in library, either because given libname is wrong or
#T # the matrix must be added first by an authorized person.
#T # The order would be:
#T #           PrintClmsToLib( <file>, [clf] );
#T 
#T         fi;
#T       fi;
#T     fi;
#T 
#T     return cll;
#T end );


#############################################################################
##
#F  LibraryFusion( <name>, <fus> )
##
InstallGlobalFunction( LibraryFusion, function( name, fus )

    local string, linelen, i, str;

    if IsCharacterTable( name ) then
      name:= Identifier( name );
    fi;

    # Initialize the result string.
    string:= "";

    # Print the source and destination.
    Append( string, "ALF(\"" );
    Append( string, name );
    Append( string, "\",\"" );
    Append( string, fus.name );

    # Initialize the current position in the line.
    linelen:= Length( name ) + Length( fus.name ) + 11;

    # Add the values of the fusion map.
    Append( string, "\",[" );
    for i in [ 1 .. Length( fus.map ) - 1 ] do
      str:= String( fus.map[i] );
      if linelen + Length( str ) + 1 < 75 then
        linelen:= linelen + Length( str ) + 1;
      else
        Append( string, "\n" );
        linelen:= Length( str ) + 1;
      fi;
      Append( string, str );
      Append( string, "," );
    od;
    i:= Length( fus.map );
    str:= String( fus.map[i] );
    if linelen + Length( str ) + 1 < 75 then
      linelen:= linelen + Length( str ) + 1;
    else
      Append( string, "\n" );
      linelen:= Length( str ) + 1;
    fi;
    Append( string, str );
    Append( string, "]" );

    # If a text is bound, add it.
    if IsBound( fus.text ) then
      Append( string, "," );
      Append( string, TextString( fus.text ) );
    fi;
    Append( string, ");\n" );

    return string;
end );


#############################################################################
##
#F  ConsiderFactorBlocks( <tbl> )
##
##  (This is a utility for `PrintToLibBrauer'.)
##
##  Let <tbl> be the Brauer character table of a group $G$, say.
##  If the {\GAP} table library contains the tables of factor groups of $G$
##  by central subgroups
##  such that the irreducible characters of these factors precede the other
##  characters in <tbl> then the blocks of the factors can be omitted in the
##  library version of <tbl>, and references to the factors are stored
##  instead.
##
##  `ConsiderFactorBlocks' returns `0' if no such replacement is possible;
##  otherwise a record with components `faithful' and `info' is returned,
##  the former being the range of those irreducible characters of <tbl> that
##  are not covered by the factor groups, the latter being the list that
##  must be inserted as value of the `factorblocks' component in the library
##  version of <tbl>;
##  this is a list of pairs whose first entries are the names of the factor
##  tables, and the second entries are the offsets of numbers of basic set
##  characters.
##
##  Currently this special treatment of factor groups is supported only for
##  Brauer tables in the {\ATLAS} of Finite Groups.
##  This means that the central subgroups in question have order divisible
##  by $12$.
##
BindGlobal( "ConsiderFactorBlocks", function( tbl )

    local p,
          ordinary,
          factfus,
          classes,
          orders,
          pos,
          mult,
          sizes,
          kernels,
          i,
          kernel,
          size,
          name,
          facttbl,
          info,
          nonfaith1,
          nonfaith,
          1faith,
          ppos,
          2faith,
          nonfaith2,
          nonfaith4,
          4faith;

    # Get the factor fusions stored on the ordinary table.
    p:= UnderlyingCharacteristic( tbl );
    ordinary:= OrdinaryCharacterTable( tbl );
    factfus:= Filtered( ComputedClassFusions( ordinary ),
                  x -> Length( ClassPositionsOfKernel( x.map ) ) <> 1 );

    # Get the maximal cyclic normal subgroup (of order dividing $12$).
    classes:= SizesConjugacyClasses( ordinary );
    orders:= OrdersClassRepresentatives( ordinary );
    pos:= 0;
    mult:= 0;
    sizes:= List( factfus, x -> 0 );
    kernels:= [];
    for i in [ 1 .. Length( factfus ) ] do
      kernel:= ClassPositionsOfKernel( factfus[i].map );
      size:= Sum( classes{ kernel } );
      if 12 mod size = 0 and size in orders{ kernel } then
        sizes[i]:= size;
        kernels[i]:= kernel;
        if mult < size then
          pos:= i;
          mult:= size;
        fi;
      fi;
    od;
    if pos = 0 or ( mult mod p = 0 ) then
      return 0;
    fi;

    # Get the table of the factor group.
    name:= factfus[ pos ].name;
    facttbl:= CharacterTable( name );

    # Initialize the `factorblocks' info.
    info:= [ [ name, 0 ] ];
    nonfaith1:= Length( PrimeBlocks( facttbl, p ).defect );
    nonfaith:= nonfaith1;

    # If `mult' is a prime then we are done.
    if mult <> 3 and mult <> 2 then

      # Get the number of ordinary characters of `1.G'.
      1faith:= Maximum( factfus[ pos ].map );

      # Get the number of faithful ordinary characters of `2.G'.
      ppos:= First( [ 1 .. Length( factfus ) ],
                    i ->     sizes[i] = mult / 2
                         and IsSubset( kernels[ pos ], kernels[i] ) );
      2faith:= Maximum( factfus[ ppos ].map ) - 1faith;
      Add( info, [ factfus[ ppos ].name, 0 ] );
      facttbl:= CharacterTable( factfus[ ppos ].name );
      nonfaith2:= Length( PrimeBlocks( facttbl, p ).defect );
      nonfaith:= nonfaith2;

      # If `mult = 4' then we are done.
      if mult = 6 then

        # Consider `3.G'
        # (offset is the number of ordinary faithful characters of `2.G').
        ppos:= First( [ 1 .. Length( factfus ) ],
                      i ->     sizes[i] = 2
                           and IsSubset( kernels[ pos ], kernels[i] ) );
        Add( info, [ factfus[ ppos ].name, 2faith ] );
        facttbl:= CharacterTable( factfus[ ppos ].name );

        nonfaith:= Length( PrimeBlocks( facttbl, p ).defect )
                   + nonfaith2 - nonfaith1;

      elif mult = 12 then

        # Consider `4.G' (no offset).
        ppos:= First( [ 1 .. Length( factfus ) ],
                      i ->     sizes[i] = 3
                           and IsSubset( kernels[ pos ], kernels[i] ) );
        Add( info, [ factfus[ ppos ].name, 0 ] );
        facttbl:= CharacterTable( factfus[ ppos ].name );
        nonfaith4:= Length( PrimeBlocks( facttbl, p ).defect );

        # Get the number of faithful ordinary characters of `4.G'.
        4faith:= Maximum( factfus[ ppos ].map ) - 1faith - 2faith;

        # Consider `3.G' (offset is the number of
        # ordinary faithful characters of `2.G' and `4.G').
        ppos:= First( [ 1 .. Length( factfus ) ],
                      i ->     sizes[i] = 4
                           and IsSubset( kernels[ pos ], kernels[i] ) );
        Add( info, [ factfus[ ppos ].name, 2faith + 4faith ] );
        facttbl:= CharacterTable( factfus[ ppos ].name );

        # Consider `6.G' (offset is the number of
        # ordinary faithful characters of `4.G').
        ppos:= First( [ 1 .. Length( factfus ) ],
                      i ->     sizes[i] = 2
                           and IsSubset( kernels[ pos ], kernels[i] ) );
        Add( info, [ factfus[ ppos ].name, 4faith ] );
        facttbl:= CharacterTable( factfus[ ppos ].name );

        nonfaith:= Length( PrimeBlocks( facttbl, p ).defect )
                   + nonfaith4 - nonfaith2;

      fi;

    fi;

    # Return the ``shrink'' information.
    return rec( offset := nonfaith,
                info   := info );
end );


#############################################################################
##
#F  BlanklessPrintTo( <stream>, <obj>, <ncols>, <used>, <quote> )
##
InstallGlobalFunction( BlanklessPrintTo,
    function( stream, obj, ncols, used, quote )

    local PrintToEx, i, names, newstring, newstream, len;

    PrintToEx := function( arg )
      local len, entry;
      len:= Sum( arg, Length );
      if ncols < used + len then
        PrintTo( stream, "\n" );
        used:= 0;
      fi;
      for entry in arg do
        PrintTo( stream, entry );
      od;
      used:= used + len;
    end;

    if not IsEmptyString( obj ) and IsList( obj ) and IsEmpty( obj ) then
      if used + 2 > ncols then
        PrintTo( stream, "\n" );
        used:= 0;
      fi;
      PrintTo( stream, "[]" );
    elif IsString( obj ) then
      if quote then
        if '\n' in obj then
          PrintTo( stream, TextString( obj ) );
          used:= 1;
        else
          PrintToEx( "\"", obj, "\"" );
        fi;
      else
        PrintToEx( obj );
      fi;
    elif IsList( obj ) then
      PrintToEx( "[" );
      for i in [ 1 .. Length( obj ) - 1 ] do
        if IsBound( obj[i] ) then
          used:= BlanklessPrintTo( stream, obj[i], ncols, used, true );
        fi;
        PrintToEx( "," );
      od;
      if not IsEmpty( obj ) then
        used:= BlanklessPrintTo( stream, obj[ Length( obj ) ], ncols, used,
                                 true );
      fi;
      PrintToEx( "]" );
    elif IsRecord( obj ) then
      PrintToEx( "rec(" );
      names:= RecNames( obj );
      for i in [ 1 .. Length( names ) - 1 ] do
        PrintToEx( names[i], ":=" );
        used:= BlanklessPrintTo( stream, obj.( names[i] ), ncols, used,
                                 true );
        PrintToEx( ",\n" );
        used:= 0;
      od;
      if not IsEmpty( names  ) then
        i:= Length( names );
        PrintToEx( names[i], ":=" );
        used:= BlanklessPrintTo( stream, obj.( names[i] ), ncols, used,
                                 true );
      fi;
      PrintToEx( ")" );
    elif IsChar( obj ) then
      PrintToEx( [ '\'', obj, '\'' ] );
    else

      newstring:= "";
      newstream:= OutputTextString( newstring, true );
      PrintTo( newstream, obj );
      CloseStream( newstream );
      len:= Length( newstring );
      if ncols < used + len then
        PrintTo( stream, "\n" );
        used:= 0;
      fi;
      PrintTo( stream, newstring );
      used:= used + len;

    fi;
    return used;
end );


#############################################################################
##
#F  PrintToLibOrdinary( <file>, <tbl> )
#F  PrintToLibBrauer( <file>, <tbl> )
#F  PrintToLib( <file>, <tbl> )
##
BindGlobal( "PrintToLibOrdinary", function( file, tbl )

    local special,
          ncols,
          used,
          string,
          stream,
          flds,
          i, j,
          name,
          powermap,
          primes,
          tblinfo,
          chars,
          fld,
          info,
          newirredinfo,
          fus,
          names,
          linelen,
          done,
          libinfo,
          maxes,
          pos;

    # Special cases are `Irr( tbl )' and `tbl!.projectives' since
    # after the call of `ShrinkChars' they may
    # contain strings, which shall be printed without `"'
    special:= function( chars, used )
        local j;
        used:= BlanklessPrintTo( stream, "[", ncols, used, false );
        for j in [ 1 .. Length( chars ) - 1 ] do
          if IsBound( chars[j] ) then
            if IsString( chars[j] ) then
              PrintTo( stream, chars[j] );            # strip the `"'
              used:= Length( chars[j] );
            else
              used:= BlanklessPrintTo( stream, chars[j], ncols, used, false );
            fi;
          fi;
          used:= BlanklessPrintTo( stream, ",", ncols, used, false );
        od;
        if not IsEmpty( chars ) then
          j:= Length( chars );
          if IsString( chars[j] ) then
            PrintTo( stream, chars[j] );              # strip the `"'
            used:= Length( chars[j] );
          else
            used:= BlanklessPrintTo( stream, chars[j], ncols, used, false );
          fi;
        fi;
        return BlanklessPrintTo( stream, "]", ncols, used, false );
    end;

    ncols:= 78;
    string:= "";
    stream:= OutputTextString( string, true );
    SetPrintFormattingStatus( stream, true );

    # Print the definition of the filename (= component in `LIBTABLE')
    PrintTo( stream, "SET_TABLEFILENAME(\"", file, "\");\n" );

    name:= Identifier( tbl );

    # Step 1:  Do the preparatory work, i.e.,
    #          shrink the Clifford records and remove the irreducibles.
    if IsLibraryCharacterTableRep( tbl ) and IsBound( tbl!.cliffordTable ) then
      if HasIrr( tbl ) then
Error( "handling of Clifford tables not yet installed!" );
        tbl!.cliffordTable:= StructuralCopy( tbl!.cliffordTable );
#T semi-shallow ?
        ShrinkClifford( tbl );
      fi;
      if IsRecord( tbl!.cliffordTable ) then
        tbl!.cliffordTable:= [ tbl!.cliffordTable.Ti.fusions,
                               tbl!.cliffordTable.Ti.tables,
                               tbl!.cliffordTable.cliffordrecords ];
      fi;
    fi;

    # Step 2:  Print the compulsory components.
    PrintTo( stream, Concatenation( "MOT(\"", Identifier( tbl ), "\",\n" ) );

    if HasInfoText( tbl ) then
      PrintTo( stream, TextString( InfoText( tbl ) ), ",\n" );
    else
      PrintTo( stream, "0,\n" );
    fi;

    if HasSizesCentralizers( tbl ) then
      used:= BlanklessPrintTo( stream, SizesCentralizers( tbl ), ncols, 0,
                               false );
      BlanklessPrintTo( stream, ",", ncols, used, false );
      PrintTo( stream, "\n" );
    else
      PrintTo( stream, "0,\n" );
    fi;

    if     HasComputedPowerMaps( tbl )
       and 1 < Length( ComputedPowerMaps( tbl ) ) then
      powermap:= ShallowCopy( ComputedPowerMaps( tbl ) );
      if IsBound( powermap[1] ) then
        Unbind( powermap[1] );
      fi;
      if HasIrr( tbl ) then
        primes:= Set( Factors( Size( tbl ) ) );
        for i in [ 2 .. Length( powermap ) ] do
          if IsBound( powermap[i] ) and not i in primes then
            Unbind( powermap[i] );
          fi;
        od;
      fi;
      used:= BlanklessPrintTo( stream, powermap, ncols, 0, false );
      BlanklessPrintTo( stream, ",", ncols, used, false );
      PrintTo( stream, "\n" );
    else
      PrintTo( stream, "0,\n" );
    fi;

    if HasIrr( tbl ) then
      used:= special( ShrinkChars( Irr( tbl ) ), 0 );
      BlanklessPrintTo( stream, ",", ncols, used, false );
      PrintTo( stream, "\n" );
    else
      PrintTo( stream, "0,\n" );
    fi;

    if HasAutomorphismsOfTable( tbl ) then
      used:= BlanklessPrintTo( stream,
                  GeneratorsOfGroup( AutomorphismsOfTable( tbl ) ),
                  ncols, 0, false );
    else
      PrintTo( stream, "0" );
      used:= 1;
    fi;

    if IsLibraryCharacterTableRep( tbl ) and IsBound( tbl!.construction ) then
      pos:= Position( TABLE_CONSTRUCTORS[1], tbl!.construction );
      BlanklessPrintTo( stream, ",", ncols, used, false );
      PrintTo( stream, "\n" );
      if pos = fail then
        used:= BlanklessPrintTo( stream, tbl!.construction, ncols, 0, false );
      else
        PrintTo( stream, TABLE_CONSTRUCTORS[2][ pos ] );
        used:= Length( TABLE_CONSTRUCTORS[2][ pos ] );
      fi;
    fi;
    BlanklessPrintTo( stream, ");", ncols, used, false );
    PrintTo( stream, "\n" );

    # Step 3:  Print the optional components.

    # Print the representative orders only if they are not redundant.
    if HasOrdersClassRepresentatives( tbl ) and
       ( IsEmpty( ComputedPowerMaps( tbl ) )
         or OrdersClassRepresentatives( tbl )
           <> ElementOrdersPowerMap( ComputedPowerMaps( tbl ) ) ) then

      used:= BlanklessPrintTo( stream,
                 Concatenation( "ARC(\"", name,
                                "\",\"OrdersClassRepresentatives\"," ),
                ncols, 0, false );
      used:= BlanklessPrintTo( stream,
                 OrdersClassRepresentatives( tbl ), ncols, used, false );
      BlanklessPrintTo( stream, ");\n", ncols, used, false );

    fi;

    # Shrink and print the projectives.
    if IsLibraryCharacterTableRep( tbl ) and IsBound( tbl!.projectives ) then

      used:= BlanklessPrintTo( stream,
                 Concatenation( "ARC(\"", name,
                                "\",\"projectives\",[" ),
                 ncols, 0, false );
      for j in tbl!.projectives do
        used:= BlanklessPrintTo( stream,
                   Concatenation( "\"", j.name, "\"," ), ncols, used, false );
        EvalChars( j.chars );
        used:= special( ShrinkChars( j.chars ), used );
        used:= BlanklessPrintTo( stream, ",", ncols, used, false );
      od;
      BlanklessPrintTo( stream, "]);", ncols, used, false );
      PrintTo( stream, "\n" );

    fi;

    # Print remaining supported components of library tables.
    flds:= Difference( SupportedLibraryTableComponents, [
#T PrintToLib also for ``tables with group'' !!
               "cliffordTable",
               "construction",
               "projectives",
              ] );

    for fld in flds do
      if IsBound( tbl!.( fld ) ) then
        used:= BlanklessPrintTo( stream,
                   Concatenation( "ARC(\"", name, "\",\"", fld, "\"," ),
                   ncols, 0, false );
        used:= BlanklessPrintTo( stream, tbl!.( fld ), ncols, used, true );
        BlanklessPrintTo( stream, ");" , ncols, used, false );
        PrintTo( stream, "\n" );
      fi;
    od;

    # Print remaining supported attributes of ordinary tables.
    done:= [
             "AutomorphismsOfTable",
             "ComputedClassFusions",
             "ComputedPowerMaps",
             "ConjugacyClasses",
             "IdentificationOfConjugacyClasses",
             "Identifier",
             "InfoText",
             "Irr",
             "IsPerfectCharacterTable",
             "IsSimpleCharacterTable",
             "IsSolvableCharacterTable",
             "NamesOfFusionSources",
             "NrConjugacyClasses",
             "OrdersClassRepresentatives",
             "SizesCentralizers",
             "SizesConjugacyClasses",
             "UnderlyingCharacteristic",
             "UnderlyingGroup",
             ];
    if HasSizesCentralizers( tbl ) then
      Add( done, "Size" );
    fi;

    for i in [ 3, 6 .. Length( SupportedCharacterTableInfo ) ] do
      fld:= SupportedCharacterTableInfo[ i-1 ];
      if     not fld in done
         and Tester( SupportedCharacterTableInfo[ i-2 ] )( tbl ) then
        used:= BlanklessPrintTo( stream,
                   Concatenation( "ARC(\"", name, "\",\"", fld, "\"," ),
                   ncols, 0, false );
        used:= BlanklessPrintTo( stream,
                   SupportedCharacterTableInfo[ i-2 ]( tbl ),
                   ncols, used, true );
        BlanklessPrintTo( stream, ");" , ncols, used, false );
        PrintTo( stream, "\n" );
      fi;
    od;

    # Add the fusion assignments.
    for fus in ComputedClassFusions( tbl ) do
      PrintTo( stream, LibraryFusion( name, fus ) );
    od;

    # Write the names information to the file.
    libinfo:= LibInfoCharacterTable( name );
    if libinfo <> fail then
      names:= [];
#T       if IsBound( libinfo.othernames ) then
#T         Append( names, libinfo.othernames );
#T       fi;
#T       if IsBound( libinfo.CASnames ) then
#T         Append( names, libinfo.CASnames );
#T       fi;
#T get the other names from somewhere ...
      if not IsEmpty( names ) then
        used:= BlanklessPrintTo( stream,
                   Concatenation( "ALN(\"", name, "\",[" ), ncols, 0, false );
        for i in [ 1 .. Length( names )-1 ] do
          used:= BlanklessPrintTo( stream,
                     Concatenation( "\"", names[i], "\"," ), ncols, used, false );
        od;
        BlanklessPrintTo( stream,
            Concatenation( "\"", names[ Length( names ) ], "\"]);" ),
            ncols, used, false );
        PrintTo( stream, "\n" );
      fi;
    fi;
    PrintTo( stream, "\n" );

    # Print to the file.
    CloseStream( stream );
    if    Length( file ) <= 3
       or file{ [ Length( file ) - 3 .. Length( file ) ] } <> ".tbl" then
      file:= Concatenation( file, ".tbl" );
    fi;
    AppendTo( file, string );
end );

BindGlobal( "PrintToLibBrauer", function( file, tbl )

    local ordtbl,
          ordname,
          prime,
          info,
          factorblocks,
          block,
          offset,
          defect,
          basicset,
          brauertree,
          decinv,
          i,
          decmat,
          lic,
          pos,
          automorphisms,
          indicator,
          ncols,
          string,
          stream,
          used;

    ordtbl:= OrdinaryCharacterTable( tbl );
    ordname:= Identifier( ordtbl );
    prime:= UnderlyingCharacteristic( tbl );

    # Fetch the blocks info.
    info:= BlocksInfo( tbl );

    # Check whether tables of factors groups are available
    # that allow one to omit some of the blocks.
    factorblocks:= ConsiderFactorBlocks( tbl );

    # `block' component (position 4)
    block:= InverseMap( List( info, r -> r.modchars ) );

    # factor blocks (pos. 9)
    offset:= 0;
    if factorblocks <> 0 then
      offset:= factorblocks.offset;
      info:= info{ [ offset + 1 .. Length( info ) ] };
      block:= Filtered( block, x -> offset < x );
      factorblocks:= factorblocks.info;
    fi;

    # `defect' component (position 5)
    defect:= List( info, r -> r.defect );

    # `basicset' component (position 6)
    # `brauertree' component (position 7)
    # `decinv' component (position 8)
    basicset:= [];
    brauertree:= [];
    decinv:= [];
    for i in [ 1 .. Length( info ) ] do
      if info[i].defect = 1 then

        brauertree[i]:= BrauerTree( DecompositionMatrix( tbl, i + offset ) );

        # Replace multiple occurrences of a tree by an index.
        pos:= Position( brauertree, brauertree[i] );
        if pos < i then
          brauertree[i]:= pos;
        fi;

      elif info[i].defect > 1 then

        if IsBound( info[i].basicset ) and IsBound( info[i].decinv ) then
          basicset[i]:= info[i].basicset;
          decinv[i]:= info[i].decinv;
        else
          decmat:= DecompositionMatrix( tbl, i );
          lic:= LinearIndependentColumns( TransposedMat( decmat ) );
          basicset[i]:= info[i].ordchars{ lic };
          decinv[i]:= Inverse( decmat{ lic } );
        fi;

        # Replace multiple occurrences of a matrix by an index.
        pos:= Position( decinv, decinv[i] );
        if pos < i then
          decinv[i]:= pos;
        fi;

      fi;
    od;

    # automorphisms (pos. 10)
    if HasAutomorphismsOfTable( tbl ) then
      automorphisms:= GeneratorsOfGroup( AutomorphismsOfTable( tbl ) );
    else
      automorphisms:= 0;
    fi;

    # indicator (pos. 11)
    if prime = 2 and IsBound( ComputedIndicators( tbl )[2] ) then
      indicator:= ComputedIndicators( tbl )[2];
    else
      indicator:= 0;
    fi;

    # Create the output stream.
    ncols:= 78;
    string:= "";
    stream:= OutputTextString( string, true );
    SetPrintFormattingStatus( stream, true );

    # Print the definition of the filename (= component in `LIBTABLE')
    PrintTo( stream, "SET_TABLEFILENAME(\"", file, "\");\n" );

    # Print the header (positions 1, 2).
    PrintTo( stream, "MBT(\"", ordname, "\",", prime, ",\n" );

    # `InfoText' (pos. 3)
    if not HasInfoText( tbl ) then
      PrintTo( stream, "\"(no info text)\"" );
    elif InfoText( tbl ) =
         "origin: modular ATLAS of finite groups, tests: DEC, TENS" then
      PrintTo( stream, "TEXT1" );
    else
      PrintTo( stream, TextString( InfoText( tbl ) ) );
    fi;
    PrintTo( stream, ",\n" );

    # Print the other components.
    for i in [ block, defect, basicset, brauertree, decinv,
               factorblocks, automorphisms ] do
      used:= BlanklessPrintTo( stream, i, ncols, 0, false );
      BlanklessPrintTo( stream, ",", ncols, used, false );
      PrintTo( stream, "\n" );
    od;
    used:= BlanklessPrintTo( stream, indicator, ncols, 0, false );

    # Complete the function call.
    BlanklessPrintTo( stream, ");", ncols, used, false );
    PrintTo( stream, "\n\n" );

    # Close the stream.
    CloseStream( stream );
    if    Length( file ) <= 3
       or file{ [ Length( file ) - 3 .. Length( file ) ] } <> ".tbl" then
      file:= Concatenation( file, ".tbl" );
    fi;
    AppendTo( file, string );
end );

InstallGlobalFunction( PrintToLib, function( file, tbl )
    if IsOrdinaryTable( tbl ) and IsString( file ) then
      PrintToLibOrdinary( file, tbl );
    elif IsBrauerTable( tbl ) and IsString( file ) then
      PrintToLibBrauer( file, tbl );
    else
      Error( "usage: PrintToLib( <file>, <tbl> ) for string <file>\n",
             "and char. table <tbl>" );
    fi;
end );


#T ################################################################################
#T ##
#T #F  PrintClmsToLib( <file>, <clms> )
#T ##
#T InstallGlobalFunction( PrintClmsToLib, function( filename, clms )
#T 
#T     local  ind, i, il, lclms, clm, size,
#T         l,               # clmname
#T         clmlist,         # list of cliffordmatrices in the library
#T         lname,           # name of the file in the library
#T         ir,              # the internal record used here of the library
#T         found;           # whether the clm is already in the library
#T 
#T     if not( IsCliffordTable( clms ) or 
#T             IsList( clms ) and ForAll( clms, x-> IsBound( x.mat ) and 
#T                           IsBound( x.colw ) ) )  then
#T         Error( "usage: PrintClmsToLib( <file>, <clms> ) for a list ",
#T                "of cliffordrecords or a cliffordtable " ); 
#T     fi;
#T 
#T     if IsList( clms ) then lclms := Length( clms );
#T     else                   lclms := clms.size;
#T     fi;
#T 
#T     ir := [];
#T     for ind in [1..lclms] do
#T         if IsList( clms ) then clm := clms[ind];
#T         else       clm := clms.(ind);
#T         fi;
#T 
#T         size := 0; 
#T         if IsBound( clm.mat )  then size := Length( clm.mat[1] ); fi;
#T 
#T         if size = 0  then
#T             Print("#I PrintClmsToLib: no <mat> and <colw>. Nothing done.\n");
#T         elif  size > 2  then
#T             if IsBound( clm.splitinfos )  then
#T               lname := "exsp";
#T             else
#T               lname := "elab";
#T             fi;
#T             l := Concatenation( lname, String( size ));
#T 
#T             clmlist := LibraryTables( Concatenation( "clm", lname ) );
#T             found := false;
#T             if IsBound( clmlist.(l) ) then
#T                 i := 0;
#T                 il := Length( clmlist.(l) );
#T                 while ( not found and i < il ) do
#T                     i := i+1;
#T                     found := clmlist.(l)[i][1] = clm.mat 
#T                          and clmlist.(l)[i][2] = clm.colw;
#T                 od;
#T             fi;
#T             if not found and IsBound( ir[size] ) then
#T                 i := 0;
#T                 il := Length( ir[size] );
#T                 while ( not found and i < il ) do
#T                     i := i+1;
#T                     found := ir[size][i][1] = clm.mat 
#T                          and ir[size][i][2] = clm.colw;
#T                 od;
#T             fi;
#T 
#T             if not found then
#T                 if IsBound( ir[size] )  then
#T                   ir[size][Length( ir[size] )+1] := 
#T                                          [clm.mat, clm.colw];
#T                 else
#T                   ir[size] := [ [clm.mat, clm.colw] ];
#T                 fi;
#T             else
#T                 Print( "#I PrintClmsToLib: Matrix ", ind, 
#T                        " already in library or in ", filename, ".\n" );
#T             fi;
#T         fi;
#T     od;
#T 
#T     PrintTo( filename, ir, "\n" );
#T 
#T     return;
#T end );


#############################################################################
##
#F  OrbitsResidueClass( <pq>, <set> )
##
InstallGlobalFunction( OrbitsResidueClass, function( pq, set )

    local gen,
          orbs,
          pnt,
          orb,
          i;

    gen:= PowerModInt( PrimitiveRootMod( pq[1] ), (pq[1]-1)/pq[2], pq[1] );
    orbs:= [];
    while not IsEmpty( set ) do
      pnt:= set[1];
      orb:= [];
      for i in [ 1 .. pq[2] ] do
        orb[i]:= pnt;
        pnt:= ( pnt * gen ) mod pq[1];
      od;
      Add( orbs, orb );
      SubtractSet( set, orb );
    od;
    return orbs;
end );


##############################################################################
##
#F  GaloisPartnersOfIrreducibles( <tbl>, <characters>, <n> )
##
##  Compute the Galois automorphism(s) carrying to the partner(s).
##
InstallGlobalFunction( GaloisPartnersOfIrreducibles,
    function( tbl, characters, n )

    local partners,  # list of partners, result
          chi,       # loop over `characters'
          N,         # conductor of `chi'
          k,         # list of values representing the partner cohorts
          facts,     # collected factors of `N'
          primes,    # prime factors of `N'
          2part,     # $2$-part of `N'
          3part,     # $3$-part of `N'
          NN,        # part of `N' that is coprime to $2$ and $3$
          kk,        # list with possible prime residues for each in `k'
          new,       # admissible subsets of `kk'
          p;         # characteristic in the case of a Brauer table

    if n < 3 then
      Error( "only for n >=3" );
    fi;

    partners:= [];

    for chi in characters do

      N:= Conductor( chi );

      # The rules of the ordinary Atlas apply for ordinary tables
      # and for Brauer characters for which all algebraic conjugates
      # are Brauer characters.
      if    IsOrdinaryTable( tbl )
         or (     IsBrauerTable( tbl )
              and ( n <> 12 or Identifier( tbl ) <> "12.M22mod11" )
#T is this reasonable?
              and ForAll( PrimeResidues( N ),
                    k -> List( chi,
                               x -> GaloisCyc( x, k ) ) in characters ) ) then

        if n <> 12 then
          k:= [ n - 1 ];
        else
          k:= [ 5, 7, 11 ];
        fi;
        facts:= Collected( Factors( N ) );
        primes:= List( facts, x -> x[1] );
        if 2 in primes then
          2part:= 2^facts[ Position( primes, 2 ) ][2];
        else
          2part:= 1;
        fi;
        if 3 in primes then
          3part:= 3^facts[ Position( primes, 3 ) ][2];
        else
          3part:= 1;
        fi;
        NN:= N / ( 2part * 3part );

        # The automorphism $\ast k^\prime$ that carries to the
        # partner in the cohort given by $\ast k$
        # is determined by the conditions that
        # $k^\prime \equiv k \pmod{n}$,
        # $k^\prime \equiv 1$ modulo each divisor of $N$ coprime to $n$,
        # $k^\prime \equiv \pm 1$ modulo powers of $2$ and $3$
        # dividing $n$,
        # where $+1$ is preferred if there is a choice.
        # (As an example, consider $N = 60$, $n = 3$, and $k = 2$
        # where $11$ and $41$ are possible solutions,
        # and the action on $20$-th roots of unity is different;
        # this occurs for the group $3.U_3(11)$.)
        # Note that we may have to replace $N$ by the l.c.m. of $N$
        # and $n$, for example if $n = 6$ and $N$ is odd.
        kk:= List( k, y -> Filtered( PrimeResidues( LcmInt( N, n ) ),
                                      x -> x mod NN in [ 0, 1 ]
                                  and x mod n = y
                                  and x mod 2part in [ 2part-1, 1 ]
                                  and x mod 3part in [ 3part-1, 1 ] ) );
        for k in [ 1 .. Length( kk ) ] do
          if Length( kk[k] ) = 1 then
            kk[k]:= kk[k][1];
          else
            if 2part <> 1 then
              new:= Filtered( kk[k], x -> x mod 2part = 1 );
              if Length( new ) <> 0 then
                kk[k]:= new;
              fi;
            fi;
            if Length( kk[k] ) > 1 and 3part <> 1 then
              new:= Filtered( kk[k], x -> x mod 3part = 1 );
              if Length( new ) <> 0 then
                kk[k]:= new;
              fi;
            fi;
            kk[k]:= kk[k][1];
          fi;
        od;

      else

        p:= UnderlyingCharacteristic( tbl );
        if n <> 12 then
          if ( p-1 ) mod n <> 0 then
            kk:= [ p ];
          else
            kk:= [ -1 ];
          fi;
        elif Identifier( tbl ) = "12.M22mod11" then
          kk:= [ -1 ];
        else
          kk:= [ p, -1, -p ];
        fi;

      fi;

      Add( partners, kk );

    od;

    return partners;
end );


##############################################################################
##
#F  AtlasLabelsOfIrreducibles( <tbl>[, <short>] )
##
InstallGlobalFunction( AtlasLabelsOfIrreducibles, function( arg )

    local tbl,        # first argument, an ATLAS character table
          short,      # optional second argument, choice of short labels
          ordtbl,     # ordinary table of `tbl'
          out,        # index of the derived subgroup
          centre,     # centre of the derived subgroup
          mult,       # order of `centre'
          nccl,       # no. of conjugacy classes of `tbl'
          charname,   # string "\\chi" or "\\varphi"
          der,        # table of the derived subgroup corresp. to `tbl'
          ordder,     # table of the derived subgroup corresp. to `ordtbl'
          pos,        # position in a list
          irr,        # list of irreducibles
          portions,   # list that separates characters with different kernel
          divisors,   # list of divisors of the multiplier
          i, j, k, l, # loop variables
          labels,     # list of labels, result
          portionlbs, # list of labels for one portion
          max,        # current maximal offset
          partners,   # output of `GaloisPartnersOfIrreducibles'
          special,    # list of special cases requiring offsets
          offsets,    # current offsets in special case
          fus,        # various fusion maps
          rest,       # list of restricted characters
          distrib,    # positions of restrictions in `rest'
          restchi,    # one restriction to the derived subgroup
          dec,        # decomposition matrix of restrictions
          derlabels,  # list of labels of the derived subgroup
          inv,        # inverse map of `distrib'
          dl,         # list of nonzero coefficients in a decomposition
          n,          # length of `dl'
          lb,         # one label
          intermed,   # list of intermediate tables
          index,      # index of an inertia subgroup
          fus1,       # fusion from derived subgroup to intermediate group
          fus2,       # fusion from intermediate group to group
          ext,        # positions of extensions of a character
          interirr;   # irreducibles of an intermediate table

    # Get the arguments.
    tbl:= arg[1];
    short:= Length( arg ) = 2 and arg[2] = "short";

    # `tbl' is assumed to be the table of a bicyclic extension
    # of a simple group.
    # Get the index `out' of the derived subgroup
    # and the order `mult' of the centre of the derived subgroup.
    if IsBrauerTable( tbl ) then
      ordtbl:= OrdinaryCharacterTable( tbl );
    else
      ordtbl:= tbl;
    fi;
    out:= AbelianInvariants( ordtbl );
    if not IsSSortedList( out ) then
      Error( "<tbl> is not a bicyclic extension of a simple table" );
    fi;
    out:= Product( out, 1 );
    centre:= ClassPositionsOfFittingSubgroup( ordtbl );
    mult:= Sum( SizesConjugacyClasses( ordtbl ){ centre }, 0 );
    if 12 mod mult <> 0 then
      Error( "<tbl> is not a bicyclic extension of an ATLAS table" );
    fi;

    nccl:= NrConjugacyClasses( tbl );

    # Initializations for the final labels.
    if IsOrdinaryTable( tbl ) then
      charname:= "\\chi_{";
    else
      charname:= "\\varphi_{";
    fi;

    # Compute label descriptions for the derived subgroup.
    # (For tables of non-perfect groups,
    # the labels are formed relative to the labels of the
    # table of the derived subgroup,
    # so we need this table and its label descriptions.)
    if out = 1 then

      der:= tbl;
      ordder:= ordtbl;

    else

      ordder:= Identifier( ordtbl );
      pos:= Length( ordder );
      while IsDigitChar( ordder[ pos ] ) or ordder[ pos ] = '_' do
        pos:= pos - 1;
      od;
      if pos = Length( ordder ) or ordder[ pos ] <> '.' then
        Error( "derived subgroup table not given by identifier of <tbl>" );
      fi;
      ordder:= CharacterTable( ordder{ [ 1 .. pos-1 ] } );
      if ordder = fail then
        Info( InfoCharacterTable, 1 ,
              "no derived character table of ", Identifier( ordtbl ),
              " available" );
        return fail;
      fi;
      if IsBrauerTable( tbl ) then
        der:= BrauerTable( ordder, UnderlyingCharacteristic( tbl ) );
        if der = fail then
          Info( InfoCharacterTable, 1 ,
                "no ", UnderlyingCharacteristic( tbl ), "-modular ",
                "derived character table of ", Identifier( ordtbl ),
                " available" );
          return fail;
        fi;
      else
        der:= ordder;
      fi;

    fi;

    # `der' is the table of a central extension of a simple group.
    # Each label is encoded by an integer (the subscript)
    # or a list of length two (the pair of subscript and a Galois
    # automorphism).

    # Distribute the characters to different portions.
    irr:= List( Irr( der ), ValuesOfClassFunction );

    if mult <> 12 then
      divisors:= DivisorsInt( mult );
    else
      divisors:= [ 1, 2, 4, 3, 6, 12 ];
    fi;

    # The `i'-th portion consists of those characters with
    # conductor of the restriction to `centre' equal to `divisors[i]'.
    if IsBrauerTable( der ) then
      fus:= GetFusionMap( der, ordder );
      centre:= Filtered( [ 1 .. Length( fus ) ], i -> fus[i] in centre );
    fi;
    portions:= List( divisors,
                     i -> Filtered( [ 1 .. NrConjugacyClasses( der ) ],
                                j -> Conductor( irr[j]{ centre } ) = i ) );

    labels:= [];
    max:= 0;

    for i in [ 1 .. Length( portions ) ] do

      if divisors[i] < 3 then

        # The $i$-th label has index $i$.
        Append( labels, [ 1 .. Length( portions[i] ) ] + max );
        max:= Maximum( labels );

      else

        # Some of the characters are not printed in the {\ATLAS},
        # therefore the labels are more complicated.
        portionlbs:= [];

        # Compute the Galois automorphisms mapping the printed
        # characters to their partner(s).
        partners:= GaloisPartnersOfIrreducibles( der, irr{ portions[i] },
                                                 divisors[i] );

        for k in [ 1 .. Length( portions[i] ) ] do

          j:= portions[i][k];

          # Get the position(s) of the partner(s).
          pos:= List( partners[k],
                      x -> Position( irr,
                                     List( irr[j],
                                           y -> GaloisCyc( y, x ) ) ) );

          # Construct labels relative to the *first* character
          # of each set with the same proxy.
          if j < Minimum( pos ) then
            max:= max + 1;
            portionlbs[k]:= max;
            for l in [ 1 .. Length( pos ) ] do
              portionlbs[ Position( portions[i], pos[l] ) ]:=
                  [ max, partners[k][l] ];
            od;
          fi;

        od;

        Append( labels, portionlbs );

      fi;

    od;

    # Adjust the labels in the special cases
    # $A_6$, $A_7$, $L_3(4)$, $M_{22}$, $U_4(3)$, $O_7(3)$,
    # $U_6(2)$, $Suz$, $Fi_{22}$, and ${}^2E_6(2)$.
    # For these groups, $6$ divides the multiplier,
    # so at least $3.G$ needs an offset.
    special:= [
                "3.A6",           [  7,  6 ],
                "3.A6mod5",       [  5,  4 ],

                "3.A7",           [  9,  7 ],
                "3.A7mod5",       [  8,  6 ],
                "3.A7mod7",       [  7,  5 ],

                "3.L3(4)",        [ 10, 21 ],
                "4_2.L3(4)",      [ 18,  6 ],
                "6.L3(4)",        [ 18, 13 ],
                "12_1.L3(4)",     [ 24,  7 ],
                "12_2.L3(4)",     [ 41, 11, 18, 6 ],

                "4_2.L3(4)mod3",  [ 16,  5 ],
                "12_2.L3(4)mod3", [ 16,  5 ],

                "3.L3(4)mod5",    [  8, 15 ],
                "6.L3(4)mod5",    [ 14,  9 ],
                "12_1.L3(4)mod5", [ 18,  5 ],
                "12_2.L3(4)mod5", [ 31,  7, 14, 4 ],

                "3.L3(4)mod7",    [  8, 15 ],
                "6.L3(4)mod7",    [ 14,  9 ],
                "12_1.L3(4)mod7", [ 18,  5 ],
                "12_2.L3(4)mod7", [ 31,  7, 14, 4 ],

                "3.M22",          [ 12, 19 ],
                "6.M22",          [ 23,  8 ],

                "3.M22mod5",      [ 11, 10 ],
                "6.M22mod5",      [ 21,  7 ],

                "3.M22mod7",      [ 10,  9 ],
                "6.M22mod7",      [ 19,  6 ],

                "3_1.U4(3)",      [ 20, 35 ],
                "3_2.U4(3)",      [ 20, 78 ],
                "6_1.U4(3)",      [ 39, 16 ],
                "6_2.U4(3)",      [ 39, 59 ],
                "12_2.U4(3)",     [ 55, 43 ],

                "3_2.U4(3)mod2",  [ 12,  8 ],
                "6_2.U4(3)mod2",  [ 12,  8 ],
                "12_2.U4(3)mod2", [ 12,  8 ],

                "3_2.U4(3)mod5",  [ 19, 73 ],
                "6_2.U4(3)mod5",  [ 37, 55 ],
                "12_2.U4(3)mod5", [ 52, 40 ],

                "3_2.U4(3)mod7",  [ 18, 68 ],
                "6_2.U4(3)mod7",  [ 35, 51 ],
                "12_2.U4(3)mod7", [ 49, 37 ],

                "3.O7(3)",        [ 58, 30 ],
                "3.O7(3)mod5",    [ 53, 27 ],
                "3.O7(3)mod7",    [ 56, 28 ],
                "3.O7(3)mod13",   [ 56, 28 ],

                "3.Suz",          [ 43, 33 ],
                "3.Suzmod5",      [ 35, 26 ],
                "3.Suzmod7",      [ 39, 30 ],
                "3.Suzmod11",     [ 42, 32 ],
                "3.Suzmod13",     [ 41, 31 ],

                "3.Fi22",         [ 65, 49 ],
                "3.Fi22mod5",     [ 59, 43 ],
                "3.Fi22mod7",     [ 62, 46 ],
                "3.Fi22mod11",    [ 61, 45 ],
                "3.Fi22mod13",    [ 63, 47 ],

                "3.2E6(2)",       [ "?", "?" ],
                "3.2E6(2)mod5",   [ "?", "?" ],
                "3.2E6(2)mod7",   [ "?", "?" ],
                "3.2E6(2)mod11",  [ "?", "?" ],
                "3.2E6(2)mod13",  [ "?", "?" ],
                "3.2E6(2)mod17",  [ "?", "?" ],
                "3.2E6(2)mod19",  [ "?", "?" ],

                ];

    pos:= Position( special, Identifier( der ) );
    if pos <> fail then
      offsets:= special[ pos+1 ];
      for i in [ 1 .. Length( labels ) ] do
        if IsInt( labels[i] ) and labels[i] > offsets[1] then
          labels[i]:= labels[i] + offsets[2];
        elif IsList( labels[i] ) and labels[i][1] > offsets[1] then
          labels[i][1]:= labels[i][1] + offsets[2];
        elif Length( offsets ) > 2 then
          if IsInt( labels[i] ) and labels[i] > offsets[3] then
            labels[i]:= labels[i] + offsets[4];
          elif IsList( labels[i] ) and labels[i][1] > offsets[3] then
            labels[i][1]:= labels[i][1] + offsets[4];
          fi;
        fi;
      od;
    fi;

    if out = 1 then

      # Build the final labels from the subscripts.
      for i in [ 1 .. Length( labels ) ] do
        if IsInt( labels[i] ) then
          lb:= Concatenation( charname, String( labels[i] ), "}" );
        else
          lb:= Concatenation( charname, String( labels[i][1] ), "}^{\\ast" );
          k:= labels[i][2];
          if k < 0 then
            Append( lb, "\\ast" );
            k:= -k;
          fi;
          if k <> 1 then
            Append( lb, " " );
            Append( lb, String( k ) );
          fi;
          Append( lb, "}" );
        fi;
        labels[i]:= lb;
      od;

    else

      # Start with the labels for `der'.
      # They are of the form `i' (for $\chi_i$)
      # or `[ i, j ]' (for $\chi_i^{\ast j}$).
      derlabels:= labels;
      labels:= [];

      fus:= GetFusionMap( der, tbl );
      if fus = fail then
        Info( InfoCharacterTable, 1 ,
              "no fusion from ", der, " to ", tbl, " available" );
        return fail;
      fi;

      # `rest' is the list of the different restrictions of `irr'
      # to the derived subgroup.
      # `distrib[i]' is the position of the restriction of `irr[i]'
      # in `rest'.
      rest:= [];
      distrib:= [];
      irr:= List( Irr( tbl ), ValuesOfClassFunction );
      for i in [ 1 .. Length( irr ) ] do
        restchi:= irr[i]{ fus };
        pos:= Position( rest, restchi );
        if pos = fail then
          Add( rest, restchi );
          pos:= Length( rest );
        fi;
        distrib[i]:= pos;
      od;

      # Compute the decompositions of `rest' into irreducibles of `der'.
      dec:= Decomposition( List( Irr( der ), ValuesOfClassFunction ),
                           rest, "nonnegative" );

      # Store tables of intermediate groups if `out' is nonprime.
      intermed:= [];

      # Compute the labels of `irr'.
      # The number $n$ of different irreducibles of `der' contained in
      # a restriction divides `out'.
      # Let `outprime' be the $p^\prime$ part of `out'.
      # The following labels occur.
      # - If $n = 1$ then the labels are of the form
      #   $\chi_{i,k}$ and $\chi_{i,k}^{\ast j}$, respectively,
      #   where $0 \leq k < `outprime'$.
      # - If $n$ equals `out' then the labels are of the form
      #   $\chi_{i1+i2+...+iout}$ or
      #   $\chi_{i1\ast j1 + i2\ast j2 +...+ iout\ast jout}$ .
      # - In all other cases, `outprime' is either $4$ or $6$,
      #   and we need the intermediate tables.

      inv:= InverseMap( distrib );
      for i in [ 1 .. Length( rest ) ] do
        if IsInt( inv[i] ) then
          inv[i]:= [ inv[i] ];
        fi;
        dl:= Filtered( [ 1 .. Length( dec[i] ) ], j -> dec[i][j] <> 0 );
        n:= Length( dl );
        if n = 1 then
          # extension case
          for j in [ 1 .. Length( inv[i] ) ] do
            if IsInt( derlabels[ dl[1] ] ) then
              # extension
              labels[ inv[i][j] ]:= Concatenation( charname,
                                        String( derlabels[ dl[1] ] ), ",",
                                        String( j-1 ), "}" );
            else
              # conjugate of an extension
              lb:= Concatenation( charname, String( derlabels[ dl[1] ][1] ),
                       ",", String( j-1 ), "}^{\\ast" );
              k:= derlabels[ dl[1] ][2];
              if k < 0 then
                Append( lb, "\\ast" );
                k:= -k;
              fi;
              if k <> 1 then
                Append( lb, " " );
                Append( lb, String( k ) );
              fi;
              Append( lb, "}" );
              labels[ inv[i][j] ]:= lb;
            fi;
          od;
        elif n = out then
          # case of fusion
          if short then
            if ForAny( derlabels{ dl }, IsInt ) then
              lb:= Concatenation( charname,
                                  String( First( derlabels{ dl }, IsInt ) ),
                                  "+}" );
            else
              j:= derlabels[ dl[1] ];
              lb:= Concatenation( charname,
                                  String( j[1] ),
                                  "\\ast" );
              k:= j[2];
              if k < 0 then
                Append( lb, "\\ast" );
                k:= - k;
              fi;
              Append( lb, " " );
              if k <> 1 then
                Append( lb, String( k ) );
              fi;
              Append( lb, "+}" );
            fi;
          else
            lb:= ShallowCopy( charname );
            for j in derlabels{ dl } do
              if IsInt( j ) then
                Append( lb, Concatenation( String( j ), "+" ) );
              else
                Append( lb, String( j[1] ) );
                Append( lb, "\\ast" );
                k:= j[2];
                if k < 0 then
                  Append( lb, "\\ast" );
                  k:= -k;
                fi;
                Append( lb, " " );
                if k <> 1 then
                  Append( lb, String( k ) );
                fi;
                Append( lb, "+" );
              fi;
            od;
            lb[ Length( lb ) ]:= '}';
          fi;
          labels[ inv[i][1] ]:= lb;

        else

          # We have an intermediate group $U$ such that the characters
          # of the simple group extend to $U$ and the extensions of
          # different characters fuse in the full extension.
          # (In our cases, this intermediate group is uniquely determined
          # because `out' is either $4$ or $6$.)

          # The inertia subgroup has index `index'.
          index:= Length( dl );
          if not IsBound( intermed[ index ] ) then
            intermed[ index ]:= List( Filtered(
                ComputedClassFusions( ordder ),
                x -> x.name in NamesOfFusionSources( ordtbl ) ),
                    y -> CharacterTable( y.name ) );
            intermed[ index ]:= Filtered( intermed[ index ],
                 x -> Size( tbl ) / Size( x ) = index );
            if Length( intermed[ index ] ) > 1 then
              Error( "indermediate table of index ", index, " in ", ordtbl,
                     " not unique" );
            fi;
            intermed[ index ]:= intermed[ index ][1];
            if IsBrauerTable( der ) then
              intermed[ index ]:= BrauerTable( intermed[ index ],
                                         UnderlyingCharacteristic( der ) );
            fi;
          fi;
          interirr:= List( Irr( intermed[ index ] ),
                           ValuesOfClassFunction );
          fus1:= GetFusionMap( der, intermed[ index ] );
          fus2:= GetFusionMap( intermed[ index ], tbl );

          for j in inv[i] do

            # Construct the label string.
            if short then

              if ForAny( dl, x -> IsInt( derlabels[x] ) ) then

                l:= First( dl, x -> IsInt( derlabels[x] ) );

                # Get the extensions from `der' to the intermediate group.
                ext:= ValuesOfClassFunction( Irr( der )[l] );
                ext:= Filtered( [ 1 .. Length( interirr ) ],
                        x -> Irr( intermed[ index ] )[x]{ fus1 } = ext );

                # Count which extension occurs in the restriction from `tbl'
                # to the intermediate group.
                k:= Decomposition( interirr, [ irr[j]{ fus2 } ],
                                   "nonnegative" );
                k:= First( [ 1 .. Length( ext ) ],
                           x -> k[1][ ext[x] ] <> 0 );

                lb:= Concatenation( charname,
                                    String( derlabels[l] ),
                                    ",",
                                    String( k-1 ),
                                    "+}" );

              else

                l:= dl[1];

                # Get the extensions from `der' to the intermediate group.
                ext:= ValuesOfClassFunction( Irr( der )[l] );
                ext:= Filtered( [ 1 .. Length( interirr ) ],
                        x -> Irr( intermed[ index ] )[x]{ fus1 } = ext );

                # Count which extension occurs in the restriction from `tbl'
                # to the intermediate group.
                k:= Decomposition( interirr, [ irr[j]{ fus2 } ],
                                   "nonnegative" );
                k:= First( [ 1 .. Length( ext ) ],
                           x -> k[1][ ext[x] ] <> 0 );

                lb:= Concatenation( charname,
                                    String( derlabels[l][1] ),
                                    ",",
                                    String( k-1 ),
                                    "\\ast" );

                if derlabels[l][2] < 0 then
                  Append( lb, "\\ast" );
                  derlabels[l][2]:= -derlabels[l][2];
                fi;
                if derlabels[l][2] <> 1 then
                  Append( lb, " " );
                  Append( lb, String( derlabels[l][2] ) );
                fi;
                Append( lb, "+}" );

              fi;

            else

              lb:= ShallowCopy( charname );

              for l in dl do

                # Get the extensions from `der' to the intermediate group.
                ext:= ValuesOfClassFunction( Irr( der )[l] );
                ext:= Filtered( [ 1 .. Length( interirr ) ],
                        x -> Irr( intermed[ index ] )[x]{ fus1 } = ext );

                # Count which extension occurs in the restriction from `tbl'
                # to the intermediate group.
                k:= Decomposition( interirr, [ irr[j]{ fus2 } ],
                                   "nonnegative" );
                k:= First( [ 1 .. Length( ext ) ],
                           x -> k[1][ ext[x] ] <> 0 );

                if IsInt( derlabels[l] ) then
                  Append( lb, String( derlabels[l] ) );
                  Append( lb, "," );
                  Append( lb, String( k-1 ) );
                  Append( lb, "+" );
                else
                  Append( lb, String( derlabels[l][1] ) );
                  Append( lb, "," );
                  Append( lb, String( k-1 ) );
                  Append( lb, "\\ast" );
                  if derlabels[l][2] < 0 then
                    Append( lb, "\\ast" );
                    derlabels[l][2]:= -derlabels[l][2];
                  fi;
                  if derlabels[l][2] <> 1 then
                    Append( lb, String( derlabels[l][2] ) );
                  fi;
                  Append( lb, "+" );
                fi;

              od;

              lb[ Length( lb ) ]:= '}';

            fi;

            labels[j]:= lb;

          od;

        fi;
      od;

    fi;


    # Return the labels.
    return labels;
end );


#############################################################################
##
#F  FirstNameCharTable( <tblname> )
#F  FileNameCharTable( <tblname> )
##
##  Add some harmless functions of the {\GAP}~3 compatibility mode
##  that are related to the character table library.
##
BindGlobal( "FirstNameCharTable",
    function( name )
    name:= LibInfoCharacterTable( name );
    if name = fail then
      return false;
    else
      return name.firstName;
    fi;
    end );

BindGlobal( "FileNameCharTable",
    function( name )
    name:= LibInfoCharacterTable( name );
    if name = fail then
      return false;
    else
      return name.fileName;
    fi;
    end );


#############################################################################
##
##  The rest of this file contains the interface between the {\GAP} libraries
##  of character tables and of tables of marks.
##
##  The interface consists of methods for the operations `CharacterTable' and
##  `TableOfMarks', with argument a table of marks and a character table,
##  respectively.
##  These methods try to get the corresponding character table resp.~table of
##  marks from the library.
##
##  If the required information is not found in the respective library,
##  and if no group is available from which this information can be computed
##  then `fail' returned.
##
##  The availability of the required information is looked up in the global
##  variable `TOM_TBL_INFO'.
##  (If not both libraries are installed then `TOM_TBL_INFO' is trivial,
##  hence the methods mentioned above become trivial.)
##


#############################################################################
##
#M  TableOfMarks( <tbl> ) . . . . . . . . . . . . . . . for a character table
#M  TableOfMarks( <G> )
##
##  We delegate from <tbl> to the underlying group in the general case.
##
##  If the argument is a group, we can use the known table of marks of the
##  known ordinary character table.
##
InstallOtherMethod( TableOfMarks,
    "for a character table with underlying group",
    [ IsCharacterTable and HasUnderlyingGroup ],
    tbl -> TableOfMarks( UnderlyingGroup( tbl ) ) );


InstallOtherMethod( TableOfMarks,
    "for a group with known ordinary character table",
    [ IsGroup and HasOrdinaryCharacterTable ], 100,
#T ?
    function( G )
    local tbl;
    tbl:= OrdinaryCharacterTable( G );
    if HasTableOfMarks( tbl ) then
      return TableOfMarks( tbl );
    else
      TryNextMethod();
    fi;
    end );


#############################################################################
##
#M  TableOfMarks( <tbl> ) . . . . . . . . . . . for a library character table
##
##  If <tbl> is a library character table then we check whether there is a
##  corresponding table of marks in the library.
##  If there is no such table of marks but <tbl> knows a group then we
##  delegate to this group.
##  Otherwise we return `fail'.
##
InstallOtherMethod( TableOfMarks,
    "for a library character table",
    [ IsOrdinaryTable and IsLibraryCharacterTableRep ],
    function( tbl )
    local pos;
    pos:= Position( TOM_TBL_INFO[1], LowercaseString( Identifier( tbl ) ) );
    if pos <> fail then
      return TableOfMarks( TOM_TBL_INFO[2][ pos ] );
    elif HasUnderlyingGroup( tbl ) then
      TryNextMethod();
    fi;
    return fail;
    end );


#############################################################################
##
#M  CharacterTable( <tom> ) . . . . . . . . . . . . . .  for a table of marks
#M  CharacterTable( <G> )
##
##  We delegate from <tom> to the underlying group in the general case.
##
##  If the argument is a group, we can use the known character table of the
##  known table table of marks.
##
InstallOtherMethod( CharacterTable,
    "for a table of marks with underlying group",
    [ IsTableOfMarks and HasUnderlyingGroup ],
    tom -> CharacterTable( UnderlyingGroup( tom ) ) );


InstallOtherMethod( CharacterTable,
    "for a group with known table of marks",
    [ IsGroup and HasTableOfMarks ], 100,
#T ?
    function( G )
    local tom;
    tom:= TableOfMarks( G );
    if HasOrdinaryCharacterTable( tom ) then
      return OrdinaryCharacterTable( tom );
    else
      TryNextMethod();
    fi;
    end );


#############################################################################
##
#M  CharacterTable( <tom> ) . . . . . . . . . .  for a library table of marks
##
##  If <tom> is a library table of marks then we check whether there is a
##  corresponding character table in the library.
##  If there is no such character table but <tom> knows a group then we
##  delegate to this group.
##  Otherwise we return `fail'.
##
InstallOtherMethod( CharacterTable,
    "for a library table of marks",
    [ IsTableOfMarks and IsLibTomRep ],
    function( tom )
    local pos;
    pos:= Position( TOM_TBL_INFO[2], LowercaseString( Identifier( tom ) ) );
    if pos <> fail then
      return CharacterTable( TOM_TBL_INFO[1][ pos ] );
    elif HasUnderlyingGroup( tom ) then
      TryNextMethod();
    fi;
    return fail;
    end );


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

