r"""
Metric Spaces
"""
#*****************************************************************************
#  Copyright (C) 2015 Travis Scrimshaw <tscrim at ucdavis.edu>
#
#  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.categories.category import Category
from sage.categories.category_with_axiom import CategoryWithAxiom
from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory
from sage.categories.with_realizations import WithRealizationsCategory

class MetricSpacesCategory(RegressiveCovariantConstructionCategory):

    _functor_category = "Metric"

    @classmethod
    def default_super_categories(cls, category):
        """
        Return the default super categories of ``category.Metric()``.

        Mathematical meaning: if `A` is a metric space in the
        category `C`, then `A` is also a topological space.

        INPUT:

        - ``cls`` -- the class ``MetricSpaces``
        - ``category`` -- a category `Cat`

        OUTPUT:

        A (join) category

        In practice, this returns ``category.Metric()``, joined
        together with the result of the method
        :meth:`RegressiveCovariantConstructionCategory.default_super_categories()
        <sage.categories.covariant_functorial_construction.RegressiveCovariantConstructionCategory.default_super_categories>`
        (that is the join of ``category`` and ``cat.Metric()`` for
        each ``cat`` in the super categories of ``category``).

        EXAMPLES:

        Consider ``category=Groups()``. Then, a group `G` with a metric
        is simultaneously a topological group by itself, and a
        metric space::

            sage: Groups().Metric().super_categories()
            [Category of topological groups, Category of metric spaces]

        This resulted from the following call::

            sage: sage.categories.metric_spaces.MetricSpacesCategory.default_super_categories(Groups())
            Join of Category of topological groups and Category of metric spaces
        """
        return Category.join([category.Topological(),
                              super(MetricSpacesCategory, cls).default_super_categories(category)])

    # We currently don't have a use for this, but we probably will
    def _repr_object_names(self):
        """
        EXAMPLES::

            sage: Groups().Metric()  # indirect doctest
            Join of Category of topological groups and Category of metric spaces
        """
        return "metric {}".format(self.base_category()._repr_object_names())

class MetricSpaces(MetricSpacesCategory):
    r"""
    The category of metric spaces.

    A *metric* on a set `S` is a function `d : S \times S \to \RR`
    such that:

    - `d(a, b) \geq 0`,
    - `d(a, b) = 0` if and only if `a = b`.

    A metric space is a set `S` with a distinguished metric.

    .. RUBRIC:: Implementation

    Objects in this category must implement either a ``dist`` on the parent
    or the elements or ``metric`` on the parent; otherwise this will cause
    an infinite recursion.

    .. TODO::

        - Implement a general geodesics class.
        - Implement a category for metric additive groups
          and move the generic distance `d(a, b) = |a - b|` there.
        - Incorperate the length of a geodesic as part of the default
          distance cycle.

    EXAMPLES::

        sage: from sage.categories.metric_spaces import MetricSpaces
        sage: C = MetricSpaces()
        sage: C
        Category of metric spaces
        sage: TestSuite(C).run()
    """
    def _repr_object_names(self):
        """
        EXAMPLES::

            sage: Sets().Metric()  # indirect doctest
            Category of metric spaces
        """
        return "metric spaces"

    class ParentMethods:
        def _test_metric(self, **options):
            r"""
            Test that this metric space has a properly implemented metric.

            INPUT:

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

            EXAMPLES::

                sage: UHP = HyperbolicPlane().UHP()
                sage: UHP._test_metric()
                sage: elts = [UHP.random_element() for i in range(5)]
                sage: UHP._test_metric(some_elements=elts)
            """
            tester = self._tester(**options)
            S = tester.some_elements()
            dist = self.metric()
            for a in S:
                for b in S:
                    d = dist(a, b)
                    if a != b:
                        tester.assertGreater(d, 0)
                    else:
                        tester.assertEqual(d, 0)

        def metric(self):
            """
            Return the metric of ``self``.

            EXAMPLES::

                sage: UHP = HyperbolicPlane().UHP()
                sage: m = UHP.metric()
                sage: p1 = UHP.get_point(5 + 7*I)
                sage: p2 = UHP.get_point(1.0 + I)
                sage: m(p1, p2)
                2.23230104635820
            """
            return lambda a,b: a.dist(b)

        def dist(self, a, b):
            """
            Return the distance between ``a`` and ``b`` in ``self``.

            EXAMPLES::

                sage: UHP = HyperbolicPlane().UHP()
                sage: p1 = UHP.get_point(5 + 7*I)
                sage: p2 = UHP.get_point(1.0 + I)
                sage: UHP.dist(p1, p2)
                2.23230104635820

                sage: PD = HyperbolicPlane().PD()
                sage: PD.dist(PD.get_point(0), PD.get_point(I/2))
                arccosh(5/3)

            TESTS::

                sage: RR.dist(-1, pi)
                4.14159265358979
                sage: RDF.dist(1, -1/2)
                1.5
                sage: CC.dist(3, 2)
                1.00000000000000
                sage: CC.dist(-1, I)
                1.41421356237310
                sage: CDF.dist(-1, I)
                1.4142135623730951
            """
            return (self(a) - self(b)).abs()

    class ElementMethods:
        def abs(self):
            """
            Return the absolute value of ``self``.

            EXAMPLES::

                sage: CC(I).abs()
                1.00000000000000
            """
            P = self.parent()
            return P.metric()(self, P.zero())

        def dist(self, b):
            """
            Return the distance between ``self`` and ``other``.

            EXAMPLES::

                sage: UHP = HyperbolicPlane().UHP()
                sage: p1 = UHP.get_point(5 + 7*I)
                sage: p2 = UHP.get_point(1 + I)
                sage: p1.dist(p2)
                arccosh(33/7)
            """
            return self.parent().dist(self, b)

    class WithRealizations(WithRealizationsCategory):
        class ParentMethods:
            def dist(self, a, b):
                """
                Return the distance between ``a`` and ``b`` by converting them
                to a realization of ``self`` and doing the computation.

                EXAMPLES::

                    sage: H = HyperbolicPlane()
                    sage: PD = H.PD()
                    sage: p1 = PD.get_point(0)
                    sage: p2 = PD.get_point(I/2)
                    sage: H.dist(p1, p2)
                    arccosh(5/3)
                """
                R = self.a_realization()
                return R.dist(R(a), R(b))

    class SubcategoryMethods:
        @cached_method
        def Complete(self):
            """
            Return the full subcategory of the complete objects of ``self``.

            EXAMPLES::

                sage: Sets().Metric().Complete()
                Category of complete metric spaces

            TESTS::

                sage: TestSuite(Sets().Metric().Complete()).run()
                sage: Sets().Metric().Complete.__module__
                'sage.categories.metric_spaces'
            """
            return self._with_axiom('Complete')

    class Complete(CategoryWithAxiom):
        """
        The category of complete metric spaces.
        """

