# sage.doctest: needs sage.groups
r"""
Common category for Generalized Coxeter Groups or Complex Reflection Groups
"""
# ****************************************************************************
#  Copyright (C) 2016 Nicolas M. Thiéry <nthiery at users.sf.net>
#
# 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/
# ****************************************************************************

import itertools
from sage.misc.abstract_method import abstract_method
from sage.misc.cachefunc import cached_method
from sage.categories.category_singleton import Category_singleton
from sage.categories.category_with_axiom import CategoryWithAxiom
from sage.categories.groups import Groups


class ComplexReflectionOrGeneralizedCoxeterGroups(Category_singleton):
    r"""
    The category of complex reflection groups or generalized Coxeter groups.

    Finite Coxeter groups can be defined equivalently as groups
    generated by reflections, or by presentations. Over the last
    decades, the theory has been generalized in both directions,
    leading to the study of (finite) complex reflection groups on the
    one hand, and (finite) generalized Coxeter groups on the other
    hand. Many of the features remain similar, yet, in the current
    state of the art, there is no general theory covering both
    directions.

    This is reflected by the name of this category which is about
    factoring out the common code, tests, and declarations.

    A group in this category has:

    - A distinguished finite set of generators `(s_i)_I`, called
      *simple reflections*. The set `I` is called the *index set*. The
      name "reflection" is somewhat of an abuse as they can have
      higher order; still, they are all of finite order: `s_i^k=1` for
      some `k`.

    - A collection of *distinguished reflections* which are the
      conjugates of the simple reflections. For complex reflection
      groups, they are in one-to-one correspondence with the
      reflection hyperplanes and share the same index set.

    - A collection of *reflections* which are the conjugates of all
      the non trivial powers of the simple reflections.

    The usual notions of reduced words, length, irreducibility, etc
    can be canonically defined from the above.

    The following methods must be implemented:

    - :meth:`ComplexReflectionOrGeneralizedCoxeterGroups.ParentMethods.index_set`
    - :meth:`ComplexReflectionOrGeneralizedCoxeterGroups.ParentMethods.simple_reflection`

    Optionally one can define analog methods for distinguished
    reflections and reflections (see below).

    At least one of the following methods must be implemented:

    - :meth:`ComplexReflectionOrGeneralizedCoxeterGroups.ElementMethods.apply_simple_reflection`
    - :meth:`ComplexReflectionOrGeneralizedCoxeterGroups.ElementMethods.apply_simple_reflection_left`
    - :meth:`ComplexReflectionOrGeneralizedCoxeterGroups.ElementMethods.apply_simple_reflection_right`
    - :meth:`ComplexReflectionOrGeneralizedCoxeterGroups.ElementMethods._mul_`

    It's recommended to implement either ``_mul_`` or both
    ``apply_simple_reflection_left`` and ``apply_simple_reflection_right``.

    .. SEEALSO::

        - :class:`complex_reflection_groups.ComplexReflectionGroups`
        - :class:`generalized_coxeter_groups.GeneralizedCoxeterGroups`

    EXAMPLES::

        sage: from sage.categories.complex_reflection_or_generalized_coxeter_groups import ComplexReflectionOrGeneralizedCoxeterGroups
        sage: C = ComplexReflectionOrGeneralizedCoxeterGroups(); C
        Category of complex reflection or generalized Coxeter groups
        sage: C.super_categories()
        [Category of finitely generated enumerated groups]

        sage: C.required_methods()
        {'element': {'optional': ['reflection_length'],
                     'required': []},
          'parent': {'optional': ['distinguished_reflection', 'hyperplane_index_set',
                                  'irreducible_components',
                                  'reflection', 'reflection_index_set'],
                    'required':  ['__contains__', 'index_set']}}

    TESTS::

        sage: TestSuite(C).run()
    """

    @cached_method
    def super_categories(self):
        r"""
        Return the super categories of ``self``.

        EXAMPLES::

            sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
            sage: ComplexReflectionGroups().super_categories()
            [Category of complex reflection or generalized Coxeter groups]
        """
        return [Groups().FinitelyGenerated()]

    class SubcategoryMethods:
        def Irreducible(self):
            r"""
            Return the full subcategory of irreducible objects of ``self``.

            A complex reflection group, or generalized Coxeter group
            is *reducible* if its simple reflections can be split in
            two sets `X` and `Y` such that the elements of `X` commute
            with that of `Y`. In particular, the group is then direct
            product of `\langle X \rangle` and `\langle Y \rangle`.
            It's *irreducible* otherwise.

            EXAMPLES::

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: ComplexReflectionGroups().Irreducible()
                Category of irreducible complex reflection groups
                sage: CoxeterGroups().Irreducible()
                Category of irreducible Coxeter groups

            TESTS::

                sage: TestSuite(ComplexReflectionGroups().Irreducible()).run()
                sage: CoxeterGroups().Irreducible.__module__
                'sage.categories.complex_reflection_or_generalized_coxeter_groups'
            """
            return self._with_axiom('Irreducible')

    class ParentMethods:
        @abstract_method
        def index_set(self):
            r"""
            Return the index set of (the simple reflections of)
            ``self``, as a list (or iterable).

            .. SEEALSO::

                - :meth:`simple_reflection`
                - :meth:`simple_reflections`

            EXAMPLES::

                sage: W = CoxeterGroups().Finite().example(); W
                The 5-th dihedral group of order 10
                sage: W.index_set()
                (1, 2)

                sage: W = ColoredPermutations(1, 4)
                sage: W.index_set()
                (1, 2, 3)
                sage: W = ReflectionGroup((1,1,4), index_set=[1,3,'asdf'])  # optional - gap3
                sage: W.index_set()                                     # optional - gap3
                (1, 3, 'asdf')
                sage: W = ReflectionGroup((1,1,4), index_set=('a','b','c')) # optional - gap3
                sage: W.index_set()                                     # optional - gap3
                ('a', 'b', 'c')
            """
            # return self.simple_reflections().keys()

        def simple_reflection(self, i):
            """
            Return the `i`-th simple reflection `s_i` of ``self``.

            INPUT:

            - ``i`` -- an element from the index set

            .. SEEALSO::

                - :meth:`index_set`
                - :meth:`simple_reflections`

            EXAMPLES::

                sage: W = CoxeterGroups().example()
                sage: W
                The symmetric group on {0, ..., 3}
                sage: W.simple_reflection(1)
                (0, 2, 1, 3)
                sage: s = W.simple_reflections()
                sage: s[1]
                (0, 2, 1, 3)

                sage: W = ReflectionGroup((1,1,4), index_set=[1,3,'asdf'])  # optional - gap3
                sage: for i in W.index_set():                           # optional - gap3
                ....:     print('%s %s'%(i, W.simple_reflection(i)))
                1 (1,7)(2,4)(5,6)(8,10)(11,12)
                3 (1,4)(2,8)(3,5)(7,10)(9,11)
                asdf (2,5)(3,9)(4,6)(8,11)(10,12)
            """
            if i not in self.index_set():
                raise ValueError("%s is not in the Dynkin node set %s" % (i, self.index_set()))
            return self.one().apply_simple_reflection(i)  # don't care about left/right

        @cached_method
        def simple_reflections(self):
            r"""
            Return the simple reflections `(s_i)_{i\in I}` of ``self`` as
            a family indexed by :meth:`index_set`.

            .. SEEALSO::

                - :meth:`simple_reflection`
                - :meth:`index_set`

            EXAMPLES:

            For the symmetric group, we recognize the simple transpositions::

                sage: W = SymmetricGroup(4); W
                Symmetric group of order 4! as a permutation group
                sage: s = W.simple_reflections()
                sage: s
                Finite family {1: (1,2), 2: (2,3), 3: (3,4)}
                sage: s[1]
                (1,2)
                sage: s[2]
                (2,3)
                sage: s[3]
                (3,4)

            Here are the simple reflections for a colored symmetric
            group and a reflection group::

                sage: W = ColoredPermutations(1,3)
                sage: W.simple_reflections()
                Finite family {1: [[0, 0, 0], [2, 1, 3]], 2: [[0, 0, 0], [1, 3, 2]]}

                sage: W = ReflectionGroup((1,1,3), index_set=['a','b']) # optional - gap3
                sage: W.simple_reflections()                            # optional - gap3
                Finite family {'a': (1,4)(2,3)(5,6), 'b': (1,3)(2,5)(4,6)}

            This default implementation uses :meth:`.index_set` and
            :meth:`.simple_reflection`.
            """
            from sage.sets.family import Family
            return Family(self.index_set(), self.simple_reflection)

        def number_of_simple_reflections(self):
            r"""
            Return the number of simple reflections of ``self``.

            EXAMPLES::

                sage: W = ColoredPermutations(1,3)
                sage: W.number_of_simple_reflections()
                2
                sage: W = ColoredPermutations(2,3)
                sage: W.number_of_simple_reflections()
                3
                sage: W = ColoredPermutations(4,3)
                sage: W.number_of_simple_reflections()
                3
                sage: W = ReflectionGroup((4,2,3))                      # optional - gap3
                sage: W.number_of_simple_reflections()                  # optional - gap3
                4
            """
            return len(self.index_set())

        ##########################################################################
        # Group generators, etc from simple reflections
        ##########################################################################

        def group_generators(self):
            r"""
            Return the simple reflections of ``self``, as
            distinguished group generators.

            .. SEEALSO::

                - :meth:`simple_reflections`
                - :meth:`Groups.ParentMethods.group_generators`
                - :meth:`Semigroups.ParentMethods.semigroup_generators`

            EXAMPLES::

                sage: D10 = FiniteCoxeterGroups().example(10)
                sage: D10.group_generators()
                Finite family {1: (1,), 2: (2,)}
                sage: SymmetricGroup(5).group_generators()
                Finite family {1: (1,2), 2: (2,3), 3: (3,4), 4: (4,5)}

                sage: W = ColoredPermutations(3,2)
                sage: W.group_generators()
                Finite family {1: [[0, 0],
                                   [2, 1]],
                               2: [[0, 1],
                                   [1, 2]]}

            The simple reflections are also semigroup generators, even
            for an infinite group::

                sage: W = WeylGroup(["A",2,1])
                sage: W.semigroup_generators()
                Finite family {0: [-1  1  1]
                                  [ 0  1  0]
                                  [ 0  0  1],
                               1: [ 1  0  0]
                                  [ 1 -1  1]
                                  [ 0  0  1],
                               2: [ 1  0  0]
                                  [ 0  1  0]
                                  [ 1  1 -1]}
            """
            return self.simple_reflections()

        semigroup_generators = group_generators

        def simple_reflection_orders(self):
            """
            Return the orders of the simple reflections.

            EXAMPLES::

                sage: W = WeylGroup(['B',3])
                sage: W.simple_reflection_orders()
                [2, 2, 2]
                sage: W = CoxeterGroup(['C',4])
                sage: W.simple_reflection_orders()
                [2, 2, 2, 2]
                sage: SymmetricGroup(5).simple_reflection_orders()
                [2, 2, 2, 2]
                sage: C = ColoredPermutations(4, 3)
                sage: C.simple_reflection_orders()
                [2, 2, 4]
            """
            one = self.one()
            s = self.simple_reflections()
            from sage.rings.integer_ring import ZZ

            def mult_order(x):
                ct = ZZ.one()
                cur = x
                while cur != one:
                    cur *= x
                    ct += ZZ.one()
                return ZZ(ct)
            return [mult_order(s[i]) for i in self.index_set()]

        def _an_element_(self):
            """
            Implement: :meth:`Sets.ParentMethods.an_element` by
            returning the product of the simple reflections (a Coxeter
            element).

            EXAMPLES::

                sage: W = SymmetricGroup(4); W
                Symmetric group of order 4! as a permutation group
                sage: W.an_element()               # indirect doctest
                (2,3,4)

            For a complex reflection group::

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: W = ComplexReflectionGroups().example(); W
                5-colored permutations of size 3
                sage: W.an_element()
                [[1, 0, 0], [3, 1, 2]]
            """
            return self.prod(self.simple_reflections())

        def some_elements(self):
            r"""
            Implement :meth:`Sets.ParentMethods.some_elements` by
            returning some typical elements of ``self``.

            The result is currently composed of the simple reflections
            together with the unit and the result of :meth:`an_element`.

            EXAMPLES::

                sage: W = WeylGroup(['A',3])
                sage: W.some_elements()
                [
                [0 1 0 0]  [1 0 0 0]  [1 0 0 0]  [1 0 0 0]  [0 0 0 1]
                [1 0 0 0]  [0 0 1 0]  [0 1 0 0]  [0 1 0 0]  [1 0 0 0]
                [0 0 1 0]  [0 1 0 0]  [0 0 0 1]  [0 0 1 0]  [0 1 0 0]
                [0 0 0 1], [0 0 0 1], [0 0 1 0], [0 0 0 1], [0 0 1 0]
                ]

                sage: W = ColoredPermutations(1,4)
                sage: W.some_elements()
                [[[0, 0, 0, 0], [2, 1, 3, 4]],
                 [[0, 0, 0, 0], [1, 3, 2, 4]],
                 [[0, 0, 0, 0], [1, 2, 4, 3]],
                 [[0, 0, 0, 0], [1, 2, 3, 4]],
                 [[0, 0, 0, 0], [4, 1, 2, 3]]]
            """
            return list(self.simple_reflections()) + [self.one(), self.an_element()]

        ######################################################################
        # Reflections
        ######################################################################

        @abstract_method(optional=True)
        def reflection_index_set(self):
            r"""
            Return the index set of the reflections of ``self``.

            .. SEEALSO::

                - :meth:`reflection`
                - :meth:`reflections`

            EXAMPLES::

                sage: # optional - gap3
                sage: W = ReflectionGroup((1,1,4))
                sage: W.reflection_index_set()
                (1, 2, 3, 4, 5, 6)
                sage: W = ReflectionGroup((1,1,4), reflection_index_set=[1,3,'asdf',7,9,11])
                sage: W.reflection_index_set()
                (1, 3, 'asdf', 7, 9, 11)
                sage: W = ReflectionGroup((1,1,4), reflection_index_set=('a','b','c','d','e','f'))
                sage: W.reflection_index_set()
                ('a', 'b', 'c', 'd', 'e', 'f')
            """

        @abstract_method(optional=True)
        def reflection(self, i):
            r"""
            Return the `i`-th reflection of ``self``.

            For `i` in `1,\dots,N`, this gives the `i`-th reflection of
            ``self``.

            .. SEEALSO::

                - :meth:`reflections_index_set`
                - :meth:`reflections`

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,4))                      # optional - gap3
                sage: for i in W.reflection_index_set():                # optional - gap3
                ....:     print('%s %s'%(i, W.reflection(i)))
                1 (1,7)(2,4)(5,6)(8,10)(11,12)
                2 (1,4)(2,8)(3,5)(7,10)(9,11)
                3 (2,5)(3,9)(4,6)(8,11)(10,12)
                4 (1,8)(2,7)(3,6)(4,10)(9,12)
                5 (1,6)(2,9)(3,8)(5,11)(7,12)
                6 (1,11)(3,10)(4,9)(5,7)(6,12)
            """

        @cached_method
        def reflections(self):
            r"""
            Return a finite family containing the reflections of
            ``self``, indexed by :meth:`reflection_index_set`.

            .. SEEALSO::

                - :meth:`reflection`
                - :meth:`reflection_index_set`

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,3))                      # optional - gap3
                sage: reflections = W.reflections()                     # optional - gap3
                sage: for index in sorted(reflections.keys()):          # optional - gap3
                ....:     print('%s %s'%(index, reflections[index]))
                1 (1,4)(2,3)(5,6)
                2 (1,3)(2,5)(4,6)
                3 (1,5)(2,4)(3,6)

                sage: W = ReflectionGroup((1,1,3),reflection_index_set=['a','b','c'])   # optional - gap3
                sage: reflections = W.reflections()                     # optional - gap3
                sage: for index in sorted(reflections.keys()):          # optional - gap3
                ....:     print('%s %s'%(index, reflections[index]))
                a (1,4)(2,3)(5,6)
                b (1,3)(2,5)(4,6)
                c (1,5)(2,4)(3,6)

                sage: W = ReflectionGroup((3,1,1))                      # optional - gap3
                sage: reflections = W.reflections()                     # optional - gap3
                sage: for index in sorted(reflections.keys()):          # optional - gap3
                ....:     print('%s %s'%(index, reflections[index]))
                1 (1,2,3)
                2 (1,3,2)

                sage: W = ReflectionGroup((1,1,3), (3,1,2))             # optional - gap3
                sage: reflections = W.reflections()                     # optional - gap3
                sage: for index in sorted(reflections.keys()):          # optional - gap3
                ....:     print('%s %s'%(index, reflections[index]))
                1 (1,6)(2,5)(7,8)
                2 (1,5)(2,7)(6,8)
                3 (3,9,15)(4,10,16)(12,17,23)(14,18,24)(20,25,29)(21,22,26)(27,28,30)
                4 (3,11)(4,12)(9,13)(10,14)(15,19)(16,20)(17,21)(18,22)(23,27)(24,28)(25,26)(29,30)
                5 (1,7)(2,6)(5,8)
                6 (3,19)(4,25)(9,11)(10,17)(12,28)(13,15)(14,30)(16,18)(20,27)(21,29)(22,23)(24,26)
                7 (4,21,27)(10,22,28)(11,13,19)(12,14,20)(16,26,30)(17,18,25)(23,24,29)
                8 (3,13)(4,24)(9,19)(10,29)(11,15)(12,26)(14,21)(16,23)(17,30)(18,27)(20,22)(25,28)
                9 (3,15,9)(4,16,10)(12,23,17)(14,24,18)(20,29,25)(21,26,22)(27,30,28)
                10 (4,27,21)(10,28,22)(11,19,13)(12,20,14)(16,30,26)(17,25,18)(23,29,24)
            """
            from sage.sets.family import Family
            return Family(self.reflection_index_set(), self.reflection)

        ##########################################################################
        # distinguished reflections
        ##########################################################################

        @abstract_method(optional=True)
        def hyperplane_index_set(self):
            r"""
            Return the index set of the distinguished reflections of ``self``.

            This is also the index set of the reflection hyperplanes
            of ``self``, hence the name. This name is slightly abusive
            since the concept of reflection hyperplanes is not defined
            for all generalized Coxeter groups. However for all
            practical purposes this is only used for complex
            reflection groups, and there this is the desirable name.

            .. SEEALSO::

                - :meth:`distinguished_reflection`
                - :meth:`distinguished_reflections`

            EXAMPLES::

                sage: # optional - gap3
                sage: W = ReflectionGroup((1,1,4))
                sage: W.hyperplane_index_set()
                (1, 2, 3, 4, 5, 6)
                sage: W = ReflectionGroup((1,1,4), hyperplane_index_set=[1,3,'asdf',7,9,11])
                sage: W.hyperplane_index_set()
                (1, 3, 'asdf', 7, 9, 11)
                sage: W = ReflectionGroup((1,1,4), hyperplane_index_set=('a','b','c','d','e','f'))
                sage: W.hyperplane_index_set()
                ('a', 'b', 'c', 'd', 'e', 'f')
            """

        @abstract_method(optional=True)
        def distinguished_reflection(self, i):
            r"""
            Return the `i`-th distinguished reflection of ``self``.

            INPUT:

            - ``i`` -- an element of the index set of the distinguished reflections.

            .. SEEALSO::

                - :meth:`distinguished_reflections`
                - :meth:`hyperplane_index_set`

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,4), hyperplane_index_set=('a','b','c','d','e','f'))  # optional - gap3
                sage: for i in W.hyperplane_index_set():                    # optional - gap3
                ....:     print('%s %s'%(i, W.distinguished_reflection(i)))
                a (1,7)(2,4)(5,6)(8,10)(11,12)
                b (1,4)(2,8)(3,5)(7,10)(9,11)
                c (2,5)(3,9)(4,6)(8,11)(10,12)
                d (1,8)(2,7)(3,6)(4,10)(9,12)
                e (1,6)(2,9)(3,8)(5,11)(7,12)
                f (1,11)(3,10)(4,9)(5,7)(6,12)
            """

        @cached_method
        def distinguished_reflections(self):
            r"""
            Return a finite family containing the distinguished
            reflections of ``self``, indexed by
            :meth:`hyperplane_index_set`.

            A *distinguished reflection* is a conjugate of a simple
            reflection. For a Coxeter group, reflections and
            distinguished reflections coincide. For a Complex
            reflection groups this is a reflection acting on the
            complement of the fixed hyperplane `H` as
            `\operatorname{exp}(2 \pi i / n)`, where `n` is the order
            of the reflection subgroup fixing `H`.

            .. SEEALSO::

                - :meth:`distinguished_reflection`
                - :meth:`hyperplane_index_set`

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,3))                      # optional - gap3
                sage: distinguished_reflections = W.distinguished_reflections() # optional - gap3
                sage: for index in sorted(distinguished_reflections.keys()):        # optional - gap3
                ....:     print('%s %s'%(index, distinguished_reflections[index]))
                1 (1,4)(2,3)(5,6)
                2 (1,3)(2,5)(4,6)
                3 (1,5)(2,4)(3,6)

                sage: W = ReflectionGroup((1,1,3),hyperplane_index_set=['a','b','c'])   # optional - gap3
                sage: distinguished_reflections = W.distinguished_reflections() # optional - gap3
                sage: for index in sorted(distinguished_reflections.keys()):        # optional - gap3
                ....:     print('%s %s'%(index, distinguished_reflections[index]))
                a (1,4)(2,3)(5,6)
                b (1,3)(2,5)(4,6)
                c (1,5)(2,4)(3,6)

                sage: W = ReflectionGroup((3,1,1))                      # optional - gap3
                sage: distinguished_reflections = W.distinguished_reflections() # optional - gap3
                sage: for index in sorted(distinguished_reflections.keys()):        # optional - gap3
                ....:     print('%s %s'%(index, distinguished_reflections[index]))
                1 (1,2,3)

                sage: W = ReflectionGroup((1,1,3), (3,1,2))             # optional - gap3
                sage: distinguished_reflections = W.distinguished_reflections() # optional - gap3
                sage: for index in sorted(distinguished_reflections.keys()):    # optional - gap3
                ....:     print('%s %s'%(index, distinguished_reflections[index]))
                1 (1,6)(2,5)(7,8)
                2 (1,5)(2,7)(6,8)
                3 (3,9,15)(4,10,16)(12,17,23)(14,18,24)(20,25,29)(21,22,26)(27,28,30)
                4 (3,11)(4,12)(9,13)(10,14)(15,19)(16,20)(17,21)(18,22)(23,27)(24,28)(25,26)(29,30)
                5 (1,7)(2,6)(5,8)
                6 (3,19)(4,25)(9,11)(10,17)(12,28)(13,15)(14,30)(16,18)(20,27)(21,29)(22,23)(24,26)
                7 (4,21,27)(10,22,28)(11,13,19)(12,14,20)(16,26,30)(17,18,25)(23,24,29)
                8 (3,13)(4,24)(9,19)(10,29)(11,15)(12,26)(14,21)(16,23)(17,30)(18,27)(20,22)(25,28)
            """
            from sage.sets.family import Family
            return Family(self.hyperplane_index_set(), self.distinguished_reflection)

        ##########################################################################
        # from_reduced_word
        ##########################################################################

        def from_reduced_word(self, word, word_type='simple'):
            r"""
            Return an element of ``self`` from its (reduced) word.

            INPUT:

            - ``word`` -- a list (or iterable) of elements of the
              index set of ``self`` (resp. of the distinguished
              or of all reflections)
            - ``word_type`` -- (optional, default: ``'simple'``):
              either ``'simple'``, ``'distinguished'``, or ``'all'``

            If ``word`` is `[i_1,i_2,\ldots,i_k]`, then this returns
            the corresponding product of simple reflections
            `s_{i_1} s_{i_2} \cdots s_{i_k}`.

            If ``word_type`` is ``'distinguished'`` (resp. ``'all'``),
            then the product of the distinguished reflections (resp. all
            reflections) is returned.

            .. NOTE::

                The main use case is for constructing elements from
                reduced words, hence the name of this method.
                However, the input word need *not* be reduced.

            .. SEEALSO::

                - :meth:`index_set`
                - :meth:`reflection_index_set`
                - :meth:`hyperplane_index_set`
                - :meth:`~ComplexReflectionOrGeneralizedCoxeterGroups.ElementMethods.apply_simple_reflections`
                - :meth:`~CoxeterGroup.ElementMethods.reduced_word`
                - :meth:`~CoxeterGroup.ParentMethods._test_reduced_word`

            EXAMPLES::

                sage: W = CoxeterGroups().example()
                sage: W
                The symmetric group on {0, ..., 3}
                sage: s = W.simple_reflections()
                sage: W.from_reduced_word([0,2,0,1])
                (0, 3, 1, 2)
                sage: W.from_reduced_word((0,2,0,1))
                (0, 3, 1, 2)
                sage: s[0]*s[2]*s[0]*s[1]
                (0, 3, 1, 2)

            We now experiment with the different values for
            ``word_type`` for the colored symmetric group::

                sage: W = ColoredPermutations(1,4)
                sage: W.from_reduced_word([1,2,1,2,1,2])
                [[0, 0, 0, 0], [1, 2, 3, 4]]

                sage: W.from_reduced_word([1, 2, 3]).reduced_word()
                [1, 2, 3]

                sage: W = WeylGroup("A3", prefix='s')
                sage: AS = W.domain()
                sage: r1 = AS.roots()[4]
                sage: r1
                (0, 1, 0, -1)
                sage: r2 = AS.roots()[5]
                sage: r2
                (0, 0, 1, -1)
                sage: W.from_reduced_word([r1, r2], word_type='all')
                s3*s2

                sage: W = WeylGroup("G2", prefix='s')
                sage: W.from_reduced_word(W.domain().positive_roots(), word_type='all')
                s1*s2

                sage: W = ReflectionGroup((1,1,4))           # optional - gap3
                sage: W.from_reduced_word([1,2,3], word_type='all').reduced_word()  # optional - gap3
                [1, 2, 3]

                sage: W.from_reduced_word([1,2,3], word_type='all').reduced_word_in_reflections()   # optional - gap3
                [1, 2, 3]

                sage: W.from_reduced_word([1,2,3]).reduced_word_in_reflections()    # optional - gap3
                [1, 2, 3]

            TESTS::

                sage: W = WeylGroup(['E',6])
                sage: W.from_reduced_word([2,3,4,2])
                [ 0  1  0  0  0  0  0  0]
                [ 0  0 -1  0  0  0  0  0]
                [-1  0  0  0  0  0  0  0]
                [ 0  0  0  1  0  0  0  0]
                [ 0  0  0  0  1  0  0  0]
                [ 0  0  0  0  0  1  0  0]
                [ 0  0  0  0  0  0  1  0]
                [ 0  0  0  0  0  0  0  1]
            """
            if word_type == 'simple':
                return self.one().apply_simple_reflections(word)
            else:
                return self.one().apply_reflections(word, word_type=word_type)

        ##########################################################################
        # Irreducible components
        ##########################################################################

        def irreducible_component_index_sets(self):
            r"""
            Return a list containing the index sets of the irreducible components of
            ``self`` as finite reflection groups.

            EXAMPLES::

                sage: W = ReflectionGroup([1,1,3], [3,1,3], 4); W       # optional - gap3
                Reducible complex reflection group of rank 7 and type A2 x G(3,1,3) x ST4
                sage: sorted(W.irreducible_component_index_sets())      # optional - gap3
                [[1, 2], [3, 4, 5], [6, 7]]

            ALGORITHM:

            Take the connected components of the graph on the
            index set with edges ``(i,j)``, where ``s[i]`` and
            ``s[j]`` do not commute.
            """
            I = self.index_set()
            s = self.simple_reflections()
            from sage.graphs.graph import Graph
            G = Graph([I,
                       [[i,j]
                        for i,j in itertools.combinations(I,2)
                        if s[i]*s[j] != s[j]*s[i] ]],
                      format="vertices_and_edges")
            return G.connected_components(sort=False)

        @abstract_method(optional=True)
        def irreducible_components(self):
            r"""
            Return the irreducible components of ``self`` as finite
            reflection groups.

            EXAMPLES::

                sage: W = ReflectionGroup([1,1,3], [3,1,3], 4)          # optional - gap3
                sage: W.irreducible_components()                        # optional - gap3
                [Irreducible real reflection group of rank 2 and type A2,
                 Irreducible complex reflection group of rank 3 and type G(3,1,3),
                 Irreducible complex reflection group of rank 2 and type ST4]
            """
            # TODO: provide a default implementation using the above and parabolic subgroups

        def number_of_irreducible_components(self):
            r"""
            Return the number of irreducible components of ``self``.

            EXAMPLES::

                sage: SymmetricGroup(3).number_of_irreducible_components()
                1

                sage: ColoredPermutations(1,3).number_of_irreducible_components()
                1

                sage: ReflectionGroup((1,1,3),(2,1,3)).number_of_irreducible_components()   # optional - gap3
                2

            TESTS::

                sage: SymmetricGroup(3).number_of_irreducible_components.__module__
                'sage.categories.complex_reflection_or_generalized_coxeter_groups'
            """
            return len(self.irreducible_component_index_sets())

        def is_irreducible(self):
            r"""
            Return ``True`` if ``self`` is irreducible.

            EXAMPLES::

                sage: W = ColoredPermutations(1,3); W
                1-colored permutations of size 3
                sage: W.is_irreducible()
                True

                sage: W = ReflectionGroup((1,1,3),(2,1,3)); W           # optional - gap3
                Reducible real reflection group of rank 5 and type A2 x B3
                sage: W.is_irreducible()                                # optional - gap3
                False
            """
            return self.number_of_irreducible_components() == 1

        def is_reducible(self):
            r"""
            Return ``True`` if ``self`` is not irreducible.

            EXAMPLES::

                sage: W = ColoredPermutations(1,3); W
                1-colored permutations of size 3
                sage: W.is_reducible()
                False

                sage: W = ReflectionGroup((1,1,3), (2,1,3)); W          # optional - gap3
                Reducible real reflection group of rank 5 and type A2 x B3
                sage: W.is_reducible()                                  # optional - gap3
                True
            """
            return not self.is_irreducible()

    class ElementMethods:
        def apply_simple_reflection_left(self, i):
            r"""
            Return ``self`` multiplied by the simple reflection ``s[i]``
            on the left.

            This low level method is used intensively. Coxeter groups
            are encouraged to override this straightforward
            implementation whenever a faster approach exists.

            EXAMPLES::

                sage: W = CoxeterGroups().example()
                sage: w = W.an_element(); w
                (1, 2, 3, 0)
                sage: w.apply_simple_reflection_left(0)
                (0, 2, 3, 1)
                sage: w.apply_simple_reflection_left(1)
                (2, 1, 3, 0)
                sage: w.apply_simple_reflection_left(2)
                (1, 3, 2, 0)

            EXAMPLES::

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: W = ComplexReflectionGroups().example()
                sage: w = W.an_element(); w
                [[1, 0, 0], [3, 1, 2]]
                sage: w.apply_simple_reflection_left(1)
                [[0, 1, 0], [1, 3, 2]]
                sage: w.apply_simple_reflection_left(2)
                [[1, 0, 0], [3, 2, 1]]
                sage: w.apply_simple_reflection_left(3)
                [[1, 0, 1], [3, 1, 2]]

            TESTS::

                sage: w.apply_simple_reflection_left.__module__
                'sage.categories.complex_reflection_or_generalized_coxeter_groups'
            """
            s = self.parent().simple_reflections()
            return s[i] * self

        def apply_simple_reflection_right(self, i):
            """
            Return ``self`` multiplied by the simple reflection ``s[i]``
            on the right.

            This low level method is used intensively. Coxeter groups
            are encouraged to override this straightforward
            implementation whenever a faster approach exists.

            EXAMPLES::

                sage: W = CoxeterGroups().example()
                sage: w = W.an_element(); w
                (1, 2, 3, 0)
                sage: w.apply_simple_reflection_right(0)
                (2, 1, 3, 0)
                sage: w.apply_simple_reflection_right(1)
                (1, 3, 2, 0)
                sage: w.apply_simple_reflection_right(2)
                (1, 2, 0, 3)

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: W = ComplexReflectionGroups().example()
                sage: w = W.an_element(); w
                [[1, 0, 0], [3, 1, 2]]
                sage: w.apply_simple_reflection_right(1)
                [[1, 0, 0], [3, 2, 1]]
                sage: w.apply_simple_reflection_right(2)
                [[1, 0, 0], [2, 1, 3]]
                sage: w.apply_simple_reflection_right(3)
                [[2, 0, 0], [3, 1, 2]]

            TESTS::

                sage: w.apply_simple_reflection_right.__module__
                'sage.categories.complex_reflection_or_generalized_coxeter_groups'
            """
            s = self.parent().simple_reflections()
            return self * s[i]

        def apply_simple_reflection(self, i, side='right'):
            """
            Return ``self`` multiplied by the simple reflection ``s[i]``.

            INPUT:

            - ``i`` -- an element of the index set
            - ``side`` -- (default: ``"right"``) ``"left"`` or ``"right"``

            This default implementation simply calls
            :meth:`apply_simple_reflection_left` or
            :meth:`apply_simple_reflection_right`.

            EXAMPLES::

                sage: W = CoxeterGroups().example()
                sage: w = W.an_element(); w
                (1, 2, 3, 0)
                sage: w.apply_simple_reflection(0, side = "left")
                (0, 2, 3, 1)
                sage: w.apply_simple_reflection(1, side = "left")
                (2, 1, 3, 0)
                sage: w.apply_simple_reflection(2, side = "left")
                (1, 3, 2, 0)

                sage: w.apply_simple_reflection(0, side = "right")
                (2, 1, 3, 0)
                sage: w.apply_simple_reflection(1, side = "right")
                (1, 3, 2, 0)
                sage: w.apply_simple_reflection(2, side = "right")
                (1, 2, 0, 3)

            By default, ``side`` is ``"right"``::

                sage: w.apply_simple_reflection(0)
                (2, 1, 3, 0)

            Some tests with a complex reflection group::

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: W = ComplexReflectionGroups().example(); W
                5-colored permutations of size 3
                sage: w = W.an_element(); w
                [[1, 0, 0], [3, 1, 2]]
                sage: w.apply_simple_reflection(1, side="left")
                [[0, 1, 0], [1, 3, 2]]
                sage: w.apply_simple_reflection(2, side="left")
                [[1, 0, 0], [3, 2, 1]]
                sage: w.apply_simple_reflection(3, side="left")
                [[1, 0, 1], [3, 1, 2]]

                sage: w.apply_simple_reflection(1, side="right")
                [[1, 0, 0], [3, 2, 1]]
                sage: w.apply_simple_reflection(2, side="right")
                [[1, 0, 0], [2, 1, 3]]
                sage: w.apply_simple_reflection(3, side="right")
                [[2, 0, 0], [3, 1, 2]]

            TESTS::

                sage: w.apply_simple_reflection_right.__module__
                'sage.categories.complex_reflection_or_generalized_coxeter_groups'
            """
            if side == 'right':
                return self.apply_simple_reflection_right(i)
            else:
                return self.apply_simple_reflection_left(i)

        def apply_simple_reflections(self, word, side='right', type='simple'):
            r"""
            Return the result of the (left/right) multiplication of
            ``self`` by ``word``.

            INPUT:

            - ``word`` -- a sequence of indices of simple reflections
            - ``side`` -- (default: ``'right'``) indicates multiplying
              from left or right

            This is a specialized implementation of
            :meth:`apply_reflections` for the simple reflections. The
            rationale for its existence are:

            - It can take advantage of ``apply_simple_reflection``,
              which often is less expensive than computing a product.

            - It reduced burden on implementations that would want to
              provide an optimized version of this method.

            EXAMPLES::

               sage: W = CoxeterGroups().example()
               sage: w = W.an_element(); w
               (1, 2, 3, 0)
               sage: w.apply_simple_reflections([0,1])
               (2, 3, 1, 0)
               sage: w
               (1, 2, 3, 0)
               sage: w.apply_simple_reflections([0,1],side='left')
               (0, 1, 3, 2)
            """
            for i in word:
                self = self.apply_simple_reflection(i, side)
            return self

        def apply_reflections(self, word, side='right', word_type='all'):
            r"""
            Return the result of the (left/right) multiplication of
            ``self`` by ``word``.

            INPUT:

            - ``word`` -- a sequence of indices of reflections
            - ``side`` -- (default: ``'right'``) indicates multiplying
              from left or right
            - ``word_type`` -- (optional, default: ``'all'``):
              either ``'simple'``, ``'distinguished'``, or ``'all'``

            EXAMPLES::

                sage: # optional - gap3
                sage: W = ReflectionGroup((1,1,3))
                sage: W.one().apply_reflections([1])
                (1,4)(2,3)(5,6)
                sage: W.one().apply_reflections([2])
                (1,3)(2,5)(4,6)
                sage: W.one().apply_reflections([2,1])
                (1,2,6)(3,4,5)


                sage: W = CoxeterGroups().example()
                sage: w = W.an_element(); w
                (1, 2, 3, 0)
                sage: w.apply_reflections([0,1], word_type='simple')
                (2, 3, 1, 0)
                sage: w
                (1, 2, 3, 0)
                sage: w.apply_reflections([0,1], side='left', word_type='simple')
                (0, 1, 3, 2)


                sage: W = WeylGroup("A3", prefix='s')
                sage: w = W.an_element(); w
                s1*s2*s3
                sage: AS = W.domain()
                sage: r1 = AS.roots()[4]
                sage: r1
                (0, 1, 0, -1)
                sage: r2 = AS.roots()[5]
                sage: r2
                (0, 0, 1, -1)
                sage: w.apply_reflections([r1, r2], word_type='all')
                s1


                sage: # optional - gap3
                sage: W = ReflectionGroup((1,1,3))
                sage: W.one().apply_reflections([1], word_type='distinguished')
                (1,4)(2,3)(5,6)
                sage: W.one().apply_reflections([2],   word_type='distinguished')
                (1,3)(2,5)(4,6)
                sage: W.one().apply_reflections([3],   word_type='distinguished')
                (1,5)(2,4)(3,6)
                sage: W.one().apply_reflections([2,1], word_type='distinguished')
                (1,2,6)(3,4,5)

                sage: W = ReflectionGroup((1,1,3), hyperplane_index_set=['A','B','C']); W   # optional - gap3
                Irreducible real reflection group of rank 2 and type A2
                sage: W.one().apply_reflections(['A'], word_type='distinguished')   # optional - gap3
                (1,4)(2,3)(5,6)
            """
            if word_type == 'simple':
                reflections = self.parent().simple_reflections()
            elif word_type == 'distinguished':
                reflections = self.parent().distinguished_reflections()
            else:
                reflections = self.parent().reflections()
            if side == 'left':
                for i in word:
                    self = reflections[i] * self
            else:
                for i in word:
                    self = self * reflections[i]
            return self

        def _mul_(self, other):
            r"""
            Return the product of ``self`` and ``other``

            This default implementation computes a reduced word of
            ``other`` using :meth:`reduced_word`, and applies the
            corresponding simple reflections on ``self`` using
            :meth:`apply_simple_reflections`.

            EXAMPLES::

                sage: W = FiniteCoxeterGroups().example(); W
                The 5-th dihedral group of order 10
                sage: w = W.an_element()
                sage: w
                (1, 2)
                sage: w._mul_(w)
                (1, 2, 1, 2)
                sage: w._mul_(w)._mul_(w)
                (2, 1, 2, 1)

            This method is called when computing ``self * other``::

                sage: w * w
                (1, 2, 1, 2)

            TESTS::

                sage: w._mul_.__module__
                'sage.categories.complex_reflection_or_generalized_coxeter_groups'
            """
            return self.apply_simple_reflections(other.reduced_word())

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

            EXAMPLES::

                sage: W = WeylGroup(['B',7])
                sage: w = W.an_element()
                sage: u = w.inverse()  # indirect doctest
                sage: u == ~w
                True
                sage: u * w == w * u
                True
                sage: u * w
                [1 0 0 0 0 0 0]
                [0 1 0 0 0 0 0]
                [0 0 1 0 0 0 0]
                [0 0 0 1 0 0 0]
                [0 0 0 0 1 0 0]
                [0 0 0 0 0 1 0]
                [0 0 0 0 0 0 1]
            """
            return self.parent().one().apply_simple_reflections(self.reduced_word_reverse_iterator())

        def apply_conjugation_by_simple_reflection(self, i):
            r"""
            Conjugate ``self`` by the ``i``-th simple reflection.

            EXAMPLES::

                sage: W = WeylGroup(['A',3])
                sage: w = W.from_reduced_word([3,1,2,1])
                sage: w.apply_conjugation_by_simple_reflection(1).reduced_word()
                [3, 2]
            """
            return self.apply_simple_reflection(i).apply_simple_reflection(i, side='left')

        @abstract_method(optional=True)
        def reflection_length(self):
            r"""
            Return the reflection length of ``self``.

            This is the minimal length of a factorization of ``self``
            into reflections.

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1]

                sage: W = ReflectionGroup((2,1,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 1, 1, 2, 2, 2]

                sage: W = ReflectionGroup((3,1,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]

                sage: W = ReflectionGroup((2,2,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 2]
            """

        def is_reflection(self):
            r"""
            Return whether ``self`` is a reflection.

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,4))                      # optional - gap3
                sage: [t.is_reflection() for t in W.reflections()]      # optional - gap3
                [True, True, True, True, True, True]
                sage: len([t for t in W.reflections() if t.is_reflection()])    # optional - gap3
                6

                sage: W = ReflectionGroup((2,1,3))                      # optional - gap3
                sage: [t.is_reflection() for t in W.reflections()]      # optional - gap3
                [True, True, True, True, True, True, True, True, True]
                sage: len([t for t in W.reflections() if t.is_reflection()])    # optional - gap3
                9
            """
            return self.reflection_length() == 1

    class Irreducible(CategoryWithAxiom):
        class ParentMethods:
            def irreducible_components(self):
                r"""
                Return a list containing all irreducible components of
                ``self`` as finite reflection groups.

                EXAMPLES::

                    sage: W = ColoredPermutations(4, 3)
                    sage: W.irreducible_components()
                    [4-colored permutations of size 3]
                """
                return [self]
