#############################################################################
##
#W  ctadmin.tbi         GAP character table library             Thomas Breuer
#W                                                               Ute Schiffer
##
#H  @(#)$Id: ctadmin.tbi,v 4.90 2004/03/30 08:59:59 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/gap4/ctadmin_tbi" ) :=
    "@(#)$Id: ctadmin.tbi,v 4.90 2004/03/30 08:59:59 gap Exp $";


#############################################################################
##
#F  PParseBackwards( <string>, <format> )
##
BindGlobal( "PParseBackwards", function( string, format )
#T Remove this as soon as `gpisotyp' is available!
    local result, pos, j, pos2;

    # Scan the string backwards.
    result:= [];
    pos:= Length( string );
    for j in Reversed( format ) do
      if IsString( j ) then
        pos2:= pos - Length( j );
        if pos2 < 0 or string{ [ pos2+1 .. pos ] } <> j then
          return fail;
        fi;
      else
        pos2:= pos;
        while 0 < pos2 and j( string[ pos2 ] ) do
          pos2:= pos2-1;
        od;
      fi;
      if j = IsDigitChar then
        Add( result, Int( string{ [ pos2+1 .. pos ] } ) );
      else
        Add( result, string{ [ pos2+1 .. pos ] } );
      fi;
      pos:= pos2;
    od;
    if 0 < pos then
      return fail;
    fi;

    return Reversed( result );
    end );


#############################################################################
##
#F  ReadTbl( <name> ) . . . . . . . . . . . . . . . .  character tables files
##
##  This function is used to read data files of the character table library.
##  If the initial part of <name> is one of `~/', `/' or `./' then we
##  interpret the name as *absolute*, and try to read the file analogous to
##  `Read'; otherwise we interpret the name as *relative* to the `data'
##  directory of the `ctbllib' package.
##
BindGlobal( "ReadTbl",
    function( name )
    local readIndent, found;

    if 2 <= Length( name )
       and ( name[1] = '/' or name{ [ 1, 2 ] } in [ "~/", "./" ] ) then
      name:= USER_HOME_EXPAND( name );
      readIndent:= SHALLOW_COPY_OBJ( READ_INDENT );
      APPEND_LIST_INTR( READ_INDENT, "  " );
      InfoRead1( "#I", READ_INDENT, "Read( \"", name, "\" )\n" );
      found:= IsReadableFile( name ) = true and READ( name );
      READ_INDENT:= readIndent;
      if found and READ_INDENT = "" then
        InfoRead1( "#I  Read( \"", name, "\" ) done\n" );
      fi;
      return found;
    else
      return DoRereadPkg( Concatenation( "ctbllib/data/", name ),
                          "character tables" );
    fi;
    end );


#############################################################################
##
#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 two, the first entry being the list of identifiers of
##  tables of marks, the second entry being the list of corresponding
##  identifiers of library character tables.
##
##  (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;
    if not IsBound( LIBTABLE.( filename ) ) then
      LIBTABLE.( filename ):= rec();
    fi;
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>, <spec>] ) .  add library table fusions
##
InstallGlobalFunction( ALF, function( arg )
    local pos, fus, 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;

    fus:= rec( name:= arg[2], map:= arg[3] );
    if 4 <= Length( arg ) then
      text:= Concatenation( arg[4] );
      ConvertToStringRep( text );
      fus.text:= text;
    fi;
    if Length( arg ) = 5 then
      text:= arg[5];
      ConvertToStringRep( text );
      fus.specification:= text;
    fi;

    Add( LIBTABLE.( LIBTABLE.TABLEFILENAME ).( arg[1] ).ComputedClassFusions,
         fus );
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> )
##
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.ConstructionInfoCharacterTable:= 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 suffix, file, pos;

    # Omit the initial path for the name of the component in `LIBTABLE'.
    suffix:= filename;
    pos:= Position( suffix, '/' );
    while pos <> fail do
      suffix:= suffix{ [ pos+1 .. Length( suffix ) ] };
      pos:= Position( suffix, '/' );
    od;

    if not IsBound( LIBTABLE.LOADSTATUS.( suffix ) )
       or LIBTABLE.LOADSTATUS.( suffix ) = "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'
      if LIBTABLE.unload then
        for file in RecNames( LIBTABLE.LOADSTATUS ) do
          if LIBTABLE.LOADSTATUS.( file ) <> "userloaded" and
             suffix{ [ 4 .. Length( suffix ) ] }
              <> file{ [ 4 .. Length( file ) ] } then
            LIBTABLE.( file ):= rec();
            LIBTABLE.LOADSTATUS.( file ):= "unloaded";
          fi;
        od;
      fi;

      LIBTABLE.( suffix ):= rec();

      # Try to read the file.
      if not ReadTbl( Concatenation( filename, ".tbl" ) ) then
        Info( InfoCharacterTable, 1,
              "no file `", filename, ".tbl' in the GAP table library" );
        return fail;
      fi;

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

    fi;

    return LIBTABLE.( suffix );
end );


############################################################################
##
#V  CTblLibSupportedGenericIdentifiers
##
##  Make generic identifiers admissible.
##
BindGlobal( "CTblLibSupportedGenericIdentifiers", [
    [ PParseBackwards, [ "c", IsDigitChar ],
                       "Cyclic", [ 2 ] ],
    [ PParseBackwards, [ "alt(", IsDigitChar, ")" ],
                       "Alternating", [ 2 ] ],
    [ PParseBackwards, [ "sym(", IsDigitChar, ")" ],
                       "Symmetric", [ 2 ] ],
    [ PParseBackwards, [ "dihedral(", IsDigitChar, ")" ],
                       "Dihedral", [ 2 ] ],
    [ PParseBackwards, [ "2.sym(", IsDigitChar, ")" ],
                       "DoubleCoverSymmetric", [ 2 ] ],
    [ PParseBackwards, [ "2.alt(", IsDigitChar, ")" ],
                       "DoubleCoverAlternating", [ 2 ] ],
    ] );


#############################################################################
##
#F  CTblLibTrySpecialization( <tblname> )
##
BindGlobal( "CTblLibTrySpecialization", function( tblname )
    local entry, scan;

    tblname:= LowercaseString( tblname );
    for entry in CTblLibSupportedGenericIdentifiers do
      scan:= entry[1]( tblname, entry[2] );
      if scan <> fail then
        return CallFuncList( CharacterTableFromLibrary,
                   Concatenation( [ entry[3] ], scan{ entry[4] } ) );
      fi;
    od;
    return fail;
    end );


#############################################################################
##
#F  CharacterTableFromLibrary( <tblname> )
#F  CharacterTableFromLibrary( <series>, <param1>[, <param2>] )
##
InstallGlobalFunction( CharacterTableFromLibrary, function( arg )
    local i,
          j,
          tblname,
          firstname,
          filename,
          libtbl,
          librarytables,
          file,
          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
        # Perhaps it is the identifier of a generic table.
        libtbl:= CTblLibTrySpecialization( tblname );
        if libtbl <> fail then
          return libtbl;
        fi;
        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.ProjectivesInfo:= [];
          Unbind( 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.ProjectivesInfo, rec(
                                              name  := fld[i],
                                              chars := fld[i+1],
                                              map   := fus[3]
                                             ) );
              fi;
            od;
          od;
        fi;

        # Obey the construction component.
        if IsBound( libtbl.ConstructionInfoCharacterTable ) then
          if IsFunction( libtbl.ConstructionInfoCharacterTable ) then
#T for backwards compatibility, supported in Version 1.1.
            libtbl.ConstructionInfoCharacterTable( libtbl );
          else
            CallFuncList(
                ValueGlobal( libtbl.ConstructionInfoCharacterTable[1] ),
                Concatenation( [ libtbl ],
                    libtbl.ConstructionInfoCharacterTable{ [ 2 ..  Length(
                        libtbl.ConstructionInfoCharacterTable ) ] } ) );
          fi;
        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.tomfusion ) then
          if IsBound( libtbl.tomfusion.text ) then
            libtbl.tomfusion.text:= Concatenation( libtbl.tomfusion.text );
            ConvertToStringRep( libtbl.tomfusion.text );
          fi;
          libtbl.FusionToTom:= libtbl.tomfusion;
# For backwards compatibility, do not unbind the component.
          libtbl.tomidentifier:= libtbl.tomfusion.name;
        fi;

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

        if IsBound( libtbl.extInfo ) then
          libtbl.ExtensionInfoCharacterTable:= libtbl.extInfo;
          Unbind( libtbl.extInfo );
        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 ...
#T move this to the compat. mode files!
##
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:= Concatenation( i[1], "mod", String( prime ) );
        if IsBound( LIBTABLE.( filename ).( facttbl ) ) then
          facttbl:= LIBTABLE.( filename ).( facttbl );
        else
          # The factor table is in another file (hopefully a rare case).
          facttbl:= CharacterTableFromLibrary( facttbl );
        fi;
        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 a library table then
##  take the Brauer table from the library if possible,
##  apply a generic construction 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, info, t, orig, result, fus, rest;

    modtbl:= BrauerTableFromLibrary( tbl, p );
    if modtbl <> fail then
      return modtbl;
    elif HasConstructionInfoCharacterTable( tbl ) then
      info:= ConstructionInfoCharacterTable( tbl );
      if IsList( info ) and info[1] = "ConstructPermuted" then
        t:= CallFuncList( CharacterTableFromLibrary, info[2] );
        orig:= t mod p;
        if orig <> fail then
          result:= CharacterTableRegular( tbl, p );
          # Restrict the permutation (if given) to the `p'-regular classes.
          fus:= GetFusionMap( orig, t );
          if IsBound( info[3] ) then
            fus:= OnTuples( fus, Inverse( info[3] ) );
          fi;
          rest:= CompositionMaps( InverseMap( GetFusionMap( result, tbl ) ),
                     fus );
          SetIrr( result, List( Irr( orig ),
                        chi -> Character( result,
                                 ValuesOfClassFunction( chi ){ rest } ) ) );
          return result;
        fi;
      fi;
    fi;

    TryNextMethod();
    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  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  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  CompleteGroup()
##
InstallGlobalFunction( CompleteGroup,
    function() Error( "this is just a dummy function" ); end );


#############################################################################
##
#F  OfThose()
##
InstallGlobalFunction( OfThose,
    function() Error( "this is just a dummy function" ); end );


#############################################################################
##
#F  CTblLibAccessFunction( [ {<func>, <val>} ], <mode> )
#F  CTblLibAccessFunction( [ <func>, <val>, ...{, OfThose, <func>}], <mode> )
##
BindGlobal( "CTblLibAccessFunction", function( args, mode )
    local sporsimp, multinfo, simpinfo, autoinfo,
          result, list, startpos, name, vals, pos, newvals, val, resul,
          ident, pp, ext;

    if IsEmpty( args ) then
      if   mode = "all" then
        # all table names in the library
        return ShallowCopy( LIBLIST.firstnames );
      else
        # one table name in the library
        return LIBLIST.firstnames[1];
      fi;
    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] );
#T access gpisotyp data!

    # Initialize the result.
    result:= [];

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

    if     ( args[1] = IsSimple or args[1] = IsSimpleGroup
                                or args[1] = IsSimpleCharacterTable )
       and args[2] = true then
      list:= simpinfo;
      startpos:= 3;
#T careful:
#T in the case of user extensions, loop also over these files!!
    elif args[1] = IsSporadicSimple and args[2] = true then
      list:= sporsimp;
      startpos:= 3;
    else
      list:= LIBLIST.firstnames;
      startpos:= 1;
    fi;

    # Loop over the names.
    for name in list do

      # now there are two possibilities:
      # Either one filters the list `list',
      # or we reach an `OfThose', so we replace the current entry of `list'
      # by the image under the mapping instruction after `OfThose'.

      vals:= [ name ];
      pos:= startpos;
      while pos <= Length( args ) do

        newvals:= [];
        for val in vals do

          if args[ pos ] = OfThose then

            # Supported special cases are `SchurCover', `AutomorphismGroup',
            # `CompleteGroup'.
            if args[ pos + 1 ] in
                [ SchurCover, AutomorphismGroup, CompleteGroup ] then

              ident:= val;
              if IsCharacterTable( ident ) then
                ident:= Identifier( ident );
              fi;
              pp:= Position( simpinfo, ident );
              if pp = fail then
                if not IsCharacterTable( val ) then
                  val:= CharacterTable( val );
                fi;
                if HasExtensionInfoCharacterTable( val ) then
                  ext:= ExtensionInfoCharacterTable( val );
                else
                  Error( "no info about multiplier and autom. group of `",
                         val, "' stored" );
                fi;
              else
                ext:= [ multinfo[ pp ], autoinfo[ pp ] ];
              fi;
              if args[ pos + 1 ] <> AutomorphismGroup then
                if ext[1] <> "" then
                  ident:= Concatenation( ext[1], ".", ident );
                fi;
              fi;
              if args[ pos + 1 ] <> SchurCover then
                if ext[2] <> "" then
                  ident:= Concatenation( ident, ".", ext[2] );
                fi;
              fi;
              Add( newvals, ident );
#T in these cases,
#T better use `val' itself if the image is the simple group?
#T or avoid storing a possibly large number of tables?
#T or admit switching the mode?

            else

              if not IsCharacterTable( val ) then
                val:= CharacterTable( val );
              fi;
              resul:= args[ pos+1 ]( val );
              if   IsString( resul ) then
                Add( newvals, resul );
              elif ForAll( resul, IsString ) then
                UniteSet( newvals, resul );
              else
                Error( "<args>[", pos+1, "] must return a (list of) strings" );
              fi;

            fi;

          else

            if not IsCharacterTable( val ) then
              val:= CharacterTable( val );
            fi;
            resul:= args[ pos ]( val );
            if    resul = args[ pos+1 ]
               or ( IsList( args[ pos+1 ] ) and resul in args[ pos+1 ] ) then
              Add( newvals, val );
            fi;

          fi;

        od;

        pos:= pos + 2;
        vals:= newvals;

      od;

      # All conditions have been checked for `name'.
      for val in vals do
        if not IsString( val ) then
          val:= Identifier( val );
        fi;
        if mode = "all" then
          AddSet( result, val );
        else
          return val;
        fi;
      od;

    od;

    # Return the result.
    if mode = "all" then
      return result;
    else
      return fail;
    fi;
end );


#############################################################################
##
#F  AllCharacterTableNames( {<func>, <val>} )
#F  AllCharacterTableNames( <func>, <val>, ...{, OfThose, <func>} )
##
InstallGlobalFunction( AllCharacterTableNames, function( arg )
    return CTblLibAccessFunction( arg, "all" );
    end );


#############################################################################
##
#F  OneCharacterTableName( {<func>, <val>} )
#F  OneCharacterTableName( <func>, <val>, ...{, OfThose, <func>} )
##
InstallGlobalFunction( OneCharacterTableName, function( arg )
    return CTblLibAccessFunction( arg, "one" );
    end );


#############################################################################
##
#F  CTblLibSetUnload( <value> )
##
InstallGlobalFunction( CTblLibSetUnload, function( value )
    if value in [ true, false ] then
      LIBTABLE.unload:= value;
    else
      Error( "<value> must be one of `true', `false'" );
    fi;
    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     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 target, string, linelen, i, str, spec;

    if IsCharacterTable( name ) then
      name:= Identifier( name );
    fi;
    target:= fus.name;
    if IsCharacterTable( target ) then
      target:= Identifier( target );
    fi;

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

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

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

    # Add the values of the fusion map.
    Append( string, "\",[" );
    for i in fus.map do
      str:= String( 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;
    string[ Length( string ) ]:= ']';

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

    # If a specification is bound, add it.
    if IsBound( fus.specification ) then
      spec:= fus.specification;
      if IsString( spec ) then
        spec:= Concatenation( "\"", spec, "\"" );
      fi;
      Append( string, "," );
      Append( string, spec );
    fi;
    Append( string, ");\n" );

    return string;
end );


#############################################################################
##
#F  LibraryFusionTblToTom( <tblname>, <fus> )
##
InstallGlobalFunction( LibraryFusionTblToTom, 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, "ARC(\"" );
    Append( string, name );
    Append( string, "\",\"tomfusion\",rec(name:=\"" );
    Append( string, fus.name );
    Append( string, "\",map:=[" );

    # Initialize the current position in the line.
    linelen:= Length( string );

    # Add the values of the fusion map.
    for i in fus.map do
      str:= String( i );
      if linelen + Length( str ) >= 74 then
        Add( string, '\n' );
        linelen:= 0;
      fi;
      linelen:= linelen + Length( str ) + 1;
      Append( string, str );
      Add( string, ',' );
    od;
    string[ Length( string ) ]:= ']';

    # If a text is bound, add it.
    if IsBound( fus.text ) then
      Add( string, ',' );
      if linelen >= 71 then
        Add( string, '\n' );
      fi;
      Append( string, "text:=" );
      Append( string, TextString( fus.text ) );
    fi;
    Append( string, "));\n" );

    return string;
end );


#############################################################################
##
#F  ConsiderFactorBlocks( <tbl> )
##
##  (This is a utility for `CTblLibStringBrauer'.)
##
##  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;

    # Print the arguments without separators and line breaks.
    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, "[]" );
      used:= used + 2;
    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  CTblLibStringOrdinary( <tbl> )
#F  CTblLibStringBrauer( <tbl> )
#F  PrintToLib( <file>, <tbl> )
##
BindGlobal( "CTblLibStringOrdinary", function( tbl )
    local special,
          ncols,
          used,
          string,
          stream,
          i, j,
          name,
          powermap,
          primes,
          tblinfo,
          chars,
          fld,
          info,
          fus,
          names,
          linelen,
          done,
          val,
          libinfo,
          maxes,
          pos;

    # Special cases are `Irr( tbl )' and `ProjectivesInfo( tbl )' 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 );
    primes:= Set( Factors( Size( tbl ) ) );

    name:= Identifier( tbl );

    # Step 1:  Do the preparatory work, i.e.,
    #          shrink the Clifford records and remove the irreducibles,
    #          compute missing irreducibles, power maps, and table
    #          automorphisms if an underlying group is stored.
    if     IsLibraryCharacterTableRep( tbl )
       and HasConstructionInfoCharacterTable( tbl )
       and IsList( ConstructionInfoCharacterTable( tbl ) )
       and ConstructionInfoCharacterTable( tbl )[1] = "ConstructClifford" then
      if HasIrr( tbl ) then
Error( "handling of Clifford tables not yet installed!" );
        ShrinkClifford( tbl );
      fi;
    elif HasUnderlyingGroup( tbl ) then
      Irr( tbl );
      for i in primes do
        PowerMap( tbl, i );
      od;
      AutomorphismsOfTable( tbl );
    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
        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,
                  Filtered( GeneratorsOfGroup( AutomorphismsOfTable( tbl ) ),
                            p -> not IsOne( p ) ),
                  ncols, 0, false );
    else
      PrintTo( stream, "0" );
      used:= 1;
    fi;

    if     IsLibraryCharacterTableRep( tbl )
       and HasConstructionInfoCharacterTable( tbl ) then
      BlanklessPrintTo( stream, ",", ncols, used, false );
      PrintTo( stream, "\n" );
      if IsFunction( ConstructionInfoCharacterTable( tbl ) ) then
        used:= BlanklessPrintTo( stream,
                   ConstructionInfoCharacterTable( tbl ), ncols, 0,
                   false );
      else
        used:= BlanklessPrintTo( stream,
                   ConstructionInfoCharacterTable( tbl ), ncols, 0,
                   false );
#T is this meaningful?
      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 HasProjectivesInfo( tbl ) then

      used:= BlanklessPrintTo( stream,
                 Concatenation( "ARC(\"", name,
                                "\",\"projectives\",[" ),
                 ncols, 0, false );
      for j in ProjectivesInfo( tbl ) 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.
    for fld in SupportedLibraryTableComponents do
#T CTblLibStringOrdinary also for ``tables with group'' !!
      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",
             "ConstructionInfoCharacterTable",
             "ExtensionInfoCharacterTable",
             "FusionToTom",
             "IdentificationOfConjugacyClasses",
             "Identifier",
             "InfoText",
             "Irr",
             "IsPerfectCharacterTable",
             "IsSimpleCharacterTable",
             "IsSolvableCharacterTable",
             "NamesOfFusionSources",
             "NrConjugacyClasses",
             "OrdersClassRepresentatives",
             "ProjectivesInfo",
             "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
        val:= SupportedCharacterTableInfo[ i-2 ]( tbl );

        # Omit empty lists, for example in the case `ComputedPrimeBlockss'.
        if not IsList( val ) or not IsEmpty( val ) then
          used:= BlanklessPrintTo( stream,
                     Concatenation( "ARC(\"", name, "\",\"", fld, "\"," ),
                     ncols, 0, false );
          used:= BlanklessPrintTo( stream,
                     val,
                     ncols, used, true );
          BlanklessPrintTo( stream, ");" , ncols, used, false );
          PrintTo( stream, "\n" );
        fi;

      fi;
    od;
    if     HasIsSimpleCharacterTable( tbl )
       and IsSimpleCharacterTable( tbl ) then
      BlanklessPrintTo( stream,
          Concatenation( "ARC(\"", name, "\",\"isSimple\",true);\n" ),
          ncols, 0, false );
    fi;
    if HasExtensionInfoCharacterTable( tbl ) then
      BlanklessPrintTo( stream,
          Concatenation( "ARC(\"", name, "\",\"extInfo\",[\"",
                         String( ExtensionInfoCharacterTable( tbl )[1] ),
                         "\",\"",
                         String( ExtensionInfoCharacterTable( tbl )[2] ),
                         "\"]);\n" ),
          ncols, 0, false );
    fi;

    if  HasFusionToTom( tbl ) then
      PrintTo( stream, LibraryFusionTblToTom( tbl, FusionToTom( tbl ) ) );
    fi;

    # 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 );
    return string;
end );

BindGlobal( "CTblLibStringBrauer", function( 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 + offset );
          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 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 );
    return string;
end );

InstallGlobalFunction( PrintToLib, function( file, tbl )
    local suffix, pos, string;

    if IsString( file ) and IsCharacterTable( tbl ) then

      if    Length( file ) <= 3
         or file{ [ Length( file ) - 3 .. Length( file ) ] } <> ".tbl" then
        file:= Concatenation( file, ".tbl" );
      fi;

      # Print the definition of the filename (= component in `LIBTABLE')
      suffix:= file;
      pos:= Position( suffix, '/' );
      while pos <> fail do
        suffix:= suffix{ [ pos+1 .. Length( suffix ) ] };
        pos:= Position( suffix, '/' );
      od;
      string:= Concatenation( "SET_TABLEFILENAME(\"",
                   suffix{ [ 1 .. Length( suffix )-4 ] }, "\");\n" );

      if IsOrdinaryTable( tbl ) then
        Append( string, CTblLibStringOrdinary( tbl ) );
      elif IsBrauerTable( tbl ) then
        Append( string, CTblLibStringBrauer( tbl ) );
      fi;

      AppendTo( file, string );

    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;

    # If `pq' is a pair `[ <p>, <q> ]' then take a residue class mod <p>
    # of order <q>.
    # If `pq' is a triple `[ <p>, <q>, <k> ]' then take the orbits of the
    # automorphism $\ast <k>$ modulo <p>, which is assumed to have order <q>.
    if Length( pq ) = 2 then
      gen:= PowerModInt( PrimitiveRootMod( pq[1] ), (pq[1]-1)/pq[2], pq[1] );
    else
      gen:= pq[3];
    fi;

    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  FusionCharTableTom( <tbl>, <tom> )  . . . . . . . . . . .  element fusion
##
InstallMethod( FusionCharTableTom,
    "for library character table and library table of marks",
    [ IsOrdinaryTable and IsLibraryCharacterTableRep and HasFusionToTom,
      IsTableOfMarks and IsLibTomRep ],
    function( tbl, tom )
    local fus;

    fus:= FusionToTom( tbl ).map;
    if HasPermutationTom( tom ) then
      fus:= OnTuples( fus, PermutationTom( tom ) );
    fi;
    if HasClassPermutation( tbl ) then
      fus:= Permuted( fus, ClassPermutation( tbl ) );
    fi;

    return fus;
    end );


#############################################################################
##
#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( <G> )
##
##  If the argument is a group, we can use the known character table of the
##  known table table of marks.
##
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 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> stores an underlying group
##  then we delegate to this group.
##  Otherwise we return `fail'.
##
InstallOtherMethod( CharacterTable,
    "for a table of marks",
    [ IsTableOfMarks ],
    function( tom )
    local pos;

    if IsLibTomRep( tom ) then
      pos:= Position( TOM_TBL_INFO[1],
                      LowercaseString( Identifier( tom ) ) );
      if pos <> fail then
        return CharacterTable( TOM_TBL_INFO[2][ pos ] );
      fi;
    fi;
    if HasUnderlyingGroup( tom ) then
      return CharacterTable( UnderlyingGroup( tom ) );
    fi;
    return fail;
    end );


#############################################################################
##
#F  NameOfLibraryCharacterTable( <tomname> )
##
InstallGlobalFunction( NameOfLibraryCharacterTable, function( tomname )
    local pos;

    pos:= Position( TOM_TBL_INFO[1], LowercaseString( tomname ) );
    if pos <> fail then
      pos:= LibInfoCharacterTable( TOM_TBL_INFO[2][ pos ] );
      if IsRecord( pos ) then
        return pos.firstName;
      fi;
    fi;
    return fail;
    end );


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

