#############################################################################
##
##  util.gi                           CRISP                  Burkhard Höfling
##
##  Copyright © 2000-2003, 2005, 2015 Burkhard Höfling
##


#############################################################################
##
#M  CentralizesLayer(<list>, <mpcgs>)
##
InstallMethod(CentralizesLayer, "generic method", IsIdenticalObj,
    [IsListOrCollection, IsModuloPcgs], 0,
    function(l, mpcgs)

        local len, i, x, exp, j;
    
        len := Length(mpcgs);
        for i in [1..len] do
            for x in l do
                exp := ExponentsConjugateLayer(mpcgs, mpcgs[i], x);
                if exp[i] <> 1 then
                    return false;
                fi;
                for j in [1..len] do
                    if exp[j] <> 0 and i <> j then
                        return false;
                    fi;
                od;
            od;
        od;
        return true;
    end);


InstallMethod(CentralizesLayer, "for empty list", true,
    [IsListOrCollection and IsEmpty, IsModuloPcgs], 0,
    ReturnTrue);


#############################################################################
##
#M  PrimePowerGensPcSequence(<grp>)
##
InstallMethod(PrimePowerGensPcSequence, 
    "for group which can easily compute a pcgs", true,
    [IsGroup and CanEasilyComputePcgs], 0,
    function(G)
        local pcgs, primes, gens, x, o, qr, r, p, f, pos, len;
            
        primes := [];
        gens := [];
        len := 0;
        pcgs := Pcgs(G);
        
        for x in pcgs do
            o := Order(x);
            p := RelativeOrderOfPcElement(pcgs, x);
            
            # we are looking for the p-part of x
            qr := [o, 0];
            repeat
                r := qr[1];
                qr := QuotientRemainder(Integers, r, p);
            until qr[2] <> 0;
            pos := PositionSorted(primes, p);
            
            if pos <= Length(primes) and primes[pos] = p then
                Add(gens[pos], x^r);
            else #insert a new prime
                Add(primes, p, pos);
                Add(gens, [x^r], pos);
                len := len + 1;
            fi;
        od;
        return rec(primes := primes, generators := gens, pcgs := pcgs);
    end);
    
    
#############################################################################
##
#M  PrimePowerGensPcSequence(<grp>)
##
InstallMethod(PrimePowerGensPcSequence, "for group with special pcgs", true,
    [IsGroup and HasSpecialPcgs], 0,
    function(G)
        local pcgs, primes, gens, x, o, qr, r, p, f, pos, len;
            
        primes := [];
        gens := [];
        len := 0;
        pcgs := SpecialPcgs(G);
        
        # elements already have prime power order
        for x in pcgs do
            p := RelativeOrderOfPcElement(pcgs, x);
            
            pos := PositionSorted(primes, p);
            if pos <= Length(primes) and primes[pos] = p then
                Add(gens[pos], x);
            else #insert a new prime
                Add(primes, p, pos);
                Add(gens, [x], pos);
                len := len + 1;
            fi;
        od;
        return rec(primes := primes, generators := gens, pcgs := pcgs);
    end);
    

############################################################################
##
#M  IsNilpotentGroup
##
InstallMethod(IsNilpotentGroup, "for pcgs computable group", true,
    [IsGroup and IsFinite and CanEasilyComputePcgs], 0,
    function(G)
        local pseq, i, j;
        
        if HasSpecialPcgs(G) then
            TryNextMethod();
        fi;
        
        pseq := PrimePowerGensPcSequence(G);
        for i in [1..Length(pseq.generators)] do
            for j in [i+1..Length(pseq.generators)] do
                if ForAny(pseq.generators[i], x ->
                    ForAny(pseq.generators[j], y -> x^y <> x)) then
                        return false;
                fi;
            od;
        od;
        return true;
    end);
    
        
############################################################################
##
#M  NormalGeneratorsOfNilpotentResidual
##
InstallMethod(NormalGeneratorsOfNilpotentResidual, "for pcgs computable group",
    true,
    [IsGroup and IsFinite and CanEasilyComputePcgs], 
    0,
    function(G)
        local pgens, gens, i, j, x, y, c, id;
        id := One(G);
        gens := [];
        pgens := PrimePowerGensPcSequence(G);
        for i in [1..Length(pgens.generators)] do
            for j in [i+1..Length(pgens.generators)] do
                for x in pgens.generators[i] do
                    for y in pgens.generators[j] do
                        c := Comm(x, y);
                        if c <> id then
                            Add(gens, c);
                        fi;
                    od;
                od;
            od;
        od;
        return gens;
    end);
    

############################################################################
##
#M  NormalGeneratorsOfNilpotentResidual
##
InstallMethod(NormalGeneratorsOfNilpotentResidual, 
    "generic method - use lower central series",
    true,
    [IsGroup], 
    0,
    function(G)
        local lcs;
        lcs := LowerCentralSeries(G);
        return GeneratorsOfGroup(lcs[Length(lcs)]);
    end);

        
###################################################################################
##
#F  CompositionSeriesElAbFactorUnderAction(<act>, <M>, <N>)
##
##  computes a <act>-composition series of the elementary abelian normal section M/N,
##  i.e. a list M = N_0 > N_1 > ... > N_r = N of subgroups of <grp> such that 
##  there is no <grp>-invariant subgroup between N_i and N_{i+1}.
##
InstallGlobalFunction("CompositionSeriesElAbFactorUnderAction",
    function(act, M, N)

        local m;
        
        m := Pcgs(M) mod InducedPcgs(Pcgs(M), N);
        return List(PcgsCompositionSeriesElAbModuloPcgsUnderAction(act, m),
            s -> SubgroupByPcgs(M, s));
    end);
    

        
###################################################################################
##
#F  PcgsCompositionSeriesElAbModuloPcgsUnderAction(<act>, <sec>)
##
##  computes a series of pcgs representing a <act>-composition series of the elementary 
##  abelian normal section M/N, represented by the modulo pcgs <sec>.
## i.e. a list M = N_0 > N_1 > ... > N_r = N of subgroups of N such that 
##  there is no <grp>-invariant subgroup between N_i and N_{i+1}.
##
InstallGlobalFunction("PcgsCompositionSeriesElAbModuloPcgsUnderAction",
    function(act, pcgs)

        local ppcgs, bas, bases, mats, p, one, new, ser, b, v, gens, num, 
            y, len, depth, ddepth, pos, dp, t0, t;
        
        num := NumeratorOfModuloPcgs(pcgs);
        
        if Length(pcgs) = 0 then
            return [num];
        elif Length(pcgs) = 1 then
            return [num, DenominatorOfModuloPcgs(pcgs)];
        fi;
        
        ppcgs := ParentPcgs(num);
        p := RelativeOrderOfPcElement(pcgs, pcgs[1]);
            
        if IsGroup(act) then
            act := GeneratorsOfGroup(act);
        fi;
        
        if Length(act) = 0 then
            bases := IdentityMat(Length(pcgs), GF(p));
            bas := List([1..Length(bases)], i -> bases{[i..Length(bases)]});    
            Add(bas, []);
        else
            one := One(GF(p));
            mats := List(act, a ->
			    List(pcgs, x -> ExponentsOfPcElement(pcgs, x^a)*one));            
            bas := [];
        
            t0 := Runtime();
            bases := MTX.BasesCompositionSeries(GModuleByMats(mats, GF(p)));
            t := Runtime() - t0;
        
            for b in bases do
                new := MutableCopyMat(b);
                TriangulizeMat(new);
                Add(bas, new);
            od;
        fi;
        
        Sort(bas, function(a, b) return Length(a) > Length(b); end);
        
        ser := [];
        
        ddepth := List(DenominatorOfModuloPcgs(pcgs), 
            y -> DepthOfPcElement(ppcgs, y));
            
        for b in bas do
            gens := List(DenominatorOfModuloPcgs(pcgs));
            len := Length(gens);
            depth := ShallowCopy(ddepth);

            for v in Reversed(b) do
                y := PcElementByExponentsNC(pcgs, v);
                dp := DepthOfPcElement(ppcgs, y);
                pos := PositionSorted(depth, dp);
                while pos <= len and dp = depth[pos] do
                    y := ReducedPcElement(ppcgs, y, gens[pos]);
                    dp := DepthOfPcElement(ppcgs, y);
                    pos := PositionSorted(depth, dp);
                od;
                Add(gens, y, pos);
                Add(depth, dp, pos);
                len := len + 1;
            od;
            Add(ser, InducedPcgsByPcSequenceNC(ppcgs, gens));
        od;
        return ser;
    end);


###################################################################################
##
#M  ChiefSeries(<grp>)
##
InstallMethod(ChiefSeries, 
    "for pcgs computable group refining PcgsElementaryAbelianSeries",
    true, [IsGroup and CanEasilyComputePcgs], 1,
    function(G)
    
        local gens, pcgs, inds, elabser, i, ser;
        
        pcgs := PcgsElementaryAbelianSeries(G);
        inds := IndicesEANormalSteps(pcgs);
        elabser := List(inds, 
             i -> InducedPcgsByPcSequence(pcgs, pcgs{[i..Length(pcgs)]}));        
        ser := [];
        for i in [1..Length(elabser)-1] do
            Append(ser, 
                PcgsCompositionSeriesElAbModuloPcgsUnderAction(
                    pcgs{[1..inds[i]-1]}, elabser[i] mod elabser[i+1]));
            Unbind(ser[Length(ser)]);
        od;
        
        ser := List(ser, p -> SubgroupByPcgs(G, p));
        Add(ser, TrivialSubgroup(G));
        return ser;
    end);
    

###################################################################################
##
#M  CompositionSeriesUnderAction(<act>, <grp>)
##
InstallMethod(CompositionSeriesUnderAction, "for soluble group",
    true,
    [IsListOrCollection, IsGroup and IsSolvableGroup], 0,
    function(act, G)
    
        local gens, pcgs, inds, elabser, i, ser;
        
        if IsGroup(act) then
            act := ShallowCopy(GeneratorsOfGroup(act));
        fi;
        
        for i in [1..Length(act)] do
             if FamilyObj(act[i]) = FamilyObj(One(G)) then
                 act[i] := ConjugatorAutomorphism(G, act[i]);
             fi;
        od;
        
        pcgs := PcgsElementaryAbelianSeries(G);
        if pcgs = fail then
            TryNextMethod();
        fi;

#        InvariantElementaryAbelianSeries with four arguments seems to be buggy
#        elabser := List(InvariantElementaryAbelianSeries(G, act, TrivialSubgroup(G), true),
#            S -> InducedPcgs(pcgs, S));
        elabser := List(InvariantElementaryAbelianSeries(G, act),
            S -> InducedPcgs(pcgs, S));

        ser := [];
        for i in [1..Length(elabser)-1] do
            Append(ser, 
                PcgsCompositionSeriesElAbModuloPcgsUnderAction(
                    act, elabser[i] mod elabser[i+1]));
            Unbind(ser[Length(ser)]);
        od;
        
        ser := List(ser, p -> SubgroupByPcgs(G, p));
        Add(ser, TrivialSubgroup(G));
        return ser;
    end);
    

###################################################################################
##
#M  CompositionSeriesUnderAction(<act>, <grp>)
##
CRISP_RedispatchOnCondition(CompositionSeriesUnderAction,     
    "redispatch if group is finite or soluble",
    true,
    [IsListOrCollection, IsGroup],
    [, IsFinite and IsSolvableGroup], # no conditions on first argument
    0);


###################################################################################
##
#F  PcgsElementaryAbelianSeriesFromPrimeOrdersPcgs
##
InstallGlobalFunction("PcgsElementaryAbelianSeriesFromPrimeOrdersPcgs",
    function(pcgs)
    
        local ro, p, new, i, nsteps, j, k,  n, d, 
            npcgs, dpcgs, der, depths, x, m;
        
        if IsPcgsElementaryAbelianSeries(pcgs) then
            return pcgs;
        fi;
        
        ro := RelativeOrders( pcgs );
        
        i := 1;
        nsteps := [1];
        
        while i <= Length(pcgs) do        
        
            p := ro[i];
            n := DepthOfPcElement(pcgs, pcgs[i]^p);
            j := i + 1;
            
            while j < n do
            
                if ro[j] = p then
                    d := DepthOfPcElement(pcgs, pcgs[j]^p);
                
                    if d < n then
                        n := d;
                    fi;
                
                    d := Minimum(List([i..j-1], k ->
                        DepthOfPcElement(pcgs, Comm(pcgs[j], pcgs[k]))));
                    
                    if d < n then
                        n := d;
                    fi;
                    
                    j := j + 1;

                else
                    n := j;
                fi;
            od;
            
            # now find smallest normal subgroup of the series containing 
            # the previously computed group
            j := 1;
            while i < n and j < n do
                k := n;
                while i < n and k <= Length(pcgs) do
                    d := DepthOfPcElement(pcgs, Comm(pcgs[k], pcgs[j]));
                    if d < n then
                        n := d;
                    fi;
                    k := k + 1;
                od;
                j := j + 1;
            od;
            
            if n < i then 
                Error("internal error: PcgsElementaryAbelianSeriesFromPrimeOrdersPcgs, wrong depth");
            elif n = i then # no abelian normal section found - change pcgs
                Info(InfoPcGroup, 2, "changing pcgs");
                npcgs := InducedPcgsByPcSequenceNC(pcgs, pcgs{[i..Length(pcgs)]});
                der := DerivedSubgroup(GroupOfPcgs(npcgs));
                dpcgs := InducedPcgs(pcgs, der);
                m := npcgs mod dpcgs;
                dpcgs := List(dpcgs);
                depths := List(dpcgs, x -> DepthOfPcElement(pcgs, x));
                for x in Reversed(m) do
                    # elements may already be contained in the subgroup, so don't check return values'
                    AddPcElementToPcSequence(pcgs, dpcgs, depths, x^p);
                od;
                dpcgs := CanonicalPcgs(
                    InducedPcgsByPcSequenceNC(pcgs, dpcgs));
                m := npcgs mod dpcgs;
                pcgs := PcgsByPcSequenceNC(FamilyObj(pcgs[1]), 
                    Concatenation(pcgs{[1..i-1]}, m, dpcgs));
                ro := Concatenation(ro{[1..i-1]}, RelativeOrders(m), RelativeOrders(dpcgs));
                SetRelativeOrders(pcgs, ro );
                Info(InfoPcGroup, 2, "changing pcgs from ",i," ",pcgs);
                n := n + Length(m);
            else
                Info(InfoPcGroup, 2, "normal step found at ",n);
            fi;
            Add(nsteps, n);
            i := n;
        od;

        SetIsPcgsElementaryAbelianSeries(pcgs, true);
        SetIndicesEANormalSteps(pcgs, nsteps);
        return pcgs;
    end);


###################################################################################
##
#M  PcgsElementaryAbelianSeries
##
InstallMethod(PcgsElementaryAbelianSeries, "CRISP method for pc group", 
    true,
    [IsPcGroup and IsFinite],
    1, # this makes the priority higher than the(slow) library method which uses SpecialPcgs
    function(G)
        local pcgs;
        pcgs := Pcgs(G);
        if not IsPrimeOrdersPcgs(pcgs) then
	      TryNextMethod();
        fi;
        return PcgsElementaryAbelianSeriesFromPrimeOrdersPcgs(pcgs);
    end);


###################################################################################
##
#M  PcgsElementaryAbelianSeries
##
InstallMethod(PcgsElementaryAbelianSeries, "for pc group with parent group", true,
    [IsPcGroup and IsFinite and HasParent], 
    1, # this makes the priority higher than the(slow) library method which uses SpecialPcgs
    function(G)
        local P, pcgs, ppcgs, depths, pinds, inds, i, j;
        
        P := Parent(G);
        if HasPcgsElementaryAbelianSeries(P) then
            ppcgs := PcgsElementaryAbelianSeries(P);
            pcgs := CanonicalPcgs(InducedPcgs(ppcgs, G));
            # now find an elementary abelian series
            depths := List(pcgs, x -> DepthOfPcElement(ppcgs, x));
            pinds := IndicesEANormalSteps(ppcgs);
            inds := [];
            i := 1;
            for j in [1..Length(depths)] do
                if depths[j] >= pinds[i] then
                    Add(inds, j);
                    repeat
                        i := i + 1;
                    until depths[j] < pinds[i];
                fi;
            od;
            Add(inds, Length(pcgs)+1);
            SetIsPcgsElementaryAbelianSeries(pcgs, true);
            SetIndicesEANormalSteps(pcgs, inds);
            return pcgs;
        else
            TryNextMethod();
        fi;
    end);
    

###################################################################################
##
#M  PcgsElementaryAbelianSeries
##
InstallMethod(PcgsElementaryAbelianSeries, "finite group", true,
    [IsGroup and IsFinite], 0,
    function(G) 
        if not IsSolvableGroup(G) then
            Error("The group <G> must be soluble");
        elif IsPrimeOrdersPcgs(Pcgs(G)) then
            return PcgsElementaryAbelianSeriesFromPrimeOrdersPcgs(Pcgs(G));
        else
            TryNextMethod();
        fi;
    end);


###################################################################################
##
#M  PcgsElementaryAbelianSeries
##
InstallMethod(PcgsElementaryAbelianSeries, "for group with parent group", true,
    [IsGroup and HasParent], 0, 
    function(G)
        local P, pcgs, ppcgs, pinds, depths, inds, i, j;
        
        P := Parent(G);
        if HasPcgsElementaryAbelianSeries(P) then
            ppcgs := PcgsElementaryAbelianSeries(P);
            pcgs := CanonicalPcgs(InducedPcgs(ppcgs, G));

            depths := List(pcgs, x -> DepthOfPcElement(ppcgs, x));
            pinds := IndicesEANormalSteps(ppcgs);
            inds := [];
            i := 1;
            for j in [1..Length(depths)] do
                if depths[j] >= pinds[i] then
                    Add(inds, j);
                    repeat
                        i := i + 1;
                    until depths[j] < pinds[i];
                fi;
            od;
            Add(inds, Length(pcgs)+1);
            SetIsPcgsElementaryAbelianSeries(pcgs, true);
            SetIndicesEANormalSteps(pcgs, inds);
            return pcgs;
        else
            TryNextMethod();
        fi;
    end);

    
#############################################################################
##
#M  SiftedPcElementWrtPcSequence(<pcgs>, <seq>, <depths>, <x>)
##
##  sifts an element x through a list seq of elements resembling a Pcgs,
##  reducing x if it has the same depth as an element in seq
##
InstallMethod(SiftedPcElementWrtPcSequence, "generic method",
    function(fpcgs, fseq, fdepths, fx) return IsIdenticalObj(fpcgs, fseq) and 
        IsIdenticalObj(fseq, CollectionsFamily(fx));
    end,
    [IsPcgs, IsListOrCollection, IsList, IsMultiplicativeElementWithInverse], 0, 
    function(pcgs, seq, depths,  x)

    local d, pos;
        
    d := DepthOfPcElement(pcgs, x);
    pos := PositionSorted(depths, d);
    
    while pos <= Length(depths) and depths[pos] = d do
        x := ReducedPcElement(pcgs, x, seq[pos]);
        d := DepthOfPcElement(pcgs, x);
        pos := PositionSorted(depths, d);
    od;
    
    return x;
end);


#############################################################################
##
#M  SiftedPcElementWrtPcSequence(<pcgs>, <seq>, <depths>, <x>)
##
InstallMethod(SiftedPcElementWrtPcSequence, "method for an empty collection",
    function(a,b,c, d) return 
        IsIdenticalObj(a, CollectionsFamily(d));
    end,
    [IsPcgs, IsListOrCollection and IsEmpty, IsList, IsMultiplicativeElementWithInverse], 
    0, 
    function(pcgs, seq, depths, x)
        return x;
    end);
    
    
#############################################################################
##
#M  AddPcElementToPcSequence(<pcgs>, <seq>, <depths>, <x>)
##
InstallMethod(AddPcElementToPcSequence, "generic method",
    function(fpcgs, fseq, fdepths, fx) return IsIdenticalObj(fpcgs, fseq) and 
        IsIdenticalObj(fseq, CollectionsFamily(fx));
    end,
    [IsPcgs, IsListOrCollection, IsList, IsMultiplicativeElementWithInverse], 0, 
    function(pcgs, seq, depths,  x)

    local d, pos, len;
    
    d := DepthOfPcElement(pcgs, x);
    pos := PositionSorted(depths, d);
    len := Length(depths);
    
    while pos <= len and depths[pos] = d do
        x := ReducedPcElement(pcgs, x, seq[pos]);
        d := DepthOfPcElement(pcgs, x);
        pos := PositionSorted(depths, d);
    od;
    
    if d > Length(pcgs) then
        return false;
    fi;
    
    Add(seq, x, pos);
    Add(depths, d, pos);
    return true;
end);


#############################################################################
##
#M  AddPcElementToPcSequence(<pcgs>, <seq>, <depths>, <x>)
##
InstallMethod(AddPcElementToPcSequence, "method for an empty collection",
    function(a,b,d, c) return 
        IsIdenticalObj(a, CollectionsFamily(c));
    end,
    [IsPcgs, IsListOrCollection and IsEmpty, IsList and IsEmpty, IsMultiplicativeElementWithInverse], 
    0, 
    function(pcgs, seq, depths, x)
        local d;
        d := DepthOfPcElement(pcgs, x);
        if d > Length(pcgs) then
            return false;
        fi;
        depths[1] := d;
        seq[1] := x;
        return true;
    end);


#############################################################################
##
#M  CRISP_SmallGeneratingSet(<G>)
##
InstallMethod(CRISP_SmallGeneratingSet, "subset of pcgs",
    [IsGroup and CanEasilyComputePcgs],
        function(G)
            local H, gens, g;
            H := TrivialSubgroup(G);
            gens := [];
            for g in Pcgs(G) do
                if not g in H then
                    Add(gens, g);
                    H := ClosureGroup(H,g);
                    if Size(H)=Size(G) then
                        return gens;
                    elif Length(gens)>Length(GeneratorsOfGroup(G)) then
                        TryNextMethod();
                    fi;
                fi;
            od;
            Error("G is not generated by pcgs");
        end);


#############################################################################
##
#M  CRISP_SmallGeneratingSet(<G>)
##
InstallMethod(CRISP_SmallGeneratingSet, "use generators",
    [IsGroup], GeneratorsOfGroup);


#############################################################################
##
#M  CRISP_SmallGeneratingSet(<G>)
##
InstallMethod(CRISP_SmallGeneratingSet, "use SmallGeneratingSet",
    [IsGroup and HasSmallGeneratingSet], SUM_FLAGS, SmallGeneratingSet);


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