r"""
Finite semigroups
"""
#*****************************************************************************
#  Copyright (C) 2008      Teresa Gomez-Diaz (CNRS) <Teresa.Gomez-Diaz@univ-mlv.fr>
#                2008-2009 Florent Hivert <florent.hivert at univ-rouen.fr>
#                2008-2014 Nicolas M. Thiery <nthiery at users.sf.net>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#                  http://www.gnu.org/licenses/
#******************************************************************************

from sage.misc.cachefunc import cached_method
from sage.misc.misc import attrcall
from sage.categories.category_with_axiom import CategoryWithAxiom
from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets

class FiniteSemigroups(CategoryWithAxiom):
    r"""
    The category of finite (multiplicative) semigroups.

    A :class:`semigroup <Semigroups>` is a :class:`finite sets <FiniteSets>`
    endowed with an associative binary operation `*`.

    .. NOTE::

        A finite semigroup in Sage is currently automatically endowed
        with an :class:`enumerated set <EnumeratedSets>` structure, with
        the default enumeration being obtained by iteratively
        multiplying the semigroup generators (see
        :meth:`FiniteSemigroups.super_categories` and
        :meth:`FiniteSemigroups.ParentMethods.__iter__`). Therefore a
        finite semigroup must at this point either implement an
        enumeration or provide semigroup generators.

        .. TODO:: make this optional

    EXAMPLES::

        sage: C = FiniteSemigroups(); C
        Category of finite semigroups
        sage: C.super_categories()
        [Category of semigroups, Category of finite enumerated sets]
        sage: sorted(C.axioms())
        ['Associative', 'Finite']
        sage: C.example()
        An example of a finite semigroup: the left regular band generated by ('a', 'b', 'c', 'd')

    TESTS::

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

    @cached_method
    def extra_super_categories(self):
        r"""
        Returns a list of the (immediate) super categories of ``self``.

        EXAMPLES::

            sage: FiniteSemigroups().extra_super_categories()
            [Category of finite enumerated sets]

        """
        return [FiniteEnumeratedSets()]

    class ParentMethods:
        r"""
        Collection of methods shared by all finite semigroups.
        """
        def some_elements(self):
            r"""
            Returns an iterable containing some elements of the semigroup.

            .. note:

               Since the semigroup is finite, this method returns self.

            EXAMPLES::

                sage: S = FiniteSemigroups().example(alphabet=('x','y'))
                sage: S.some_elements()
                An example of a finite semigroup: the left regular band generated by ('x', 'y')
                sage: list(S)
                ['y', 'x', 'xy', 'yx']

            """
            return self

        def idempotents(self):
            r"""
            Returns the idempotents of the semigroup

            EXAMPLES::

                sage: S = FiniteSemigroups().example(alphabet=('x','y'))
                sage: sorted(S.idempotents())
                ['x', 'xy', 'y', 'yx']
            """
            return [x for x in self if x.is_idempotent()]

        def succ_generators(self, side="twosided"):
            r"""
            Returns the the successor function of the ``side``-sided Cayley
            graph of ``self``.

            This is a function that maps an element of ``self`` to all the
            products of ``x`` by a generator of this semigroup, where the
            product is taken on the left, right or both sides.

            INPUT::

             - ``side``: "left", "right", or "twosided"

            FIXME: find a better name for this method
            FIXME: should we return a set? a family?

            EXAMPLES::

                sage: S = FiniteSemigroups().example()
                sage: S.succ_generators("left" )(S('ca'))
                ('ac', 'bca', 'ca', 'dca')
                sage: S.succ_generators("right")(S('ca'))
                ('ca', 'cab', 'ca', 'cad')
                sage: S.succ_generators("twosided" )(S('ca'))
                ('ac', 'bca', 'ca', 'dca', 'ca', 'cab', 'ca', 'cad')

            """
            left  = (side == "left"  or side == "twosided")
            right = (side == "right" or side == "twosided")
            generators = self.semigroup_generators()
            return lambda x: (tuple(g * x for g in generators) if left  else ()) + (tuple(x * g for g in generators) if right else ())

        def __iter__(self):
            """
            Returns an iterator over the elements of ``self``.

            EXAMPLES::

                sage: S = FiniteSemigroups().example(alphabet=('x','y'))
                sage: it = S.__iter__()
                sage: list(it)
                ['y', 'x', 'xy', 'yx']

            """
            from sage.combinat.backtrack import TransitiveIdeal
            return TransitiveIdeal(self.succ_generators(side = "right"), self.semigroup_generators()).__iter__()

        def ideal(self, gens, side="twosided"):
            r"""
            Returns the ``side``-sided ideal generated by ``gens``.

            INPUT::

             - ``gens``: a list (or iterable) of elements of ``self``
             - ``side``: [default: "twosided"] "left", "right" or "twosided"

            EXAMPLES::

                sage: S = FiniteSemigroups().example()
                sage: list(S.ideal([S('cab')], side="left"))
                ['cab', 'dcab', 'adcb', 'acb', 'bdca', 'bca', 'abdc',
                'cadb', 'acdb', 'bacd', 'abcd', 'cbad', 'abc', 'acbd',
                'dbac', 'dabc', 'cbda', 'bcad', 'cabd', 'dcba',
                'bdac', 'cba', 'badc', 'bac', 'cdab', 'dacb', 'dbca',
                'cdba', 'adbc', 'bcda']
                sage: list(S.ideal([S('cab')], side="right"))
                ['cab', 'cabd']
                sage: list(S.ideal([S('cab')], side="twosided"))
                ['cab', 'dcab', 'acb', 'adcb', 'acbd', 'bdca', 'bca',
                'cabd', 'abdc', 'cadb', 'acdb', 'bacd', 'abcd', 'cbad',
                'abc', 'dbac', 'dabc', 'cbda', 'bcad', 'dcba', 'bdac',
                'cba', 'cdab', 'bac', 'badc', 'dacb', 'dbca', 'cdba',
                'adbc', 'bcda']
                sage: list(S.ideal([S('cab')]))
                ['cab', 'dcab', 'acb', 'adcb', 'acbd', 'bdca', 'bca',
                'cabd', 'abdc', 'cadb', 'acdb', 'bacd', 'abcd', 'cbad',
                'abc', 'dbac', 'dabc', 'cbda', 'bcad', 'dcba', 'bdac',
                'cba', 'cdab', 'bac', 'badc', 'dacb', 'dbca', 'cdba',
                'adbc', 'bcda']

            """
            from sage.combinat.backtrack import TransitiveIdeal
            return TransitiveIdeal(self.succ_generators(side = side), gens)

        @cached_method
        def j_classes(self):
            r"""
            Returns the $J$-classes of the semigroup.

            Two elements $u$ and $v$ of a monoid are in the same $J$-class
            if $u$ divides $v$ and $v$ divides $u$.

            OUTPUT:

             All the $J$-classes of self, as a list of lists.

            EXAMPLES::

                sage: S = FiniteSemigroups().example(alphabet=('a','b', 'c'))
                sage: sorted(map(sorted, S.j_classes()))
                [['a'], ['ab', 'ba'], ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'], ['ac', 'ca'], ['b'], ['bc', 'cb'], ['c']]
            """
            return self.cayley_graph(side="twosided", simple=True).strongly_connected_components()

        @cached_method
        def j_classes_of_idempotents(self):
            r"""
            Returns all the idempotents of self, grouped by J-class.

            OUTPUT:

             a list of lists.

            EXAMPLES::

                sage: S = FiniteSemigroups().example(alphabet=('a','b', 'c'))
                sage: sorted(map(sorted, S.j_classes_of_idempotents()))
                [['a'], ['ab', 'ba'], ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'], ['ac', 'ca'], ['b'], ['bc', 'cb'], ['c']]
            """
            return [l for l in map(lambda cl: list(filter(attrcall('is_idempotent'), cl)),
                                   self.j_classes()) if len(l) > 0]

        @cached_method
        def j_transversal_of_idempotents(self):
            r"""
            Returns a list of one idempotent per regular J-class

            EXAMPLES::

                sage: S = FiniteSemigroups().example(alphabet=('a','b', 'c'))
                sage: sorted(S.j_transversal_of_idempotents())
                ['a', 'ab', 'ac', 'acb', 'b', 'c', 'cb']
            """
            def first_idempotent(l):
                for x in l:
                    if x.is_idempotent():
                        return x
                return None
            return [x for x in map(first_idempotent, self.j_classes()) if not x is None]

        # TODO: compute eJe, where J is the J-class of e
        # TODO: construct the action of self on it, as a permutation group
