#############################################################################
##
#W  autos.gi                 AutPGrp package                     Bettina Eick
##

#############################################################################
##
#F LinearActionPGAut( <P>, <M>, <aut> )
##
BindGlobal( "LinearActionPGAut", function( P, M, aut )
    local p, gensP, pcgsM, gensG, defn, imgs, mat, d;

    # set up
    p := PrimePGroup( P );
    gensP := GeneratorsOfGroup( P );
    pcgsM := Pcgs( M );
    gensG := DifferenceLists( gensP, pcgsM );

    # compute matrix
    defn := P!.definitions;
    imgs := List( aut!.baseimgs, x -> MappedPcElement( x, aut!.pcgs, gensG ));
    for d in defn do
        if not IsNegRat( d ) then
            Add( imgs, SubstituteDef( d, imgs, p ) );
        fi;
    od;
    imgs := imgs{List( pcgsM, x -> Position( gensP, x ) )};

    # two cases - the first for efficiency
    if imgs = pcgsM then
        aut!.mat := 1;
    else
        mat := List(imgs, x -> ExponentsOfPcElement(pcgsM, x)*One(M!.field));
        mat := ImmutableMatrix( M!.field, mat );
        aut!.mat :=  mat;
    fi;
end );

#############################################################################
##
#F LinearActionAutGrp( <A>, <P>, <M> )
##
InstallGlobalFunction( LinearActionAutGrp,
  function( A, P, M )
    local aut;

    # add information
    for aut in A.glAutos do
        LinearActionPGAut( P, M, aut );
    od;
    for aut in A.agAutos do
        LinearActionPGAut( P, M, aut );
    od;
    A.field := M!.field;
    A.prime := PrimePGroup( P );
    A.one!.mat := 1;
  end);

#############################################################################
##
#F CentralAutos( <G>, <N> )
##
BindGlobal( "CentralAutos", function( G, N )
    local base, pcgs, cent, b, i, imgs, aut;

    base := Pcgs(N);
    pcgs := Pcgs(G);
    cent := [];
    for b in base do
        for i in [1..RankPGroup(G)] do
            imgs := ShallowCopy( pcgs );
            imgs[i] := imgs[i] * b;
            aut := PGAutomorphism( G, pcgs, imgs );
            Add( cent, aut );
        od;
    od;
    return cent;
end );

#############################################################################
##
#F InduceAuto( <F>, <aut> )
##
BindGlobal( "InduceAuto", function( F, aut )
    local pcgsF, baseF, imgsG, imgsF, hom;
    pcgsF := Pcgs( F );
    baseF := pcgsF{[1..RankPGroup(F)]};
    imgsG := aut!.baseimgs;
    imgsF := List( imgsG, x -> MappedPcElement( x, aut!.pcgs, pcgsF ) );
    if CHECK then 
        hom := GroupHomomorphismByImages( F, F, baseF, imgsF );
        if not IsGroupHomomorphism( hom ) then 
            Error("no hom");
        elif not IsBijective( hom ) then
            Error("no bijection");
        fi;
    fi;
    return PGAutomorphism( F, baseF, imgsF );
end );

#############################################################################
##
#F InduceAutGroup( <A>, <Q>, <P>, <M>, <U> )
##
InstallGlobalFunction( InduceAutGroup,
  function( A, Q, P, M, U )
    local p, r, F, s, t, pcgsF, pcgsL, L, B, central;

    # set up
    p := PrimePGroup( P );
    r := RankPGroup( P );

    # create factor
    F := Range( EpimorphismQuotientSystem(Q) );
    SetIsPGroup( F, true );
    SetPrimePGroup( F, p );
    SetRankPGroup( F, r );
    pcgsF := Pcgs(F);

    # get definitions for F
    F!.definitions := RewriteDef( pcgsF, Q!.definitions, p );

    # get p-centre of F
    s := Length( Pcgs(P) ) - Length( Pcgs(M) );
    t := s + Length( Pcgs(M) ) - Length( Pcgs(U) );
    pcgsL := InducedPcgsByPcSequenceNC( pcgsF, pcgsF{[s+1..t]} );
    L := SubgroupByPcgs( F, pcgsL );

    # induce autos
    B := rec();
    B.glAutos := List( A.glAutos, x -> InduceAuto( F, x ) );
    B.agAutos := List( A.agAutos, x -> InduceAuto( F, x ) );
    central   := CentralAutos( F, L );
    Append( B.agAutos, central );

    # add information
    B.glOrder := A.glOrder;
    B.agOrder := Concatenation( A.agOrder, List( central, x -> p ) );
    B.group   := F;
    B.one     := IdentityPGAutomorphism( F );
    B.size    := B.glOrder * Product( B.agOrder );

    # if possible add projective operation
    if IsBound( A.glOper ) then B.glOper := A.glOper; fi;

    # and return
    return B;
  end);

#############################################################################
##
#F ConvertAuto( <aut>, <iso> )
##
BindGlobal( "ConvertAuto", function( aut, iso )
    local G, pcgs, imgs, auto;
    
    G := Source( iso );
    pcgs := SpecialPcgs( G );
    imgs := List( pcgs, x -> ImagesRepresentative( iso, x ) );
    imgs := List( imgs, x -> ImagesRepresentative( aut, x ) );
    imgs := List( imgs, x -> PreImagesRepresentative( iso, x ) );
   
    if not CHECK then
        auto := GroupHomomorphismByImagesNC(G, G, pcgs, imgs );
        SetIsBijective( auto, true );
    else
        auto := GroupHomomorphismByImages( G, G, AsList(pcgs), imgs );
        if not IsGroupHomomorphism( auto ) then
            Error("automorphism is no homomorphism");
        elif not IsBijective( auto ) then
            Error("automorphism is not bijective");
        fi;
    fi;

    return auto;
end );

#############################################################################
##
#F ConvertAutGroup ( <A>, <G> )
##
InstallGlobalFunction( ConvertAutGroup,
  function( A, G )
    local r, gens, imgs, iso, C;

    r := RankPGroup( G );
    gens := SpecialPcgs( G ){[1..r]};
    imgs := Pcgs( A.group ){[1..r]};
    if not CHECK then 
        iso := GroupHomomorphismByImagesNC( G, A.group, gens, imgs );
        SetIsBijective( iso, true );
    else
        iso := GroupHomomorphismByImages( G, A.group, gens, imgs );
        if not IsGroupHomomorphism( iso ) then
            Error("isomorphism is no homomorphism");
        elif not IsBijective( iso ) then
            Error("isomorphism is not bijective");
        fi;
    fi;

    C := rec();
    C.glAutos := List( A.glAutos, x -> ConvertAuto( x, iso ) );
    C.glOrder := A.glOrder;
    C.agAutos := List( A.agAutos, x -> ConvertAuto( x, iso ) );
    C.agOrder := A.agOrder;
    C.one := IdentityMapping( G );
    C.group := G;
    C.size := A.size;

    # if possible add projective operation
    if IsBound( A.glOper ) then C.glOper := A.glOper; fi;

    return C;
  end);

#############################################################################
##
#F AddInfoCover( Q, P, M, U )
##
InstallGlobalFunction( AddInfoCover, 
  function( Q, P, M, U )
    local r, p, f, fam, gensP, pcgsP, gensM, pcgsM, gensU, pcgsU, pos, def; 
  
    r := Q!.RanksOfDescendingSeries;
    p := Q!.prime;
    f := Q!.field;
    fam := FamilyPcgs( P );

    # info for P
    gensP := GeneratorsOfGroup( P );
    pcgsP := InducedPcgsByPcSequenceNC( fam, gensP );
    SetPcgs( P, pcgsP );
    SetRankPGroup( P, r[1] );
    SetPrimePGroup( P, p );

    # info for M
    gensM := GeneratorsOfGroup( M );
    pcgsM := InducedPcgsByPcSequenceNC( fam, gensM );
    SetPcgs( M, pcgsM );
    SetPrimePGroup( M, p );
    M!.field := f;

    # info for U
    gensU := GeneratorsOfGroup( U );
    pcgsU := InducedPcgsByGeneratorsNC( fam, gensU );
    SetPcgs( U, pcgsU );
    
    # get definitions of M
    pos := List( pcgsP, x -> Position( fam, x ) );
    def := Q!.definitions{pos};
    P!.definitions := RewriteDef( pcgsP, def, p );
  end);

#############################################################################
##
#F AutomorphismGroupPGroup( <G>, <flag> ) . . . .automorphisms in hybird form
##
InstallGlobalFunction( AutomorphismGroupPGroup, function( arg )
    local p, r, G, pcgs, first, n, str, A, F, Q, i, s, t, P, N, M, U, B,
          baseU, baseN, epi, f;

    # catch the trivial case
    G := arg[1];
    p := PrimePGroup( G );
    r := RankPGroup( G );
    if Size( G ) = 1 then return Group( [], IdentityMapping(G) ); fi;

    # choose a initialisation
    if Length( arg ) = 1 then

        if IsHomoCyclic( G ) then
            InitAutGroup := InitAutomorphismGroupFull;
        elif (p^r - 1)/(p - 1) < 30000 then
            InitAutGroup := InitAutomorphismGroupOver;
        else   
            InitAutGroup := InitAutomorphismGroupChar;
        fi;

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

        if arg[2] = "Full" then 
            InitAutGroup := InitAutomorphismGroupFull;
        elif arg[2] = "Over" then 
            InitAutGroup := InitAutomorphismGroupOver;
        elif arg[2] = "Char" then 
            InitAutGroup := InitAutomorphismGroupChar;
        else
            Error("invalid initialisation");
        fi;

    elif Length( arg ) = 2 and IsBool(arg[2]) and arg[2] = true then
        str := Interrupt("choose initialisation (Over/Char/Full)");
        if str = "Over" then
            InitAutGroup := InitAutomorphismGroupOver;
        elif str = "Char" then
            InitAutGroup := InitAutomorphismGroupChar;
        elif str = "Full" then
            InitAutGroup := InitAutomorphismGroupFull;
        else
            Error("invalid initialisation");
        fi;

    fi;

    # choose flags
    CHOP_MULT := true;
    NICE_STAB := true; 
    USE_LABEL := false;

    # compute special pcgs 
    pcgs := SpecialPcgs( G );
    first := LGFirst( SpecialPcgs(G) );
    p := PrimePGroup( G );
    n := Length(pcgs);
    r := RankPGroup( G );
    f := GF(p);
    
    # init automorphism group - compute Aut(G/G_1)
    Info( InfoAutGrp, 1, 
          "step 1: ",p,"^", first[2]-1, " -- init automorphisms ");

    A := InitAutGroup( G );

    # loop over remaining steps
    F := Range( IsomorphismFpGroupByPcgs( pcgs, "f" ) );
    Q := PQuotient( F, p, 1 );
    for i in [2..Length(first)-1] do

        # print info
        s := first[i];
        t := first[i+1];
        Info( InfoAutGrp, 1, 
              "step ",i,": ",p,"^", t-s, " -- aut grp has size ", A.size );

        # the cover
        Info( InfoAutGrp, 2, "  computing cover");
        P := PCover( Q );
        M := PMultiplicator( Q, P );
        N := Nucleus( Q, P );
        U := AllowableSubgroup( Q, P );
        AddInfoCover( Q, P, M, U );

        # induced action of A on M
        Info( InfoAutGrp, 2, "  computing matrix action");
        LinearActionAutGrp( A, P, M );

        # compute stabilizer
        Info( InfoAutGrp, 2, "  computing stabilizer of U");
        baseN := GeneratorsOfGroup(N);
        baseU := GeneratorsOfGroup(U);
        baseN := List(baseN, x -> ExponentsOfPcElement(Pcgs(M), x)) * One(f);
        baseU := List(baseU, x -> ExponentsOfPcElement(Pcgs(M), x)) * One(f);
        baseU := EcheloniseMat( baseU );
        PGOrbitStabilizer( A, baseU, baseN, false );

        # next step of p-quotient
        IncorporateCentralRelations( Q );
        RenumberHighestWeightGenerators( Q );

        # induce to next factor
        Info( InfoAutGrp, 2, "  induce autos and add central autos");
        A := InduceAutGroup( A, Q, P, M, U );
    od;

    # now get a real automorphism group
    Info( InfoAutGrp, 1, "final step: convert");
    return ConvertAutGroup( A, G );
end );

#############################################################################
##
#M ConvertHybridAutGroup( <A> )
##
InstallGlobalFunction( ConvertHybridAutGroup, function( A )
    local B, pcgs;
    B := Group( Concatenation( A.glAutos, A.agAutos ), A.one );
    SetSize( B, A.glOrder * Product( A.agOrder ) );
    if Length( A.glAutos ) = 0 then 
        SetIsSolvableGroup( B, true ); 
        pcgs := PcgsByPcSequenceNC( FamilyObj( A.one ), A.agAutos );
        SetRelativeOrders( pcgs, A.agOrder );
        SetOneOfPcgs( pcgs, A.one );
        SetGeneralizedPcgs( B, pcgs );
    fi;
    SetIsGroupOfAutomorphisms( B, true );
    return B;
end );

#############################################################################
##
#M AutomorphismGroup
##
InstallMethod( AutomorphismGroup,
               "for finite p-groups",
               true,
               [IsPGroup and IsFinite and CanEasilyComputePcgs],
               0,
function( G )
    local A;

    # the trivial case is a problem
    if Size( G ) = 1 then return Group( [], IdentityMapping(G) ); fi;

    # compute
    A :=  AutomorphismGroupPGroup( G );

    # translate and return
    A:=ConvertHybridAutGroup( A );
    SetIsAutomorphismGroup(A,true);
    if IsFinite(G) then
      SetIsGroupOfAutomorphismsFiniteGroup(A,true);
    fi;
    return A;
end );

               
