# sage_setup: distribution = sagemath-groups
r"""
Semidirect product of groups

AUTHORS:

- Mark Shimozono (2013) initial version
"""
# ****************************************************************************
#       Copyright (C) 2013 Mark Shimozono <mshimo at math.vt.edu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#                  https://www.gnu.org/licenses/
# ****************************************************************************
from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups
from sage.categories.groups import Groups
from sage.sets.cartesian_product import CartesianProduct
from sage.misc.cachefunc import cached_method


class GroupSemidirectProductElement(CartesianProduct.Element):
    r"""
    Element class for :class:`GroupSemidirectProduct`.
    """

    def _repr_(self):
        r"""
        A string representing the semidirect product group.

        EXAMPLES::

            sage: def twist(x, y):
            ....:     return y
            sage: GroupSemidirectProduct(WeylGroup(['A',2],prefix='s'),         # indirect doctest
            ....:                        WeylGroup(['A',3],prefix='t'), twist)
            Semidirect product of Weyl Group of type ['A', 2]
            (as a matrix group acting on the ambient space) acting on
            Weyl Group of type ['A', 3]
            (as a matrix group acting on the ambient space)
        """
        def wrapper(prefix, s):
            if prefix is None:
                return s
            return "%s[%s]" % (prefix, s)

        par = self.parent()

        g = self.cartesian_projection(0)
        h = self.cartesian_projection(1)
        gstr = wrapper(par._prefix0, repr(g))
        hstr = wrapper(par._prefix1, repr(h))
        if par._print_tuple:
            return "(%s, %s)" % (gstr, hstr)

        if self == par.one():
            return "1"
        if g == g.parent().one():
            return hstr
        if h == h.parent().one():
            return gstr
        return gstr + " * " + hstr

    def __invert__(self):
        r"""
        Return the inverse of ``self``.

        EXAMPLES::

            sage: # needs sage.rings.number_field
            sage: L = RootSystem(['A',2]).root_lattice()
            sage: from sage.groups.group_exp import GroupExp
            sage: EL = GroupExp()(L)
            sage: W = L.weyl_group(prefix='s')
            sage: def twist(w, v):
            ....:     return EL(w.action(v.value))
            sage: G = GroupSemidirectProduct(W, EL, twist, prefix1='t')
            sage: g = G.an_element(); g
            s1*s2 * t[2*alpha[1] + 2*alpha[2]]
            sage: g.inverse()
            s2*s1 * t[2*alpha[1]]
        """
        par = self.parent()
        g = self.cartesian_projection(0)
        h = self.cartesian_projection(1)

        if par.act_to_right():
            return self.__class__(par, (~g, par._twist(g, ~h)))
        else:
            hi = ~h
            return self.__class__(par, (par._twist(hi, ~g), hi))

    def to_opposite(self):
        r"""
        Send an element to its image in the opposite semidirect product.

        EXAMPLES::

            sage: # needs sage.rings.number_field
            sage: L = RootSystem(['A',2]).root_lattice(); L
            Root lattice of the Root system of type ['A', 2]
            sage: from sage.groups.group_exp import GroupExp
            sage: EL = GroupExp()(L)
            sage: W = L.weyl_group(prefix='s'); W
            Weyl Group of type ['A', 2]
            (as a matrix group acting on the root lattice)
            sage: def twist(w, v):
            ....:     return EL(w.action(v.value))
            sage: G = GroupSemidirectProduct(W, EL, twist, prefix1='t'); G
            Semidirect product of Weyl Group of type ['A', 2] (as a matrix
            group acting on the root lattice) acting on Multiplicative form of
            Root lattice of the Root system of type ['A', 2]
            sage: mu = L.an_element(); mu
            2*alpha[1] + 2*alpha[2]
            sage: w = W.an_element(); w
            s1*s2
            sage: g = G((w,EL(mu))); g
            s1*s2 * t[2*alpha[1] + 2*alpha[2]]
            sage: g.to_opposite()
            t[-2*alpha[1]] * s1*s2
            sage: g.to_opposite().parent()
            Semidirect product of
             Multiplicative form of Root lattice of the Root system of type ['A', 2]
             acted upon by Weyl Group of type ['A', 2]
              (as a matrix group acting on the root lattice)
        """
        par = self.parent()
        Gop = par.opposite_semidirect_product()
        g = self.cartesian_projection(0)
        h = self.cartesian_projection(1)
        if par.act_to_right():
            return Gop((par._twist(g, h), g))
        return Gop((h, par._twist(~h, g)))


class GroupSemidirectProduct(CartesianProduct):
    r"""
    Return the semidirect product of the groups ``G`` and ``H`` using the
    homomorphism ``twist``.

    INPUT:

    - ``G``, ``H`` -- multiplicative groups
    - ``twist`` -- (default: ``None``) a function defining
      a homomorphism (see below)
    - ``act_to_right`` -- boolean (default: ``True``)
    - ``prefix0`` -- string (default: ``None``)
    - ``prefix1`` -- string (default: ``None``)
    - ``print_tuple`` -- boolean (default: ``False``)
    - ``category`` -- a category (default: ``Groups()``)

    A semidirect product of groups `G` and `H` is a group structure on
    the Cartesian product `G \times H` whose product agrees with that
    of `G` on `G \times 1_H` and with that of `H` on `1_G \times H`,
    such that either `1_G \times H` or `G \times 1_H` is a normal
    subgroup. In the former case, the group is denoted `G \ltimes H`
    and in the latter, `G \rtimes H`.

    If ``act_to_right`` is ``True``, this indicates the group `G \ltimes H`
    in which `G` acts on `H` by automorphisms. In this case there is a
    group homomorphism `\phi \in \mathrm{Hom}(G, \mathrm{Aut}(H))` such that

    .. MATH::

        g h g^{-1} = \phi(g)(h).

    The homomorphism `\phi` is specified by the input ``twist``, which
    syntactically is the function `G\times H\to H` defined by

    .. MATH::

        twist(g,h) = \phi(g)(h).

    The product on `G \ltimes H` is defined by

    .. MATH::

        \begin{aligned}
        (g_1,h_1)(g_2,h_2) &= g_1 h_1 g_2 h_2 \\
        &= g_1 g_2 g_2^{-1} h_1 g_2 h_2 \\
        &= (g_1g_2, twist(g_2^{-1}, h_1) h_2)
        \end{aligned}

    If ``act_to_right`` is ``False``, the group `G \rtimes H` is specified by
    a homomorphism `\psi\in \mathrm{Hom}(H,\mathrm{Aut}(G))` such that

    .. MATH::

        h g h^{-1} = \psi(h)(g)

    Then ``twist`` is the function `H\times G\to G` defined by

    .. MATH::

       twist(h,g) = \psi(h)(g).

    so that the product in `G \rtimes H` is defined by

    .. MATH::

        \begin{aligned}
        (g_1,h_1)(g_2,h_2) &= g_1 h_1 g_2 h_2 \\
        &= g_1 h_1 g_2 h_1^{-1} h_1 h_2 \\
        &= (g_1 twist(h_1,g_2), h_1 h_2)
        \end{aligned}

    If ``prefix0`` (resp. ``prefixl``) is not ``None`` then it is used
    as a wrapper for printing elements of ``G`` (resp. ``H``). If
    ``print_tuple`` is ``True`` then elements are printed in the style
    `(g,h)` and otherwise in the style `g * h`.

    EXAMPLES::

        sage: G = GL(2,QQ)
        sage: V = QQ^2
        sage: EV = GroupExp()(V)  # make a multiplicative version of V
        sage: def twist(g, v):
        ....:     return EV(g*v.value)
        sage: H = GroupSemidirectProduct(G, EV, twist=twist, prefix1='t'); H
        Semidirect product of General Linear Group of degree 2
        over Rational Field acting on Multiplicative form of Vector space
        of dimension 2 over Rational Field
        sage: x = H.an_element(); x
        t[(1, 0)]
        sage: x^2
        t[(2, 0)]

        sage: # needs sage.rings.number_field
        sage: cartan_type = CartanType(['A',2])
        sage: W = WeylGroup(cartan_type, prefix='s')
        sage: def twist(w, v):
        ....:     return w*v*(~w)
        sage: WW = GroupSemidirectProduct(W, W, twist=twist, print_tuple=True)
        sage: s = Family(cartan_type.index_set(), lambda i: W.simple_reflection(i))
        sage: y = WW((s[1],s[2])); y
        (s1, s2)
        sage: y^2
        (1, s2*s1)
        sage: y.inverse()
        (s1, s1*s2*s1)

    .. TODO::

        - Functorial constructor for semidirect products for various categories
        - Twofold Direct product as a special case of semidirect product
    """

    def __init__(self, G, H, twist=None, act_to_right=True, prefix0=None,
                 prefix1=None, print_tuple=False, category=Groups()):
        r"""

        EXAMPLES::

            sage: def twist(x, y):
            ....:     return y
            sage: import __main__
            sage: __main__.twist = twist
            sage: G = GroupSemidirectProduct(WeylGroup(['A',2],prefix='s'),
            ....:                            WeylGroup(['A',3],prefix='t'), twist)
            sage: TestSuite(G).run()

        The ``__main__`` business is a trick to pass the pickling test.
        """

        self._act_to_right = act_to_right

        def check_implemented_group(x):
            if x in Groups():
                return
            error = ("The semidirect product construction for groups "
                     "is implemented only for multiplicative groups")
            if x in CommutativeAdditiveGroups():
                error += (f". Please change the commutative additive group {x}"
                          " into a multiplicative group "
                          "using the functor sage.groups.group_exp.GroupExp")
            raise TypeError(error)

        check_implemented_group(G)
        check_implemented_group(H)

        if twist is None:
            self._twist = lambda g, h: h  # use the trivial twist
        else:
            self._twist = twist

        self._prefix0 = prefix0
        self._prefix1 = prefix1
        self._print_tuple = print_tuple
        self._category = category
        CartesianProduct.__init__(self, (G, H), category=category)

    def act_to_right(self):
        r"""
        Return ``True`` if the left factor acts on the right factor and
        ``False`` if the right factor acts on the left factor.

        EXAMPLES::

            sage: def twist(x, y):
            ....:     return y
            sage: GroupSemidirectProduct(WeylGroup(['A',2],prefix='s'),
            ....:                        WeylGroup(['A',3],prefix='t'), twist).act_to_right()
            True
        """
        return self._act_to_right

    def _repr_(self):
        r"""
        A string representing the semidirect product group.

        EXAMPLES::

            sage: def twist(x, y):
            ....:     return y
            sage: GroupSemidirectProduct(WeylGroup(['A',2],prefix='s'),         # indirect doctest
            ....:                        WeylGroup(['A',3],prefix='t'), twist)
            Semidirect product of Weyl Group of type ['A', 2] (as a matrix
            group acting on the ambient space) acting on Weyl Group
            of type ['A', 3] (as a matrix group acting on the ambient space)
        """
        cartesian_factors = self.cartesian_factors()
        if self.act_to_right():
            act_string = "acting on"
        else:
            act_string = "acted upon by"
        return "Semidirect product of %s %s %s" % (cartesian_factors[0],
                                                   act_string,
                                                   cartesian_factors[1])

    def _element_constructor_(self, x):
        r"""
        EXAMPLES::

            sage: def twist(x, y):
            ....:     return y
            sage: import __main__
            sage: __main__.twist = twist
            sage: g = GroupSemidirectProduct(WeylGroup(['A',2],prefix='s'),
            ....:                            WeylGroup(['A',3],prefix='t'), twist).an_element()
            sage: TestSuite(g).run()
        """
        def type_error():
            raise TypeError(f"{x} cannot be converted into an element of {self}")

        if isinstance(x, self.element_class) and x.parent() == self:
            return x
        if not isinstance(x, GroupSemidirectProductElement):
            if not isinstance(x, tuple):
                type_error()
            if len(x) != 2:
                type_error()
            g, h = x
        else:
            g = x.cartesian_projection(0)
            h = x.cartesian_projection(1)
        gg = self.cartesian_factors()[0](g)
        hh = self.cartesian_factors()[1](h)
        return self.element_class(self, (gg, hh))

    @cached_method
    def one(self):
        r"""
        The identity element of the semidirect product group.

        EXAMPLES::

            sage: G = GL(2,QQ)
            sage: V = QQ^2
            sage: EV = GroupExp()(V)  # make a multiplicative version of V
            sage: def twist(g, v):
            ....:     return EV(g*v.value)
            sage: one = GroupSemidirectProduct(G, EV, twist=twist, prefix1='t').one(); one
            1
            sage: one.cartesian_projection(0)
            [1 0]
            [0 1]
            sage: one.cartesian_projection(1)
            (0, 0)
        """
        return self((self.cartesian_factors()[0].one(),
                     self.cartesian_factors()[1].one()))

    def group_generators(self):
        r"""
        Return generators of ``self``.

        EXAMPLES::

            sage: twist = lambda x,y: y
            sage: import __main__
            sage: __main__.twist = twist
            sage: EZ = GroupExp()(ZZ)
            sage: GroupSemidirectProduct(EZ, EZ, twist, print_tuple=True).group_generators()
            ((1, 0), (0, 1))
        """
        def has_gens(G):
            if not hasattr(G, 'group_generators'):
                return False
            return G.group_generators()

        factors = self.cartesian_factors()
        g0 = has_gens(factors[0])
        if g0 is not False:
            g1 = has_gens(factors[1])
            if g1 is not False:
                return tuple([self((x, factors[1].one())) for x in g0] +
                             [self((factors[0].one(), x)) for x in g1])
        raise NotImplementedError("one of the factors does not "
                                  "implement 'group_generators'")

    def product(self, x, y):
        r"""
        The product of elements `x` and `y` in the semidirect product group.

        EXAMPLES::

            sage: G = GL(2,QQ)
            sage: V = QQ^2
            sage: EV = GroupExp()(V)  # make a multiplicative version of V
            sage: def twist(g, v):
            ....:     return EV(g*v.value)
            sage: S = GroupSemidirectProduct(G, EV, twist=twist, prefix1='t')
            sage: g = G([[2,1],[3,1]]); g
            [2 1]
            [3 1]
            sage: v = EV.an_element(); v
            (1, 0)
            sage: x = S((g,v)); x
            [2 1]
            [3 1] * t[(1, 0)]
            sage: x*x # indirect doctest
            [7 3]
            [9 4] * t[(0, 3)]
        """
        xg = x.cartesian_projection(0)
        xh = x.cartesian_projection(1)
        yg = y.cartesian_projection(0)
        yh = y.cartesian_projection(1)
        if self.act_to_right():
            g = xg * yg
            h = self._twist(~yg, xh) * yh
        else:
            h = xh * yh
            g = xg * self._twist(xh, yg)
        return self((g, h))

    @cached_method
    def opposite_semidirect_product(self):
        r"""
        Create the same semidirect product but with the positions of the groups exchanged.

        EXAMPLES::

            sage: G = GL(2,QQ)
            sage: L = QQ^2
            sage: EL = GroupExp()(L)
            sage: H = GroupSemidirectProduct(G, EL, prefix1='t',
            ....:                            twist=lambda g,v: EL(g*v.value)); H
            Semidirect product of General Linear Group of degree 2
            over Rational Field acting on Multiplicative form of Vector space
            of dimension 2 over Rational Field
            sage: h = H((Matrix([[0,1],[1,0]]), EL.an_element())); h
            [0 1]
            [1 0] * t[(1, 0)]
            sage: Hop = H.opposite_semidirect_product(); Hop
            Semidirect product of Multiplicative form of Vector space
            of dimension 2 over Rational Field acted upon by
            General Linear Group of degree 2 over Rational Field
            sage: hop = h.to_opposite(); hop
            t[(0, 1)] * [0 1]
            [1 0]
            sage: hop in Hop
            True
        """
        return GroupSemidirectProduct(self.cartesian_factors()[1],
                                      self.cartesian_factors()[0],
                                      twist=self._twist,
                                      act_to_right=not self.act_to_right(),
                                      prefix0=self._prefix1,
                                      prefix1=self._prefix0,
                                      print_tuple=self._print_tuple,
                                      category=self._category)

    def construction(self):
        r"""
        Return ``None``.

        This overrides the construction functor inherited from
        ``CartesianProduct``.

        EXAMPLES::

            sage: def twist(x, y):
            ....:     return y
            sage: H = GroupSemidirectProduct(WeylGroup(['A',2],prefix='s'),
            ....:                            WeylGroup(['A',3],prefix='t'), twist)
            sage: H.construction()
        """
        return None


GroupSemidirectProduct.Element = GroupSemidirectProductElement
