#############################################################################
##
#W  pcpelms.gi                   Polycyc                         Bettina Eick
##

InstallGlobalFunction( PcpElementConstruction,
function( coll, list, word )
    local   elm;
    elm := rec( collector := coll,
                exponents := Immutable(list),
                word      := Immutable(word),
                name      := "g" );

    # objectify and return
    return Objectify( coll![PC_PCP_ELEMENTS_TYPE], elm );
end );

#############################################################################
##
## Functions to create pcp elements by exponent vectors or words.
## In the NC versions we assume that elements are in normal form.
## In the other versions we collect before we return an element.
##
InstallGlobalFunction( PcpElementByExponentsNC,
function( coll, list )
    local   i,  word;
    word := ObjByExponents( coll, list );
    return PcpElementConstruction( coll, list, word );
end );

InstallGlobalFunction( PcpElementByExponents, function( coll, list )
    local h, k;

    if Length(list) > NumberOfGenerators(coll) then
        Error( "more exponents than generators" );
    fi;

    h := ObjByExponents( coll, list );
    k := list * 0;
    while CollectWordOrFail( coll, k, h ) = fail do od;
    return PcpElementByExponentsNC( coll, k );
end );

InstallGlobalFunction( PcpElementByGenExpListNC,
function( coll, word )
    local   list,  i;
    list := ExponentsByObj( coll, word );
    word := ObjByExponents( coll, list );
    return PcpElementConstruction( coll, list, word );
end );

InstallGlobalFunction( PcpElementByGenExpList, function( coll, word )
    local k;
    k := [1..coll![PC_NUMBER_OF_GENERATORS]] * 0;
    while CollectWordOrFail( coll, k, word ) = fail do od;
    return PcpElementByExponentsNC( coll, k );
end );

#############################################################################
##
#A Basic attributes of pcp elements - for IsPcpElementRep
##
InstallMethod( Collector,
        "for pcp groups",
        [ IsPcpGroup ],
        G -> Collector( One(G) ) );


InstallMethod( Collector,
        "for pcp elements",
        [ IsPcpElementRep ],
        g -> g!.collector );

InstallMethod( Exponents,
        "for pcp elements",
        [ IsPcpElementRep ],
        g -> g!.exponents );

InstallMethod( NameTag,
        "for pcp elements",
        [ IsPcpElementRep ],
        g -> g!.name );

InstallMethod( GenExpList,
        "for pcp elements",
        [ IsPcpElementRep ],
        g -> g!.word );

InstallMethod( Depth,
        "for pcp elements",
        [ IsPcpElementRep ],
function( elm )
    if Length(elm!.word) = 0 then
        return elm!.collector![PC_NUMBER_OF_GENERATORS] + 1;
    else
        return elm!.word[1];
    fi;
end );

InstallMethod( TailOfElm,
        "for pcp elements",
        [ IsPcpElement and IsPcpElementRep ],
function( elm )
    if Length( elm!.word ) = 0 then
        return 0;
    else
        return elm!.word[ Length(elm!.word) - 1 ];
    fi;
end );

InstallMethod( LeadingExponent,
        "for pcp elements",
        [ IsPcpElementRep ],
function( elm )
    if Length(elm!.word) = 0 then
        return fail;
    else
        return elm!.word[2];
    fi;
end );

##  Note, that inverses of generators with relative order > 0 are not treated
##  as inverses as they should never appear here with a negative exponent.
IsGeneratorOrInverse := function( elm )
    return Length(elm!.word) = 2 and
           (elm!.word[2] = 1 or elm!.word[2] = -1);
end;

##
## Is elm the power of a generator modulo depth d?
## If so, then return the power, otherwise return fail;
##
IsPowerOfGenerator := function( elm, d )
    if Length( elm!.word ) = 0 or
       (Length( elm!.word ) > 2 and elm!.word[3] <= d) then
        return fail;
    fi;
    return elm!.word[2];
end;

#############################################################################
##
#F FactorOrder( g )
##
InstallMethod( FactorOrder, [IsPcpElement],
function( g )
    if Length( g!.word ) = 0 then return fail; fi;
    return RelativeOrders( Collector(g) )[Depth(g)];
end );

#############################################################################
##
#F RelativeOrderPcp( g )
##
InstallMethod( RelativeOrderPcp, [IsPcpElement],
function( g )
    local r, l;
    if Length( g!.word ) = 0 then return fail; fi;
    r := FactorOrder( g );

    # the infinite case
    if r = 0 then return 0; fi;

    # the finite case
    l := LeadingExponent( g );
    if l = 1 then
       return r;
    elif IsBound( g!.normed ) and g!.normed then
        return r / LeadingExponent(g);
    elif IsPrime( r ) then
        return r;
    else
        return r / Gcd( r, l );
    fi;
end );
# TODO: Replace this by something like DeclareSynonymAttr.
# However, we cannot use DeclareSynonymAttr directly, because for
# collectors there is already an operation SetRelativeOrder.
RelativeOrder := function( g ) return RelativeOrderPcp(g); end;

#############################################################################
##
#F RelativeIndex( g )
##
InstallMethod( RelativeIndex, [IsPcpElement],
function( g )
    local r, l;
    if Length( g!.word ) = 0 then return fail; fi;
    r := FactorOrder( g );
    l := LeadingExponent( g );

    if IsBound( g!.normed ) and g!.normed then
        return l;
    elif r > 0 then
        return Gcd( r, l );
    else
        return AbsInt( l );
    fi;
end );

#############################################################################
##
#F Order( g )
##
InstallMethod( Order, [IsPcpElement],
function( g )
    local o, r;
    o := 1;
    while g <> g^0 do
        r := RelativeOrderPcp( g );
        if r = 0 then return infinity; fi;
        o := o*r;
        g := g^r;
    od;
    return o;
end );

#############################################################################
##
#F NormingExponent( g ) . . . . . . . . .returns f such that g^f is normed
##
## Note that g is normed, if the LeadingExponent of g is its RelativeIndex.
##
# FIXME: This function is documented and should be turned into a GlobalFunction
NormingExponent := function( g )
    local r, l, e;
    r := FactorOrder( g );
    l := LeadingExponent( g );
    if IsBool( l ) then
        return 1;
    elif r = 0 and l < 0 then
        return -1;
    elif r = 0 then
        return 1;
    elif IsPrime( r ) then
        return l^-1 mod r;
    else
        e := Gcdex( r, l );     # = RelativeIndex
        return e.coeff2 mod r;  # l * c2 = e mod r
    fi;
end;

#############################################################################
##
#F NormedPcpElement( g )
##
# FIXME: This function is documented and should be turned into a GlobalFunction
NormedPcpElement := function( g )
    local h;
    h := g^NormingExponent( g );
    h!.normed := true;
    return h;
end;

#############################################################################
##
#M Print pcp elements
##
InstallMethod( PrintObj,
               "for pcp elements",
               [IsPcpElement],
function( elm )
    local g, l, e, d;
    g := NameTag( elm );
    e := Exponents( elm );
    d := Depth( elm );
    if d > Length( e ) then
        Print("id");
    elif e[d] = 1 then
        Print(Concatenation(g,String(d)));
    else
        Print(Concatenation(g,String(d)),"^",e[d]);
    fi;
    for l in [d+1..Length(e)] do
        if e[l] = 1 then
            Print("*",Concatenation(g,String(l)));
        elif e[l] <> 0 then
            Print("*",Concatenation(g,String(l)),"^",e[l]);
        fi;
    od;
end );

InstallMethod( String,
               "for pcp elements",
               [IsPcpElement],
function( elm )
    local g, l, e, d, str;
    g := NameTag( elm );
    e := Exponents( elm );
    d := Depth( elm );
    if d > Length( e ) then
        return "id";
    fi;
	str := Concatenation(g,String(d));
    if e[d] <> 1 then
        Append(str, Concatenation("^",String(e[d])));
    fi;
    for l in [d+1..Length(e)] do
    	if e[l] = 0 then continue; fi;
        Append(str, Concatenation("*",g,String(l)));
		if e[l] <> 1 then
			Append(str, Concatenation("^",String(e[l])));
		fi;
    od;
    return str;
end );

#############################################################################
##
#M g * h
##
InstallMethod( \*,
               "for pcp elements",
               IsIdenticalObj,
               [IsPcpElement, IsPcpElement],
               20,
function( g1, g2 )
    local clt, e, f;

    clt := Collector( g1 );
    if TailOfElm( g1 ) < Depth( g2 ) then
        e := Exponents( g1 ) + Exponents( g2 );

    else
        e  := ShallowCopy( Exponents( g1 ) );
        f  := GenExpList( g2 );
        while CollectWordOrFail( clt, e, f ) = fail do
            e  := ShallowCopy( Exponents( g1 ) );
        od;
    fi;

    return PcpElementByExponentsNC( clt, e );
end );

#############################################################################
##
#M Inverse
##
InstallMethod( Inverse,
               "for pcp elements",
               [IsPcpElement],
function( g )
    local   clt,  k;

    clt := Collector( g );
    if IsGeneratorOrInverse( g ) and RelativeOrderPcp(g) = 0 then
        if LeadingExponent( g ) = 1 then
            k := clt![PC_INVERSES][ Depth(g) ];
        else
            k := clt![PC_GENERATORS][ Depth(g) ];
        fi;

    else

        k := FromTheLeftCollector_Inverse( clt, GenExpList(g) );
    fi;

    return PcpElementByGenExpListNC( clt, k );
end );

InstallMethod( INV,
               "for pcp elements",
               [IsPcpElement],
function( g )
    local   clt,  k;

    clt := Collector( g );
    if IsGeneratorOrInverse( g ) and RelativeOrderPcp(g) = 0 then
        if LeadingExponent( g ) = 1 then
            k := clt![PC_INVERSES][ Depth(g) ];
        else
            k := clt![PC_GENERATORS][ Depth(g) ];
        fi;

    else

        k := FromTheLeftCollector_Inverse( clt, GenExpList(g) );
    fi;

    return PcpElementByGenExpListNC( clt, k );
end );

#############################################################################
##
#M \^
##
InstallMethod( \^,
               "for a pcp element and an integer",
               [IsPcpElement, IsInt],
               SUM_FLAGS + 10,
function( g, d )
    local   res;

    # first catch the trivial cases
    if d = 0 then
        return PcpElementByExponentsNC( Collector(g), 0*Exponents(g) );
    elif d = 1 then
        return g;
    elif d = -1 then
        return Inverse(g);
    fi;

#    # use collector function
#    c := Collector(g);
#    k := FromTheLeftCollector_Power(c, ObjByExponents(c, Exponents(g)), d);
#    return PcpElementByGenExpListNC( c, k );

    # set up for computation
    if d < 0 then
        g := Inverse(g);
        d := -d;
    fi;

    # compute power
    res := g^0;
    while d > 0 do
        if d mod 2 = 1 then   res := res * g;   fi;

        d := QuoInt( d, 2 );
        if d <> 0 then   g := g * g;    fi;
    od;

    return res;
end );

InstallMethod( \^,
               "for two pcp elements",
               IsIdenticalObj,
               [IsPcpElement, IsPcpElement],
function( h, g )
    local   clt,  conj;

    clt := Collector( g );
    if IsGeneratorOrInverse( h ) and IsGeneratorOrInverse( g ) then

        if Depth( g ) = Depth( h ) then

            conj := h;

        elif Depth( g ) < Depth( h ) then

            conj := GetConjugateNC( clt,
                            Depth( h ) * LeadingExponent( h ),
                            Depth( g ) * LeadingExponent( g ) );

            conj := PcpElementByGenExpListNC( clt, conj );

        elif Depth( g ) > Depth( h ) then
            #  h^g = g^-1 * h * g

            conj := ShallowCopy( Exponents( g^-1 ) );
            while CollectWordOrFail( clt, conj,
                    [ Depth(h), LeadingExponent( h ),
                      Depth(g), LeadingExponent( g ) ] ) = fail do

                conj := ShallowCopy( Exponents( g^-1 ) );
            od;

            conj := PcpElementByExponentsNC( clt, conj );
        fi;

    elif Depth(g) = TailOfElm(g) and Depth( g ) < Depth( h ) then

        ##
        ## nicht klar ob dies etwas bringt
        ##
        g := [ Depth(g), LeadingExponent(g) ];
        conj := ShallowCopy( Exponents( h ) );
        while CollectWordOrFail( clt, conj, g ) = fail do
            conj := ShallowCopy( Exponents( h ) );
        od;

        conj[ g[1] ] := 0;
        conj := PcpElementByExponentsNC( clt, conj );

    else
        conj := g^-1 * h * g;
    fi;

    return conj;

end );


InstallMethod( GetCommutatorNC,
        "for from the left collector",
        [ IsFromTheLeftCollectorRep, IsInt, IsInt ],
function( coll, h, g )

    if g > 0 then
        if h > 0 then
            if IsBound( coll![PC_COMMUTATORS][h] ) and
               IsBound( coll![PC_COMMUTATORS][h][g] ) then
                return coll![PC_COMMUTATORS][h][g];
            else
                return fail;
            fi;
        else
            h := -h;
            if IsBound( coll![PC_INVERSECOMMUTATORS][h] ) and
               IsBound( coll![PC_INVERSECOMMUTATORS][h][g] ) then
                return coll![PC_INVERSECOMMUTATORS][h][g];
            else
                return fail;
            fi;
        fi;
    else
        g := -g;
        if h > 0 then
            if IsBound( coll![PC_COMMUTATORSINVERSE][h] ) and
               IsBound( coll![PC_COMMUTATORSINVERSE][h][g] ) then
                return coll![PC_COMMUTATORSINVERSE][h][g];
            else
                return fail;
            fi;
        else
            h := -h;
            if IsBound( coll![PC_INVERSECOMMUTATORSINVERSE][h] ) and
               IsBound( coll![PC_INVERSECOMMUTATORSINVERSE][h][g] ) then
                return coll![PC_INVERSECOMMUTATORSINVERSE][h][g];
            else
                return fail;
            fi;
        fi;

    fi;
end );

#############################################################################
##
#M Comm
##
InstallMethod( Comm,
               "for two pcp elements",
               [ IsPcpElement, IsPcpElement ],
function( h, g )
    local   clt,  conj,  ev;

    clt := Collector( g );

    if IsGeneratorOrInverse( h ) and IsGeneratorOrInverse( g ) then

        if Depth( g ) = Depth( h ) then return g^0; fi;


        if Depth( g ) < Depth( h ) then

            ##  Do we know the commutator?

            conj := GetCommutatorNC( clt, Depth( h ) * LeadingExponent( h ),
                                          Depth( g ) * LeadingExponent( g ) );
            if conj  <> fail then
                return conj;
            fi;

            ##  [h,g] = h^-1 h^g

            conj := GetConjugateNC( clt, Depth( h ) * LeadingExponent( h ),
                                         Depth( g ) * LeadingExponent( g ) );

            ev := ShallowCopy( Exponents( h^-1 ) );
            while CollectWordOrFail( clt, ev, conj ) = fail do
                ev := ShallowCopy( Exponents( h^-1 ) );
            od;

            return PcpElementByExponentsNC( clt, ev );
        fi;

        if Depth( g ) > Depth( h ) and RelativeOrderPcp( g ) = 0 then
            ##  [h,g] = (g^-1)^h * g

            conj := GetConjugateNC( clt, Depth( g ) *  -LeadingExponent( g ),
                                         Depth( h ) *   LeadingExponent( h ) );

            ev := ExponentsByObj( clt, conj );
            while CollectWordOrFail( clt, ev, GenExpList(g) ) = fail do
                ev := ExponentsByObj( clt, conj );
            od;

            return PcpElementByExponentsNC( clt, ev );
        fi;

    fi;

    return PcpElementByGenExpListNC( clt,
                   FromTheLeftCollector_Solution( clt,
                   GenExpList(g*h),GenExpList(h*g) ) );

end );



#############################################################################
##
#M One
##
InstallMethod( One, "for pcp elements", [IsPcpElement],
g -> PcpElementByExponentsNC( Collector(g), 0*Exponents(g) ) );

#############################################################################
##
#M \=
##
InstallMethod( \=,
               "for pcp elements",
               IsIdenticalObj,
               [IsPcpElement, IsPcpElement],
function( g, h )
    return Exponents( g ) = Exponents( h );
end );

#############################################################################
##
#M \<
##
InstallMethod( \<,
               "for pcp elements",
               IsIdenticalObj,
               [IsPcpElement, IsPcpElement],
function( g, h )
    return Exponents( g ) > Exponents( h );
end );

