#############################################################################
##
#W  pcpgrps.gi                   Polycyc                         Bettina Eick
##

#############################################################################
##
#F Create a pcp group by collector
##
## The trivial group is missing.
##
InstallGlobalFunction( PcpGroupByCollectorNC, function( coll )
    local n, l, e, f, G, rels;
    n := coll![ PC_NUMBER_OF_GENERATORS ];

    if n > 0 then
        l := IdentityMat( n );
    fi;

    e := List( [1..n], x -> PcpElementByExponentsNC( coll, l[x] ) );
    f := PcpElementByGenExpListNC( coll, [] );
    G := GroupWithGenerators( e, f );
    SetCgs( G, e );

    return G;
end );

InstallGlobalFunction( PcpGroupByCollector, function( coll )
    UpdatePolycyclicCollector( coll );
    if not IsConfluent( coll ) then 
        return fail; 
    else
        return PcpGroupByCollectorNC( coll );
    fi;
end );

#############################################################################
##
#F Print( <G> )
##
InstallMethod( PrintObj, true, [ IsPcpGroup ], 0,
function( G )
    Print("Pcp-group with orders ",  List( Igs(G), RelativeOrderPcp ) );
end );

InstallMethod( ViewObj, true, [ IsPcpGroup ], SUM_FLAGS,
function( G )
    Print("Pcp-group with orders ",  List( Igs(G), RelativeOrderPcp ) );
end );

#############################################################################
##
#M Igs( <pcpgrp> )
#M Ngs( <pcpgrp> )
#M Cgs( <pcpgrp> )
##
InstallMethod( Igs, true, [IsPcpGroup], 0,
function( G ) 
    if HasCgs( G ) then return Cgs(G); fi;
    if HasNgs( G ) then return Ngs(G); fi;
    return Igs( GeneratorsOfGroup(G) ); 
end );

InstallMethod( Ngs, true, [IsPcpGroup], 0,
function( G ) 
    if HasCgs( G ) then return Cgs(G); fi;
    return Ngs( Igs( GeneratorsOfGroup(G) ) ); 
end );

InstallMethod( Cgs, true, [IsPcpGroup], 0,
function( G ) 
    return Cgs( Igs( GeneratorsOfGroup(G) ) ); 
end );

NiceIgs := function( G )
    if HasCgs(G) then
        return Cgs(G);
    elif HasNgs(G) then
        return Ngs(G);
    else
        return Igs(G);
    fi;
end;

#############################################################################
##
#M Membershiptest for pcp groups
##
InstallMethod( \in, 
               true, [IsPcpElement, IsGroup and IsPcpGroup], 0,
function( g, G ) 
    return ReducedByIgs( Igs(G), g ) = One(G); 
end );

#############################################################################
##
#M Random( G )
##
InstallMethod( Random, 
               true, [IsGroup and IsPcpGroup], 0, 
function( G )
    local pcp, rel, g, i;
    pcp := Pcp(G);
    rel := RelativeOrdersOfPcp( pcp );
    g   := [];
    for i in [1..Length(rel)] do
        if rel[i] = 0 then 
            g[i] := Random( Integers );
        else
            g[i] := Random( [0..rel[i]-1] );
        fi;
    od;
    return MappedVector( g, pcp );
end );

#############################################################################
##
#M SubgroupByIgs( G, igs [, gens] )
##
## create a subgroup and set igs. If gens is given, compute pcs defined
## by <gens, pcs> and use this. Note: this function does not check if the
## generators are in G.
##
SubgroupByIgs := function( arg )
    local U, pcs;
    if Length( arg ) = 3 then
        pcs := AddToIgs( arg[2], arg[3] );
    else
        pcs := arg[2];
    fi;
    U := SubgroupNC( Parent(arg[1]), pcs );
    SetIgs( U, pcs );
    return U;
end;
    
#############################################################################
##
#M SubgroupByIgsAndIgs( G, fac, nor )
##
## fac and nor are igs for a factor and a normal subgroup. This function 
## computes an igs for the subgroups generated by fac and nor and sets it
## in the subgroup. This is a no-check function
##
SubgroupByIgsAndIgs := function( G, fac, nor )
    return SubgroupByIgs( G, AddIgsToIgs( fac, nor ) );
end;

#############################################################################
##
#M IsSubset for pcp groups
##
InstallMethod( IsSubset, true, [ IsPcpGroup, IsPcpGroup ], 0,
function( H, U )
    return ForAll( GeneratorsOfGroup(U), x -> x in H );
end );

#############################################################################
##
#M IsNormal( H, U ) . . . . . . . . . . . . . . .test if U is normalized by H
##
InstallMethod( IsNormalOp, true, [ IsPcpGroup, IsPcpGroup ], 0,
function( H, U )
    local u, h;
    for h in GeneratorsOfPcp( Pcp(H, U)) do
        for u in Igs(U) do
            if not u^h in U then return false; fi;
        od;
    od;
    return true;
end );

#############################################################################
##
#M Size( <pcpgrp> )
##
InstallMethod( Size, 
               true, [ IsPcpGroup ], 0,
function( G )
    local pcs, rel;
    pcs := Igs( G );
    rel := List( pcs, RelativeOrderPcp );
    if ForAny( rel, x -> x = 0 ) then
        return infinity;
    else
        return Product( rel );
    fi;
end );

#############################################################################
##
#M CanComputeIndex( <pcpgrp>, <pcpgrp> )
#M CanComputeSize( <pcpgrp> )
#M CanComputeSizeAnySubgroup( <pcpgrp> )
##
InstallMethod( CanComputeIndex, true, [IsPcpGroup, IsPcpGroup], 0,
function( G, H ) return true; end );
InstallTrueMethod( CanComputeSize, IsPcpGroup );
InstallTrueMethod( CanComputeSizeAnySubgroup, IsPcpGroup );

#############################################################################
##
#M IndexNC/Index( <pcpgrp>, <pcpgrp> )
##
InstallMethod( IndexNC, true, [IsPcpGroup, IsPcpGroup], 0,
function( H, U )
    local pcp, rel;
    pcp := Pcp( H, U );
    rel := RelativeOrdersOfPcp( pcp );
    if ForAny( rel, x -> x = 0 ) then
        return infinity;
    else
        return Product( rel );
    fi;
end );

InstallMethod( IndexOp, true, [IsPcpGroup, IsPcpGroup], 0,
function( H, U )
    if not IsSubgroup( H, U ) then 
        Error("H must be contained in G");
    fi;
    return IndexNC( H, U );
end );

#############################################################################
##
#M <pcpgrp> = <pcpgrp> 
##
InstallMethod( \=,
               true, [IsPcpGroup, IsPcpGroup], 0,
function( G, H )
    return Cgs( G ) = Cgs( H );
end);

#############################################################################
##
#M ClosureGroup( <pcpgrp>, <pcpgrp> )
##
InstallMethod( ClosureGroup, 
               true, [IsPcpGroup, IsPcpGroup], 0,
function( G, H )
    local pcs, U;

    if G = Parent(G) then
        return G;
    elif H = Parent(H) then
        return H;
    elif HasIgs( G ) then
        U := SubgroupByIgs( Parent(G), Igs(G), GeneratorsOfGroup(H) );
    elif HasIgs( H ) then
        U := SubgroupByIgs( Parent(G), Igs(H), GeneratorsOfGroup(G) );
    else
        U := Subgroup( Parent( G ),
             Concatenation(GeneratorsOfGroup(G), GeneratorsOfGroup(H)));
    fi;
    return U;
end );
 
#############################################################################
##
#F HirschLength( <G> )
##
InstallMethod( HirschLength, true, [IsPcpGroup], 0, 
function( G )
    local pcs, rel;
    pcs := Igs( G );
    rel := List( pcs, RelativeOrderPcp );
    return Length( Filtered( rel, x -> x = 0 ) );
end );

#############################################################################
##
#M CommutatorSubgroup( G, H )
##
InstallMethod( CommutatorSubgroup, true, [ IsPcpGroup, IsPcpGroup], 0,
function( G, H )
    local pcsG, pcsH, coms, i, j, U, u;

    pcsG := Igs(G);  
    pcsH := Igs(H);

    # if G = H then we need fewer commutators
    coms := [];
    if pcsG = pcsH then
        for i in [1..Length(pcsG)] do
            for j in [1..i-1] do
                Add( coms, Comm( pcsG[i], pcsH[j] ) );
            od;
        od;
        coms := Igs( coms );
        U    := SubgroupByIgs( Parent(G), coms );
    else
        for u in pcsG do
            coms := AddToIgs( coms, List( pcsH, x -> Comm( u, x ) ) );
        od;
        U    := SubgroupByIgs( Parent(G), coms );

        # In order to conjugate with fewer elements, compute <U,V>. If one is
        # normal than we do not need the normal closure, see Glasby 1987.
        if not (IsBound( G!.isNormal ) and G!.isNormal) and
           not (IsBound( H!.isNormal ) and H!.isNormal) then
            U := NormalClosure( ClosureGroup( G, H ), U );
        fi;
    fi;
    return U;
end );

#############################################################################
##
#M DerivedSubgroup( G )
##
InstallMethod( DerivedSubgroup, true, [IsPcpGroup], 0,
function( G ) return CommutatorSubgroup(G, G); end );

#############################################################################
##
#M PRump( G, p ). . . . . smallest normal subgroup N of G with G/N elementary
##                        abelian p-group.
##
InstallMethod( PRumpOp, true, [IsPcpGroup, IsPosInt], 0,
function( G, p )
    local D, pcp, new;
    D := DerivedSubgroup(G);
    pcp := Pcp( G, D );
    new := List( pcp, x -> x^p );
    return SubgroupByIgs( G, Igs(D), new );
end );

#############################################################################
##
#M IsNilpotentGroup( <pcpgrp> )
##
InstallMethod( IsNilpotentGroup, 
               true, [ IsPcpGroup and HasLowerCentralSeriesOfGroup ], 0,
function( G )
    local   lcs;
    
    lcs := LowerCentralSeries( G );
    return IsTrivial( lcs[ Length(lcs) ] );
end );

InstallMethod( IsNilpotentGroup, 
               true, [IsPcpGroup], 0,
function( G )
    local l, U, V, pcp, n;

    l := HirschLength(G); 
    U := ShallowCopy( G );

    repeat 

        # take next term of lc series
        U!.isNormal := true;
        V := CommutatorSubgroup( G, U );

        # if we arrive at the trivial group
        if Size( V ) = 1 then return true; fi;

        # get quotient U/V
        pcp := Pcp( U, V );
        
        # if U=V then the series has terminated at a non-trivial group
        if Length( pcp ) = 0 then
            return false;
        fi;

        # get the Hirsch length of U/V 
        n := Length( Filtered( RelativeOrdersOfPcp( pcp ), x -> x = 0));

        # compare it with l
        if n = 0 and l <> 0  then return false; fi;
        l := l - n;
         
        # iterate
        U := ShallowCopy( V );
    until false;
end );

#############################################################################
##
#M IsElementaryAbelian( <pcpgrp> )
##
InstallMethod( IsElementaryAbelian, true, [IsPcpGroup], 0,
function( G )
    local rel, p;
    if not IsFinite(G) or not IsAbelian(G) then return false; fi;
    rel := List( Igs(G), RelativeOrderPcp );
    if Length(Set(rel)) > 1 then return false; fi;
    if ForAny( rel, x -> not IsPrime(x) ) then return false; fi;
    p := rel[1];
    return ForAll( RelativeOrdersOfPcp( Pcp( G, "snf" ) ), x -> x = p );
end );

#############################################################################
##
#F IsFreeAbelian( <pcpgrp> )
##
IsFreeAbelian := function( G )
    local pcp;
    if not IsAbelian(G) then return false; fi;
    return ForAll( RelativeOrdersOfPcp( Pcp( G, "snf" ) ), x -> x = 0 );
end;

#############################################################################
##
#F AbelianInvariants( <pcpgrp > )
##
InstallMethod( AbelianInvariants, true, [IsPcpGroup], 0,
function( G )
    return RelativeOrdersOfPcp( Pcp(G, DerivedSubgroup(G),"snf") );
end );

#############################################################################
##
#F NormalClosure( K, U )
##
InstallMethod( NormalClosureOp, 
               true, [IsPcpGroup, IsPcpGroup], 0,
function( K, U )
    local tmpN, newN, done, id, gensK, pcsN, k, n, c, N;

    # take initial pcs
    pcsN := ShallowCopy( Cgs(U) );
    if Length( pcsN ) = 0 then return U; fi;

    # take generating sets
    id := One( K );
    gensK := GeneratorsOfGroup(K);
    gensK := List( gensK, x -> ReducedByIgs( pcsN, x ) );
    gensK := Filtered( gensK, x -> x <> id );

    done := false;
    repeat
        done := true;
        tmpN := ShallowCopy( pcsN );
        for k in gensK do
            for n in tmpN do
                c := ReducedByIgs( pcsN, Comm( k, n ) );
                if c <> id then 
                    newN := AddToIgs( pcsN, [c] ); 
                    if newN <> pcsN then 
                        done := false;
                        pcsN := Cgs( newN );
                    fi;
                fi;
            od;
        od;
        #Print(Length(pcsN)," obtained \n");
    until done;

    # set up result
    return SubgroupByIgs( Parent(U), pcsN );
end);

#############################################################################
##
#F ExponentsByRels . . . . . . . . . . . . . . .elements written as exponents
##
ExponentsByRels := function( rel )
    local exp, idm, i, t, j, e, f;
    exp := [List( rel, x -> 0 )];
    idm := IdentityMat( Length( rel ) );
    for i in Reversed( [1..Length(rel)] ) do
        t := [];
        for j in [1..rel[i]] do
            for e in exp do
                f := ShallowCopy( e );
                f[i] := j-1;
                Add( t, f );
            od;
        od;
        exp := t;
    od;
    return exp;
end;

#############################################################################
##
#F AsList( G )
##
InstallMethod( AsList, true, [IsPcpGroup], 0,
function( G )
    local pcs, rel, col, n, sub, exp, elms, i, g, r, t, j, e;
    pcs := Cgs(G);
    rel := List( pcs, RelativeOrderPcp );
    if ForAny( rel, x -> x = 0 ) then return [fail]; fi;
    if Length( pcs ) = 0 then return [One(G)]; fi;

    # if the pcs is a subset of the defining pc sequence
    if ForAll( pcs, x -> IsGeneratorOrInverse(x) ) then
        col := Collector( pcs[1] );
        n   := NumberOfGenerators( col );
        sub := IdentityMat( n ){ List( pcs, x -> x!.word[1] ) };
        exp := List( ExponentsByRels( rel ), x -> x * sub );
        return List( exp, x -> PcpElementByExponentsNC( col, x ) );
    fi;

    # otherwise we need to multiply elements
    elms := [One(G)];
    for i in Reversed( [1..Length(pcs)] ) do
        g := pcs[i];
        r := rel[i];
        t := [];
        for j in [1..r] do
            for e in elms do
                Add( t, g^(j-1) * e );
            od;
        od;
        elms := t;
    od;
    return elms;
end );

#############################################################################
##
#F AsSSortedList( G )
##
InstallMethod( AsSSortedList, true, [IsPcpGroup], 0,
function( G )
    return AsList( G );
end );
