# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.core import checks
from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields import FloatField, TextField
from django.utils.encoding import force_str

from .forms import OSMFormField
from .validators import validate_latitude, validate_longitude
from .widgets import OSMWidget


class Location(object):
    """
    A wrapper class bundling the description of a location (``text``) and its
    geo coordinates, latitude (``lat``) and longitude (``lon``).

    :param float lat: The latitude
    :param float lon: The longitude
    :param str: The description
    """

    def __init__(self, lat, lon, text):
        self.lat = lat
        self.lon = lon
        self.text = text

    def __str__(self):
        """
        Returns a string representation of this object in the form ``text (lat,
        lon)`` where either ``text`` or ``(lat, lon)'`` or both can be empty. In
        that case the return value is ``''``.
        """
        out = []
        if self.text is not None:
            out.append(self.text)
        if self.lat is not None and self.lon is not None:
            out.append("(%.6f, %.6f)" % (self.lat, self.lon))
        return " ".join(out)

    def __repr__(self):
        return "<Location lat=%.6f lon=%.6f text=%s>" % (
            self.lat,
            self.lon,
            force_str(self.text),
        )

    def __copy__(self):
        return self.__class__(self.lat, self.lon, self.text)

    def __eq__(self, other):
        return (
            isinstance(other, self.__class__)
            and self.lat == other.lat
            and self.lon == other.lon
            and self.text == other.text
        )

    def __ne__(self, other):
        return not (self == other)


class LatitudeField(FloatField):
    """
    Bases: :class:`django.db.models.FloatField`

    All :ref:`default field options <django:common-model-field-options>`.

    The ``validators`` parameter will be appended with
    :func:`~validate_latitude` if not already present.
    """

    def __init__(self, *args, **kwargs):
        kwargs.setdefault("validators", [])
        if validate_latitude not in kwargs["validators"]:
            kwargs["validators"].append(validate_latitude)
        super(LatitudeField, self).__init__(*args, **kwargs)

    def formfield(self, **kwargs):
        """
        :returns: A :class:`~django.forms.FloatField` with ``max_value`` 90 and
            ``min_value`` -90.
        """
        kwargs.update({"max_value": 90, "min_value": -90})
        return super(LatitudeField, self).formfield(**kwargs)


class LongitudeField(FloatField):
    """
    Bases: :class:`django.db.models.FloatField`

    All :ref:`default field options <django:common-model-field-options>`.

    The ``validators`` parameter will be appended with
    :func:`~validate_longitude` if not already present.
    """

    def __init__(self, *args, **kwargs):
        kwargs.setdefault("validators", [])
        if validate_longitude not in kwargs["validators"]:
            kwargs["validators"].append(validate_longitude)
        super(LongitudeField, self).__init__(*args, **kwargs)

    def formfield(self, **kwargs):
        """
        :returns: A :class:`~django.forms.FloatField` with ``max_value`` 180 and
            ``min_value`` -180.
        """
        kwargs.update({"max_value": 180, "min_value": -180})
        return super(LongitudeField, self).formfield(**kwargs)


class OSMField(TextField):
    """
    Bases: :class:`django.db.models.TextField`

    :param str lat_field: The name of the latitude field. ``None`` (and thus
        standard behavior) by default.
    :param str lon_field: The name of the longitude field. ``None`` (and thus
        standard behavior) by default.

    All :ref:`default field options <django:common-model-field-options>`.
    """

    def __init__(self, *args, **kwargs):
        self._lat_field_name = kwargs.pop("lat_field", None)
        self._lon_field_name = kwargs.pop("lon_field", None)
        self.data_field_name = kwargs.pop("data_field", None)
        super(OSMField, self).__init__(*args, **kwargs)

    def contribute_to_class(self, cls, name):
        info_name = "get_%s_info" % name
        if not hasattr(cls, info_name):

            def _func(obj):
                return Location(
                    getattr(obj, self.latitude_field_name),
                    getattr(obj, self.longitude_field_name),
                    getattr(obj, name),
                )

            setattr(cls, info_name, _func)

        super(OSMField, self).contribute_to_class(cls, name)

    def check(self, **kwargs):
        errors = super(OSMField, self).check(**kwargs)
        errors.extend(self._check_latitude_field())
        errors.extend(self._check_longitude_field())
        return errors

    def _check_latitude_field(self):
        opts = self.model._meta
        try:
            opts.get_field(self.latitude_field_name)
        except FieldDoesNotExist:
            return [
                checks.Error(
                    "The OSMField '%s' references the non-existent latitude field '%s'."
                    % (self.name, self.latitude_field_name),
                    hint=None,
                    obj=self,
                    id="osm_field.E001",
                )
            ]
        else:
            return []

    def _check_longitude_field(self):
        opts = self.model._meta
        try:
            opts.get_field(self.longitude_field_name)
        except FieldDoesNotExist:
            return [
                checks.Error(
                    "The OSMField '%s' references the non-existent "
                    "longitude field '%s'." % (self.name, self.longitude_field_name),
                    hint=None,
                    obj=self,
                    id="osm_field.E002",
                )
            ]
        else:
            return []

    def deconstruct(self):
        name, path, args, kwargs = super(OSMField, self).deconstruct()
        kwargs.update(
            {
                "lat_field": self.latitude_field_name,
                "lon_field": self.longitude_field_name,
            }
        )
        return name, path, args, kwargs

    def formfield(self, **kwargs):
        """
        :returns: A :class:`~osm_field.forms.OSMFormField` with a
            :class:`~osm_field.widgets.OSMWidget`.
        """
        widget_kwargs = {
            "lat_field": self.latitude_field_name,
            "lon_field": self.longitude_field_name,
        }

        if self.data_field_name:
            widget_kwargs["data_field"] = self.data_field_name

        defaults = {
            "form_class": OSMFormField,
            "widget": OSMWidget(**widget_kwargs),
        }
        defaults.update(kwargs)

        return super(OSMField, self).formfield(**defaults)

    @property
    def latitude_field_name(self):
        """
        The name of the related :class:`LatitudeField`.
        """
        if self._lat_field_name is None:
            self._lat_field_name = self.name + "_lat"
        return self._lat_field_name

    @property
    def longitude_field_name(self):
        """
        The name of the related :class:`LongitudeField`.
        """
        if self._lon_field_name is None:
            self._lon_field_name = self.name + "_lon"
        return self._lon_field_name
