r"""
Finite Complex Reflection Groups
"""
# ****************************************************************************
#       Copyright (C) 2011-2015 Christian Stump <christian.stump at gmail.com>
#
# 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.misc.abstract_method import abstract_method
from sage.misc.misc_c import prod
from sage.misc.cachefunc import cached_method
from sage.categories.category_with_axiom import CategoryWithAxiom
from sage.categories.coxeter_groups import CoxeterGroups
from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet


class FiniteComplexReflectionGroups(CategoryWithAxiom):
    r"""
    The category of finite complex reflection groups.

    See :class:`ComplexReflectionGroups` for the definition of complex
    reflection group. In the finite case, most of the information
    about the group can be recovered from its *degrees* and
    *codegrees*, and to a lesser extent to the explicit realization as
    subgroup of `GL(V)`. Hence the most important optional methods to
    implement are:

    - :meth:`ComplexReflectionGroups.Finite.ParentMethods.degrees`,
    - :meth:`ComplexReflectionGroups.Finite.ParentMethods.codegrees`,
    - :meth:`ComplexReflectionGroups.Finite.ElementMethods.to_matrix`.

    Finite complex reflection groups are completely classified. In
    particular, if the group is irreducible, then it's uniquely
    determined by its degrees and codegrees and whether it's
    reflection representation is *primitive* or not (see [LT2009]_
    Chapter 2.1 for the definition of primitive).

    .. SEEALSO:: :wikipedia:`Complex_reflection_groups`

    EXAMPLES::

        sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
        sage: ComplexReflectionGroups().Finite()
        Category of finite complex reflection groups
        sage: ComplexReflectionGroups().Finite().super_categories()
        [Category of complex reflection groups,
         Category of finite groups,
         Category of finite finitely generated semigroups]

    An example of a finite reflection group::

        sage: W = ComplexReflectionGroups().Finite().example(); W       # optional - gap3
        Reducible real reflection group of rank 4 and type A2 x B2

        sage: W.reflections()                              # optional - gap3
        Finite family {1: (1,8)(2,5)(9,12), 2: (1,5)(2,9)(8,12),
                       3: (3,10)(4,7)(11,14), 4: (3,6)(4,11)(10,13),
                       5: (1,9)(2,8)(5,12), 6: (4,14)(6,13)(7,11),
                       7: (3,13)(6,10)(7,14)}

    ``W`` is in the category of complex reflection groups::

        sage: W in ComplexReflectionGroups().Finite()      # optional - gap3
        True
    """
    def example(self):
        r"""
        Return an example of a complex reflection group.

        EXAMPLES::

            sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
            sage: ComplexReflectionGroups().Finite().example()          # optional - gap3
            Reducible real reflection group of rank 4 and type A2 x B2
        """
        from sage.combinat.root_system.reflection_group_real import ReflectionGroup
        return ReflectionGroup((1,1,3), (2,1,2))

    class SubcategoryMethods:

        @cached_method
        def WellGenerated(self):
            r"""
            Return the full subcategory of well-generated objects of ``self``.

            A finite complex generated group is *well generated* if it
            is isomorphic to a subgroup of the general linear group
            `GL_n` generated by `n` reflections.

            .. SEEALSO::

                :meth:`ComplexReflectionGroups.Finite.ParentMethods.is_well_generated`

            EXAMPLES::

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: C = ComplexReflectionGroups().Finite().WellGenerated(); C
                Category of well generated finite complex reflection groups

            Here is an example of a finite well-generated complex
            reflection group::

                sage: W = C.example(); W                # optional - gap3
                Reducible complex reflection group of rank 4 and type A2 x G(3,1,2)

            All finite Coxeter groups are well generated::

                sage: CoxeterGroups().Finite().is_subcategory(C)
                True
                sage: SymmetricGroup(3) in C
                True

            .. NOTE::

                The category of well generated finite complex
                reflection groups is currently implemented as an
                axiom. See discussion on :trac:`11187`. This may be a
                bit of overkill. Still it's nice to have a full
                subcategory.

            TESTS::

                sage: TestSuite(W).run()              # optional - gap3
                sage: TestSuite(ComplexReflectionGroups().Finite().WellGenerated()).run()   # optional - gap3
                sage: CoxeterGroups().Finite().WellGenerated.__module__
                'sage.categories.finite_complex_reflection_groups'

            We check that the axioms are properly ordered in
            ``sage.categories.category_with_axiom.axioms`` and yield
            desired output (well generated does not appear)::

                sage: CoxeterGroups().Finite()
                Category of finite coxeter groups
            """
            return self._with_axiom('WellGenerated')

    class ParentMethods:
        @abstract_method(optional=True)
        def degrees(self):
            r"""
            Return the degrees of ``self``.

            OUTPUT: a tuple of Sage integers

            EXAMPLES::

                sage: W = ColoredPermutations(1,4)
                sage: W.degrees()
                (2, 3, 4)

                sage: W = ColoredPermutations(3,3)
                sage: W.degrees()
                (3, 6, 9)

                sage: W = ReflectionGroup(31)              # optional - gap3
                sage: W.degrees()                          # optional - gap3
                (8, 12, 20, 24)
            """

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

            OUTPUT: a tuple of Sage integers

            EXAMPLES::

                sage: W = ColoredPermutations(1,4)
                sage: W.codegrees()
                (2, 1, 0)

                sage: W = ColoredPermutations(3,3)
                sage: W.codegrees()
                (6, 3, 0)

                sage: W = ReflectionGroup(31)              # optional - gap3
                sage: W.codegrees()                        # optional - gap3
                (28, 16, 12, 0)
            """

        def _test_degrees(self, **options):
            """
            Test the method :meth:`degrees`.

            INPUT:

            - ``options`` -- any keyword arguments accepted by :meth:`_tester`

            EXAMPLES::

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: W = ComplexReflectionGroups().Finite().example(); W   # optional - gap3
                Reducible real reflection group of rank 4 and type A2 x B2
                sage: W._test_degrees()                    # optional - gap3

                sage: W = SymmetricGroup(5)
                sage: W._test_degrees()

            We now break the implementation of W.degrees and check that this is caught::

                sage: W.degrees = lambda: (1/1,5)
                sage: W._test_degrees()
                Traceback (most recent call last):
                ...
                AssertionError: the degrees should be integers

                sage: W.degrees = lambda: (1,2,3)
                sage: W._test_degrees()
                Traceback (most recent call last):
                ...
                AssertionError: the degrees should be larger than 2

            We restore W to its normal state::

                sage: del W.degrees
                sage: W._test_degrees()

            See the documentation for :class:`TestSuite` for more information.
            """
            from sage.structure.element import parent
            from sage.rings.integer_ring import ZZ

            tester = self._tester(**options)
            degrees = self.degrees()
            tester.assertIsInstance(degrees, tuple,
                                    "the degrees method should return a tuple")
            tester.assertTrue(all(parent(d) is ZZ for d in degrees),
                              "the degrees should be integers")
            tester.assertTrue(all(d >= 2 for d in degrees),
                              "the degrees should be larger than 2")
            tester.assertEqual(len(degrees), self.rank(),
                               "the number of degrees should coincide with the rank")
            tester.assertEqual(sum(d - 1 for d in degrees), self.number_of_reflections(),
                               "the sum of the degrees should be consistent with the number of reflections")

        def _test_codegrees(self, **options):
            """
            Test the method :meth:`degrees`.

            INPUT:

            - ``options`` -- any keyword arguments accepted by :meth:`_tester`

            EXAMPLES::

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: W = ComplexReflectionGroups().Finite().example(); W   # optional - gap3
                Reducible real reflection group of rank 4 and type A2 x B2
                sage: W._test_codegrees()                  # optional - gap3

                sage: W = SymmetricGroup(5)
                sage: W._test_codegrees()

            We now break the implementation of W.degrees and check that this is caught::

                sage: W.codegrees = lambda: (1/1,5)
                sage: W._test_codegrees()
                Traceback (most recent call last):
                ...
                AssertionError: the codegrees should be integers

                sage: W.codegrees = lambda: (2,1,-1)
                sage: W._test_codegrees()
                Traceback (most recent call last):
                ...
                AssertionError: the codegrees should be nonnegative

            We restore W to its normal state::

                sage: del W.codegrees
                sage: W._test_codegrees()

            See the documentation for :class:`TestSuite` for more information.
            """
            from sage.structure.element import parent
            from sage.rings.integer_ring import ZZ

            tester = self._tester(**options)
            codegrees = self.codegrees()
            tester.assertIsInstance(codegrees, tuple,
                                    "the codegrees method should return a tuple")
            tester.assertTrue(all(parent(d) is ZZ for d in codegrees),
                              "the codegrees should be integers")
            tester.assertTrue(all(d >= 0 for d in codegrees),
                              "the codegrees should be nonnegative")
            tester.assertEqual(len(codegrees), self.rank(),
                               "the number of codegrees should coincide with the rank")
            tester.assertEqual(sum(d + 1 for d in codegrees),
                               self.number_of_reflection_hyperplanes(),
                               "the sum of the codegrees should be consistent with the number of reflection hyperplanes")

        @cached_method
        def number_of_reflection_hyperplanes(self):
            r"""
            Return the number of reflection hyperplanes of ``self``.

            This is also the number of distinguished reflections.  For
            real groups, this coincides with the number of
            reflections.

            This implementation uses that it is given by the sum of
            the codegrees of ``self`` plus its rank.

            .. SEEALSO:: :meth:`number_of_reflections`

            EXAMPLES::

                sage: W = ColoredPermutations(1,3)
                sage: W.number_of_reflection_hyperplanes()
                3
                sage: W = ColoredPermutations(2,3)
                sage: W.number_of_reflection_hyperplanes()
                9
                sage: W = ColoredPermutations(4,3)
                sage: W.number_of_reflection_hyperplanes()
                15
                sage: W = ReflectionGroup((4,2,3))          # optional - gap3
                sage: W.number_of_reflection_hyperplanes()  # optional - gap3
                15
            """
            from sage.rings.integer_ring import ZZ
            return ZZ.sum(codeg + 1 for codeg in self.codegrees())

        @cached_method
        def number_of_reflections(self):
            r"""
            Return the number of reflections of ``self``.

            For real groups, this coincides with the number of
            reflection hyperplanes.

            This implementation uses that it is given by the sum of
            the degrees of ``self`` minus its rank.

            .. SEEALSO:: :meth:`number_of_reflection_hyperplanes`

            EXAMPLES::

                sage: [SymmetricGroup(i).number_of_reflections() for i in range(int(8))]
                [0, 0, 1, 3, 6, 10, 15, 21]

                sage: W = ColoredPermutations(1,3)
                sage: W.number_of_reflections()
                3
                sage: W = ColoredPermutations(2,3)
                sage: W.number_of_reflections()
                9
                sage: W = ColoredPermutations(4,3)
                sage: W.number_of_reflections()
                21
                sage: W = ReflectionGroup((4,2,3))         # optional - gap3
                sage: W.number_of_reflections()            # optional - gap3
                15
            """
            from sage.rings.integer_ring import ZZ
            return ZZ.sum(deg - 1 for deg in self.degrees())

        @cached_method
        def rank(self):
            r"""
            Return the rank of ``self``.

            The rank of ``self`` is the dimension of the smallest
            faithfull reflection representation of ``self``.

            This default implementation uses that the rank is the
            number of :meth:`degrees`.

            .. SEEALSO:: :meth:`ComplexReflectionGroups.rank`

            EXAMPLES::

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

        @cached_method
        def cardinality(self):
            r"""
            Return the cardinality of ``self``.

            It is given by the product of the degrees of ``self``.

            EXAMPLES::

                sage: W = ColoredPermutations(1,3)
                sage: W.cardinality()
                6
                sage: W = ColoredPermutations(2,3)
                sage: W.cardinality()
                48
                sage: W = ColoredPermutations(4,3)
                sage: W.cardinality()
                384
                sage: W = ReflectionGroup((4,2,3))         # optional - gap3
                sage: W.cardinality()                      # optional - gap3
                192
            """
            from sage.rings.integer_ring import ZZ
            return ZZ.prod(self.degrees())

        def is_well_generated(self):
            r"""
            Return whether ``self`` is well-generated.

            A finite complex reflection group is *well generated* if
            the number of its simple reflections coincides with its rank.

            .. SEEALSO:: :meth:`ComplexReflectionGroups.Finite.WellGenerated`

            .. NOTE::

                - All finite real reflection groups are well generated.
                - The complex reflection groups of type `G(r,1,n)` and
                  of type `G(r,r,n)` are well generated.
                - The complex reflection groups of type `G(r,p,n)`
                  with `1 < p < r` are *not* well generated.

                - The direct product of two well generated finite
                  complex reflection group is still well generated.

            EXAMPLES::

                sage: W = ColoredPermutations(1,3)
                sage: W.is_well_generated()
                True

                sage: W = ColoredPermutations(4,3)
                sage: W.is_well_generated()
                True

                sage: W = ReflectionGroup((4,2,3))         # optional - gap3
                sage: W.is_well_generated()                # optional - gap3
                False

                sage: W = ReflectionGroup((4,4,3))         # optional - gap3
                sage: W.is_well_generated()                # optional - gap3
                True
            """
            return self.number_of_simple_reflections() == self.rank()

        def is_real(self):
            r"""
            Return whether ``self`` is real.

            A complex reflection group is *real* if it is isomorphic
            to a reflection group in `GL(V)` over a real vector space `V`.
            Equivalently its character table has real entries.

            This implementation uses the following statement: an
            irreducible complex reflection group is real if and only
            if `2` is a degree of ``self`` with multiplicity one.
            Hence, in general we just need to compare the number of
            occurrences of `2` as degree of ``self`` and the number of
            irreducible components.

            EXAMPLES::

                sage: W = ColoredPermutations(1,3)
                sage: W.is_real()
                True

                sage: W = ColoredPermutations(4,3)
                sage: W.is_real()
                False

            .. TODO::

                 Add an example of non real finite complex reflection
                 group that is generated by order 2 reflections.
            """
            return self.degrees().count(2) == self.number_of_irreducible_components()

        @cached_method
        def base_change_matrix(self):
            r"""
            Return the base change from the standard basis of the vector
            space of ``self`` to the basis given by the independent roots of
            ``self``.

            .. TODO::

                For non-well-generated groups there is a conflict with
                construction of the matrix for an element.

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,3))                          # optional - gap3
                sage: W.base_change_matrix()                                # optional - gap3
                [1 0]
                [0 1]

                sage: W = ReflectionGroup(23)                               # optional - gap3
                sage: W.base_change_matrix()                                # optional - gap3
                [1 0 0]
                [0 1 0]
                [0 0 1]

                sage: W = ReflectionGroup((3,1,2))                          # optional - gap3
                sage: W.base_change_matrix()                                # optional - gap3
                [1 0]
                [1 1]

                sage: W = ReflectionGroup((4,2,2))                          # optional - gap3
                sage: W.base_change_matrix()                                # optional - gap3
                [   1    0]
                [E(4)    1]
            """
            from sage.matrix.all import Matrix
            return Matrix(list(self.independent_roots())).inverse()

    class ElementMethods:

        @abstract_method(optional=True)
        def to_matrix(self):
            r"""
            Return the matrix presentation of ``self`` acting on a
            vector space `V`.

            EXAMPLES::

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

                sage: W = ColoredPermutations(1,3)
                sage: [t.to_matrix() for t in W]
                [
                [1 0 0]  [1 0 0]  [0 1 0]  [0 0 1]  [0 1 0]  [0 0 1]
                [0 1 0]  [0 0 1]  [1 0 0]  [1 0 0]  [0 0 1]  [0 1 0]
                [0 0 1], [0 1 0], [0 0 1], [0 1 0], [1 0 0], [1 0 0]
                ]

            A different representation is given by the
            colored permutations::

                sage: W = ColoredPermutations(3, 1)
                sage: [t.to_matrix() for t in W]
                [[1], [zeta3], [-zeta3 - 1]]
            """

        def _matrix_(self):
            """
            Return ``self`` as a matrix.

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,3))         # optional - gap3
                sage: [matrix(t) for t in W]               # optional - gap3
                [
                [1 0]  [ 1  1]  [-1  0]  [-1 -1]  [ 0  1]  [ 0 -1]
                [0 1], [ 0 -1], [ 1  1], [ 1  0], [-1 -1], [-1  0]
                ]
            """
            return self.to_matrix()

        def character_value(self):
            r"""
            Return the value at ``self`` of the character of the
            reflection representation given by :meth:`to_matrix`.

            EXAMPLES::

                sage: W = ColoredPermutations(1,3); W
                1-colored permutations of size 3
                sage: [t.character_value() for t in W]
                [3, 1, 1, 0, 0, 1]

            Note that this could be a different (faithful)
            representation than that given by the corresponding root
            system::

                sage: W = ReflectionGroup((1,1,3)); W      # optional - gap3
                Irreducible real reflection group of rank 2 and type A2
                sage: [t.character_value() for t in W]     # optional - gap3
                [2, 0, 0, -1, -1, 0]

                sage: W = ColoredPermutations(2,2); W
                2-colored permutations of size 2
                sage: [t.character_value() for t in W]
                [2, 0, 0, -2, 0, 0, 0, 0]

                sage: W = ColoredPermutations(3,1); W
                3-colored permutations of size 1
                sage: [t.character_value() for t in W]
                [1, zeta3, -zeta3 - 1]
            """
            return self.to_matrix().trace()

        # @cached_in_parent_method
        def reflection_length(self, in_unitary_group=False):
            r"""
            Return the reflection length of ``self``.

            This is the minimal numbers of reflections needed to
            obtain ``self``.

            INPUT:

            - ``in_unitary_group`` -- (default: ``False``) if ``True``,
              the reflection length is computed in the unitary group
              which is the dimension of the move space of ``self``

            EXAMPLES::

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

                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((2,2,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 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]
            """
            W = self.parent()
            if in_unitary_group or W.is_real():
                from sage.matrix.special import identity_matrix
                I = identity_matrix(self.parent().rank())
                return W.rank() - (self.canonical_matrix() - I).right_nullity()
            else:
                return len(self.reduced_word_in_reflections())

    class Irreducible(CategoryWithAxiom):

        def example(self):
            r"""
            Return an example of an irreducible complex reflection group.

            EXAMPLES::

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: ComplexReflectionGroups().Finite().Irreducible().example()    # optional - gap3
                Irreducible complex reflection group of rank 3 and type G(4,2,3)
            """
            from sage.combinat.root_system.reflection_group_real import ReflectionGroup
            return ReflectionGroup((4,2,3))

        class ParentMethods:
            def coxeter_number(self):
                r"""
                Return the Coxeter number of an irreducible
                reflection group.

                This is defined as `\frac{N + N^*}{n}` where
                `N` is the number of reflections, `N^*` is the
                number of reflection hyperplanes, and `n` is the
                rank of ``self``.

                EXAMPLES::

                    sage: W = ReflectionGroup(31)          # optional - gap3
                    sage: W.coxeter_number()               # optional - gap3
                    30
                """
                return (self.number_of_reflection_hyperplanes()
                        + self.number_of_reflections()) // self.rank()

            def absolute_order_ideal(self, gens=None,
                                     in_unitary_group=True,
                                     return_lengths=False):
                r"""
                Return all elements in ``self`` below given elements in the
                absolute order of ``self``.

                This order is defined by

                .. MATH::

                    \omega \leq_R \tau \Leftrightarrow \ell_R(\omega) +
                    \ell_R(\omega^{-1} \tau) = \ell_R(\tau),

                where `\ell_R` denotes the reflection length.

                This is, if ``in_unitary_group`` is ``False``, then

                .. MATH::

                    \ell_R(w) = \min\{ \ell: w = r_1\cdots r_\ell, r_i \in R \},

                and otherwise

                .. MATH::

                    \ell_R(w) = \dim\operatorname{im}(w - 1).

                .. NOTE::

                    If ``gens`` are not given, ``self`` is assumed to be
                    well-generated.

                INPUT:

                - ``gens`` -- (default: ``None``) if one or more elements
                  are given, the order ideal in the absolute order generated
                  by ``gens`` is returned.
                  Otherwise, the standard Coxeter element is used as unique
                  maximal element.

                - ``in_unitary_group`` (default:``True``) determines the
                  length function used to compute the order.
                  For real groups, both possible orders coincide, and for
                  complex non-real groups, the order in the unitary group
                  is much faster to compute.

                - ``return_lengths`` (default:``False``) whether or not
                  to also return the lengths of the elements.

                EXAMPLES::

                    sage: W = ReflectionGroup((1,1,3))                          # optional - gap3

                    sage: sorted( w.reduced_word() for w in W.absolute_order_ideal() )    # optional - gap3
                    [[], [1], [1, 2], [1, 2, 1], [2]]

                    sage: sorted( w.reduced_word() for w in W.absolute_order_ideal(W.from_reduced_word([2,1])) )  # optional - gap3
                    [[], [1], [1, 2, 1], [2], [2, 1]]

                    sage: sorted( w.reduced_word() for w in W.absolute_order_ideal(W.from_reduced_word([2])) )    # optional - gap3
                    [[], [2]]

                    sage: W = CoxeterGroup(['A', 3])
                    sage: len(list(W.absolute_order_ideal()))
                    14

                    sage: W = CoxeterGroup(['A', 2])
                    sage: for (w, l) in W.absolute_order_ideal(return_lengths=True):
                    ....:     print(w.reduced_word(), l)
                    [1, 2] 2
                    [1, 2, 1] 1
                    [2] 1
                    [1] 1
                    [] 0
                """
                if gens is None:
                    seeds = [(self.coxeter_element(), self.rank())]
                else:
                    if gens in self:
                        gens = [gens]
                    seeds = [(gen, gen.reflection_length(in_unitary_group=in_unitary_group)) for gen in gens]

                R = self.reflections()

                def succ(seed):
                    w, w_len = seed
                    w_len -= 1
                    resu = []
                    for t in R:
                        u = w * t
                        if u.reflection_length(in_unitary_group=in_unitary_group) == w_len:
                            resu.append((u, w_len))
                    return resu
                step = RecursivelyEnumeratedSet(seeds, succ,
                                                structure='graded',
                                                enumeration='breadth')
                if return_lengths:
                    return (x for x in step)
                else:
                    return (x[0] for x in step)

            def elements_below_coxeter_element(self, c=None):
                r"""
                Deprecated method.

                Superseded by :meth:`absolute_order_ideal`

                TESTS::

                    sage: W = CoxeterGroup(['A', 3])
                    sage: len(list(W.elements_below_coxeter_element()))
                    doctest:...: DeprecationWarning: The method elements_below_coxeter_element is deprecated. Please use absolute_order_ideal instead.
                    See https://trac.sagemath.org/27924 for details.
                    14
                """
                from sage.misc.superseded import deprecation
                deprecation(27924, "The method elements_below_coxeter_element is deprecated. Please use absolute_order_ideal instead.")
                return self.absolute_order_ideal(gens=c)

            # TODO: have a cached and an uncached version
            @cached_method
            def noncrossing_partition_lattice(self, c=None, L=None,
                                              in_unitary_group=True):
                r"""
                Return the interval `[1,c]` in the absolute order of
                ``self`` as a finite lattice.

                .. SEEALSO:: :meth:`absolute_order_ideal`

                INPUT:

                - ``c`` -- (default: ``None``) if an element ``c`` in ``self`` is
                  given, it is used as the maximal element in the interval

                - ``L`` -- (default: ``None``) if a subset ``L`` (must be hashable!)
                  of ``self`` is given, it is used as the underlying set (only
                  cover relations are checked).

                - ``in_unitary_group`` -- (default: ``False``) if ``False``, the
                  relation is given by `\sigma \leq \tau` if
                  `l_R(\sigma) + l_R(\sigma^{-1}\tau) = l_R(\tau)`;
                  if ``True``, the relation is given by `\sigma \leq \tau` if
                  `\dim(\mathrm{Fix}(\sigma)) + \dim(\mathrm{Fix}(\sigma^{-1}\tau))
                  = \dim(\mathrm{Fix}(\tau))`

                .. NOTE::

                    If ``L`` is given, the parameter ``c`` is ignored.

                EXAMPLES::

                    sage: W = SymmetricGroup(4)
                    sage: W.noncrossing_partition_lattice()
                    Finite lattice containing 14 elements

                    sage: W = WeylGroup(['G', 2])
                    sage: W.noncrossing_partition_lattice()
                    Finite lattice containing 8 elements

                    sage: W = ReflectionGroup((1,1,3))                          # optional - gap3

                    sage: sorted( w.reduced_word() for w in W.noncrossing_partition_lattice() ) # optional - gap3
                    [[], [1], [1, 2], [1, 2, 1], [2]]

                    sage: sorted( w.reduced_word() for w in W.noncrossing_partition_lattice(W.from_reduced_word([2,1])) )   # optional - gap3
                    [[], [1], [1, 2, 1], [2], [2, 1]]

                    sage: sorted( w.reduced_word() for w in W.noncrossing_partition_lattice(W.from_reduced_word([2])) ) # optional - gap3
                    [[], [2]]
                """
                from sage.combinat.posets.all import Poset, LatticePoset

                R = self.reflections()
                if L is None:
                    L = list(self.absolute_order_ideal(gens=c,
                                                       in_unitary_group=in_unitary_group,
                                                       return_lengths=True))
                else:
                    L = [(pi, pi.reflection_length()) for pi in L]
                rels = []
                ref_lens = {pi:l for (pi, l) in L}
                for (pi, l) in L:
                    for t in R:
                        tau = pi * t
                        if tau in ref_lens and l+1 == ref_lens[tau]:
                            rels.append((pi, tau))

                P = Poset(([], rels), cover_relations=True, facade=True)
                if P.is_lattice():
                    P = LatticePoset(P)
                return P

            def generalized_noncrossing_partitions(self, m, c=None, positive=False):
                r"""
                Return the set of all chains of length ``m`` in the
                noncrossing partition lattice of ``self``, see
                :meth:`noncrossing_partition_lattice`.

                .. NOTE::

                    ``self`` is assumed to be well-generated.

                INPUT:

                - ``c`` -- (default: ``None``) if an element ``c`` in ``self``
                  is given, it is used as the maximal element in the interval

                - ``positive`` -- (default: ``False``) if ``True``, only those
                  generalized noncrossing partitions of full support are returned

                EXAMPLES::

                    sage: W = ReflectionGroup((1,1,3))                          # optional - gap3

                    sage: sorted([w.reduced_word() for w in chain]              # optional - gap3
                    ....:        for chain in W.generalized_noncrossing_partitions(2))  # optional - gap3
                    [[[], [], [1, 2]],
                     [[], [1], [2]],
                     [[], [1, 2], []],
                     [[], [1, 2, 1], [1]],
                     [[], [2], [1, 2, 1]],
                     [[1], [], [2]],
                     [[1], [2], []],
                     [[1, 2], [], []],
                     [[1, 2, 1], [], [1]],
                     [[1, 2, 1], [1], []],
                     [[2], [], [1, 2, 1]],
                     [[2], [1, 2, 1], []]]

                    sage: sorted([w.reduced_word() for w in chain]              # optional - gap3
                    ....:        for chain in W.generalized_noncrossing_partitions(2, positive=True))   # optional - gap3
                    [[[], [1, 2], []],
                     [[], [1, 2, 1], [1]],
                     [[1], [2], []],
                     [[1, 2], [], []],
                     [[1, 2, 1], [], [1]],
                     [[1, 2, 1], [1], []],
                     [[2], [1, 2, 1], []]]
                """
                from sage.combinat.combination import Combinations
                NC = self.noncrossing_partition_lattice(c=c)
                one = self.one()
                if c is None:
                    c = self.coxeter_element()
                chains = NC.chains()
                NCm = set()
                iter = chains.breadth_first_search_iterator()
                next(iter)
                chain = next(iter)
                while len(chain) <= m:
                    chain.append(c)
                    for i in range(len(chain) - 1, 0, -1):
                        chain[i] = chain[i - 1]**-1 * chain[i]
                    k = m + 1 - len(chain)
                    for positions in Combinations(range(m + 1), k):
                        ncm = []
                        for l in range(m + 1):
                            if l in positions:
                                ncm.append(one)
                            else:
                                l_prime = l - len([i for i in positions if i <= l])
                                ncm.append(chain[l_prime])
                        if not positive or prod(ncm[:-1]).has_full_support():
                            NCm.add(tuple(ncm))
                    try:
                        chain = next(iter)
                    except StopIteration:
                        chain = list(range(m + 1))
                return NCm

            # TODO: have a cached and an uncached version
            def absolute_poset(self, in_unitary_group=False):
                r"""
                Return the poset induced by the absolute order of ``self``
                as a finite lattice.

                INPUT:

                - ``in_unitary_group`` -- (default: ``False``) if ``False``,
                  the relation is given by ``\sigma \leq \tau`` if
                  `l_R(\sigma) + l_R(\sigma^{-1}\tau) = l_R(\tau)`
                  If ``True``, the relation is given by `\sigma \leq \tau` if
                  `\dim(\mathrm{Fix}(\sigma)) + \dim(\mathrm{Fix}(\sigma^{-1}\tau))
                  = \dim(\mathrm{Fix}(\tau))`

                .. SEEALSO:: :meth:`noncrossing_partition_lattice`

                EXAMPLES::

                    sage: P = ReflectionGroup((1,1,3)).absolute_poset(); P      # optional - gap3
                    Finite poset containing 6 elements

                    sage: sorted(w.reduced_word() for w in P)                   # optional - gap3
                    [[], [1], [1, 2], [1, 2, 1], [2], [2, 1]]

                    sage: W = ReflectionGroup(4); W                             # optional - gap3
                    Irreducible complex reflection group of rank 2 and type ST4
                    sage: W.absolute_poset()                                    # optional - gap3
                    Finite poset containing 24 elements

                TESTS::

                    sage: W1 = CoxeterGroup(['A',2])
                    sage: W2 = WeylGroup(['A',2])
                    sage: W3 = SymmetricGroup(3)
                    sage: W1.absolute_poset()
                    Finite poset containing 6 elements
                    sage: W2.absolute_poset()
                    Finite poset containing 6 elements
                    sage: W3.absolute_poset()
                    Finite poset containing 6 elements
                """
                return self.noncrossing_partition_lattice(L=tuple(self), in_unitary_group=in_unitary_group)

    class WellGenerated(CategoryWithAxiom):

        def example(self):
            r"""
            Return an example of a well-generated complex reflection group.

            EXAMPLES::

                sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                sage: ComplexReflectionGroups().Finite().WellGenerated().example()  # optional - gap3
                Reducible complex reflection group of rank 4 and type A2 x G(3,1,2)
            """
            from sage.combinat.root_system.reflection_group_real import ReflectionGroup
            return ReflectionGroup((1,1,3), (3,1,2))

        class ParentMethods:
            def _test_well_generated(self, **options):
                """
                Check if ``self`` is well-generated.

                EXAMPLES::

                    sage: W = ReflectionGroup((3,1,2))     # optional - gap3
                    sage: W._test_well_generated()         # optional - gap3
                """
                tester = self._tester(**options)
                tester.assertEqual(self.number_of_simple_reflections(), self.rank())

            def is_well_generated(self):
                r"""
                Return ``True`` as ``self`` is well-generated.

                EXAMPLES::

                    sage: W = ReflectionGroup((3,1,2))     # optional - gap3
                    sage: W.is_well_generated()            # optional - gap3
                    True
                """
                return True

            coxeter_element = CoxeterGroups.ParentMethods.coxeter_element
            standard_coxeter_elements = CoxeterGroups.ParentMethods.standard_coxeter_elements

            @cached_method
            def coxeter_elements(self):
                r"""
                Return the (unique) conjugacy class in ``self`` containing all
                Coxeter elements.

                A Coxeter element is an element that has an eigenvalue
                `e^{2\pi i/h}` where `h` is the Coxeter number.

                In case of finite Coxeter groups, these are exactly the
                elements that are conjugate to one (or, equivalently,
                all) standard Coxeter element, this is, to an element
                that is the product of the simple generators in some
                order.

                .. SEEALSO:: :meth:`~sage.categories.coxeter_groups.standard_coxeter_elements`

                EXAMPLES::

                    sage: W = ReflectionGroup((1,1,3))     # optional - gap3
                    sage: sorted(c.reduced_word() for c in W.coxeter_elements())    # optional - gap3
                    [[1, 2], [2, 1]]

                    sage: W = ReflectionGroup((1,1,4))     # optional - gap3
                    sage: sorted(c.reduced_word() for c in W.coxeter_elements())    # optional - gap3
                    [[1, 2, 1, 3, 2], [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 1, 3, 2, 1], [3, 2, 1]]
                """
                return self.coxeter_element().conjugacy_class()

        class Irreducible(CategoryWithAxiom):
            r"""
            The category of finite irreducible well-generated
            finite complex reflection groups.
            """
            def example(self):
                r"""
                Return an example of an irreducible well-generated
                complex reflection group.

                EXAMPLES::

                    sage: from sage.categories.complex_reflection_groups import ComplexReflectionGroups
                    sage: ComplexReflectionGroups().Finite().WellGenerated().Irreducible().example()
                    4-colored permutations of size 3
                """
                from sage.combinat.colored_permutations import ColoredPermutations
                return ColoredPermutations(4, 3)

            class ParentMethods:
                def coxeter_number(self):
                    r"""
                    Return the Coxeter number of a well-generated,
                    irreducible reflection group. This is defined to be
                    the order of a regular element in ``self``, and is
                    equal to the highest degree of ``self``.

                    .. SEEALSO:: :meth:`ComplexReflectionGroups.Finite.Irreducible`

                    .. NOTE::

                        This method overwrites the more general
                        method for complex reflection groups since
                        the expression given here is quicker to
                        compute.

                    EXAMPLES::

                        sage: W = ColoredPermutations(1,3)
                        sage: W.coxeter_number()
                        3

                        sage: W = ColoredPermutations(4,3)
                        sage: W.coxeter_number()
                        12

                        sage: W = ReflectionGroup((4,4,3))  # optional - gap3
                        sage: W.coxeter_number()            # optional - gap3
                        8
                    """
                    return max(self.degrees())

                def number_of_reflections_of_full_support(self):
                    r"""
                    Return the number of reflections with full
                    support.

                    EXAMPLES::

                        sage: W = Permutations(4)
                        sage: W.number_of_reflections_of_full_support()
                        1

                        sage: W = ColoredPermutations(1,4)
                        sage: W.number_of_reflections_of_full_support()
                        1

                        sage: W = CoxeterGroup("B3")
                        sage: W.number_of_reflections_of_full_support()
                        3

                        sage: W = ColoredPermutations(3,3)
                        sage: W.number_of_reflections_of_full_support()
                        3
                    """
                    n = self.rank()
                    h = self.coxeter_number()
                    l = self.cardinality()
                    return (n * h * prod(d for d in self.codegrees() if d != 0)) // l

                @cached_method
                def rational_catalan_number(self, p, polynomial=False):
                    r"""
                    Return the ``p``-th rational Catalan number
                    associated to ``self``.

                    It is defined by

                    .. MATH::

                        \prod_{i = 1}^n \frac{p + (p(d_i-1)) \mod h)}{d_i},

                    where `d_1, \ldots, d_n` are the degrees and
                    `h` is the Coxeter number. See [STW2016]_
                    for this formula.

                    INPUT:

                    - ``polynomial`` -- optional boolean (default ``False``)
                      if ``True``, return instead the `q`-analogue as a
                      polynomial in `q`

                    EXAMPLES::

                        sage: W = ColoredPermutations(1,3)
                        sage: [W.rational_catalan_number(p) for p in [5,7,8]]
                        [7, 12, 15]

                        sage: W = ColoredPermutations(2,2)
                        sage: [W.rational_catalan_number(p) for p in [7,9,11]]
                        [10, 15, 21]

                    TESTS::

                        sage: W = ColoredPermutations(1,4)
                        sage: W.rational_catalan_number(3, polynomial=True)
                        q^6 + q^4 + q^3 + q^2 + 1
                    """
                    from sage.arith.all import gcd
                    from sage.combinat.q_analogues import q_int

                    h = self.coxeter_number()
                    if not gcd(h,p) == 1:
                        raise ValueError("parameter p = %s is not coprime to the Coxeter number %s" % (p, h))

                    if polynomial:
                        f = q_int
                    else:

                        def f(n):
                            return n

                    num = prod(f(p + (p * (deg - 1)) % h)
                               for deg in self.degrees())
                    den = prod(f(deg) for deg in self.degrees())
                    return num // den

                def fuss_catalan_number(self, m, positive=False,
                                        polynomial=False):
                    r"""
                    Return the ``m``-th Fuss-Catalan number
                    associated to ``self``.

                    This is defined by

                    .. MATH::

                        \prod_{i = 1}^n \frac{d_i + mh}{d_i},

                    where `d_1, \ldots, d_n` are the degrees and
                    `h` is the Coxeter number.

                    INPUT:

                    - ``positive`` -- optional boolean (default ``False``)
                      if ``True``, return instead the positive Fuss-Catalan
                      number
                    - ``polynomial`` -- optional boolean (default ``False``)
                      if ``True``, return instead the `q`-analogue as a
                      polynomial in `q`

                    See [Ar2006]_ for further information.

                    .. NOTE::

                        - For the symmetric group `S_n`, it reduces to the
                          Fuss-Catalan number `\frac{1}{mn+1}\binom{(m+1)n}{n}`.
                        - The Fuss-Catalan numbers for `G(r, 1, n)` all
                          coincide for `r > 1`.

                    EXAMPLES::

                        sage: W = ColoredPermutations(1,3)
                        sage: [W.fuss_catalan_number(i) for i in [1,2,3]]
                        [5, 12, 22]

                        sage: W = ColoredPermutations(1,4)
                        sage: [W.fuss_catalan_number(i) for i in [1,2,3]]
                        [14, 55, 140]

                        sage: W = ColoredPermutations(1,5)
                        sage: [W.fuss_catalan_number(i) for i in [1,2,3]]
                        [42, 273, 969]

                        sage: W = ColoredPermutations(2,2)
                        sage: [W.fuss_catalan_number(i) for i in [1,2,3]]
                        [6, 15, 28]

                        sage: W = ColoredPermutations(2,3)
                        sage: [W.fuss_catalan_number(i) for i in [1,2,3]]
                        [20, 84, 220]

                        sage: W = ColoredPermutations(2,4)
                        sage: [W.fuss_catalan_number(i) for i in [1,2,3]]
                        [70, 495, 1820]

                    TESTS::

                        sage: W = ColoredPermutations(2,4)
                        sage: W.fuss_catalan_number(2,positive=True)
                        330
                        sage: W = ColoredPermutations(2,2)
                        sage: W.fuss_catalan_number(2,polynomial=True)
                        q^16 + q^14 + 2*q^12 + 2*q^10 + 3*q^8 + 2*q^6 +
                        2*q^4 + q^2 + 1
                    """
                    h = self.coxeter_number()
                    if positive:
                        p = m * h - 1
                    else:
                        p = m * h + 1

                    return self.rational_catalan_number(p, polynomial=polynomial)

                def catalan_number(self, positive=False, polynomial=False):
                    r"""
                    Return the Catalan number associated to ``self``.

                    It is defined by

                    .. MATH::

                        \prod_{i = 1}^n \frac{d_i + h}{d_i},

                    where `d_1, \ldots, d_n` are the degrees and where
                    `h` is the Coxeter number.
                    See [Ar2006]_ for further information.

                    INPUT:

                    - ``positive`` -- optional boolean (default ``False``)
                      if ``True``, return instead the positive Catalan
                      number
                    - ``polynomial`` -- optional boolean (default ``False``)
                      if ``True``, return instead the `q`-analogue as a
                      polynomial in `q`

                    .. NOTE::

                        - For the symmetric group `S_n`, it reduces to the
                          Catalan number `\frac{1}{n+1} \binom{2n}{n}`.
                        - The Catalan numbers for `G(r,1,n)` all coincide
                          for `r > 1`.

                    EXAMPLES::

                        sage: [ColoredPermutations(1,n).catalan_number() for n in [3,4,5]]
                        [5, 14, 42]

                        sage: [ColoredPermutations(2,n).catalan_number() for n in [3,4,5]]
                        [20, 70, 252]

                        sage: [ReflectionGroup((2,2,n)).catalan_number() for n in [3,4,5]]  # optional - gap3
                        [14, 50, 182]

                    TESTS::

                        sage: W = ColoredPermutations(3,6)
                        sage: W.catalan_number(positive=True)
                        462
                        sage: W = ColoredPermutations(2,2)
                        sage: W.catalan_number(polynomial=True)
                        q^8 + q^6 + 2*q^4 + q^2 + 1
                    """
                    return self.fuss_catalan_number(1, positive=positive,
                                                    polynomial=polynomial)
