import math
from fontTools.misc import transform
from fontParts.base.base import (
    BaseObject,
    TransformationMixin,
    InterpolationMixin,
    SelectionMixin,
    PointPositionMixin,
    IdentifierMixin,
    dynamicProperty,
    reference
)
from fontParts.base import normalizers
from fontParts.base.compatibility import GuidelineCompatibilityReporter
from fontParts.base.color import Color
from fontParts.base.deprecated import DeprecatedGuideline, RemovedGuideline


class BaseGuideline(
                    BaseObject,
                    TransformationMixin,
                    DeprecatedGuideline,
                    RemovedGuideline,
                    PointPositionMixin,
                    InterpolationMixin,
                    IdentifierMixin,
                    SelectionMixin
                    ):

    """
    A guideline object. This object is almost always
    created with :meth:`BaseGlyph.appendGuideline`.
    An orphan guideline can be created like this::

        >>> guideline = RGuideline()
    """

    copyAttributes = (
        "x",
        "y",
        "angle",
        "name",
        "color"
    )

    def _reprContents(self):
        contents = []
        if self.name is not None:
            contents.append("'%s'" % self.name)
        if self.layer is not None:
            contents.append("('%s')" % self.layer.name)
        return contents

    # -------
    # Parents
    # -------

    # Glyph

    _glyph = None

    glyph = dynamicProperty("glyph", "The guideline's parent :class:`BaseGlyph`.")

    def _get_glyph(self):
        if self._glyph is None:
            return None
        return self._glyph()

    def _set_glyph(self, glyph):
        if self._font is not None:
            raise AssertionError("font for guideline already set")
        if self._glyph is not None:
            raise AssertionError("glyph for guideline already set")
        if glyph is not None:
            glyph = reference(glyph)
        self._glyph = glyph

    # Layer

    layer = dynamicProperty("layer", "The guideline's parent :class:`BaseLayer`.")

    def _get_layer(self):
        if self._glyph is None:
            return None
        return self.glyph.layer

    # Font

    _font = None

    font = dynamicProperty("font", "The guideline's parent :class:`BaseFont`.")

    def _get_font(self):
        if self._font is not None:
            return self._font()
        elif self._glyph is not None:
            return self.glyph.font
        return None

    def _set_font(self, font):
        if self._font is not None:
            raise AssertionError("font for guideline already set")
        if self._glyph is not None:
            raise AssertionError("glyph for guideline already set")
        if font is not None:
            font = reference(font)
        self._font = font

    # --------
    # Position
    # --------

    # x

    x = dynamicProperty(
        "base_x",
        """
        The x coordinate of the guideline.
        It must be an :ref:`type-int-float`. ::

            >>> guideline.x
            100
            >>> guideline.x = 101
        """
    )

    def _get_base_x(self):
        value = self._get_x()
        if value is None:
            return 0
        value = normalizers.normalizeX(value)
        return value

    def _set_base_x(self, value):
        if value is None:
            value = 0
        else:
            value = normalizers.normalizeX(value)
        self._set_x(value)

    def _get_x(self):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.x`. This must return an
        :ref:`type-int-float`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _set_x(self, value):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.x`. **value** will be
        an :ref:`type-int-float`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # y

    y = dynamicProperty(
        "base_y",
        """
        The y coordinate of the guideline.
        It must be an :ref:`type-int-float`. ::

            >>> guideline.y
            100
            >>> guideline.y = 101
        """
    )

    def _get_base_y(self):
        value = self._get_y()
        if value is None:
            return 0
        value = normalizers.normalizeY(value)
        return value

    def _set_base_y(self, value):
        if value is None:
            value = 0
        else:
            value = normalizers.normalizeY(value)
        self._set_y(value)

    def _get_y(self):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.y`. This must return an
        :ref:`type-int-float`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _set_y(self, value):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.y`. **value** will be
        an :ref:`type-int-float`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # angle

    angle = dynamicProperty(
        "base_angle",
        """
        The angle of the guideline.
        It must be an :ref:`type-angle`.
        Please check how :func:`normalizers.normalizeRotationAngle`
        handles the angle. There is a special case, when angle is ``None``.
        If so, when x and y are not 0, the angle will be 0. If x is 0 but y
        is not, the angle will be 0. If y is 0 and x is not, the
        angle will be 90. If both x and y are 0, the angle will be 0.
        ::

            >>> guideline.angle
            45.0
            >>> guideline.angle = 90
        """
    )

    def _get_base_angle(self):
        value = self._get_angle()
        if value is None:
            if self._get_x() != 0 and self._get_y() != 0:
                value = 0
            elif self._get_x() != 0 and self._get_y() == 0:
                value = 90
            elif self._get_x() == 0 and self._get_y() != 0:
                value = 0
            else:
                value = 0
        value = normalizers.normalizeRotationAngle(value)
        return value

    def _set_base_angle(self, value):
        if value is None:
            if self._get_x() != 0 and self._get_y() != 0:
                value = 0
            elif self._get_x() != 0 and self._get_y() == 0:
                value = 90
            elif self._get_x() == 0 and self._get_y() != 0:
                value = 0
            else:
                value = 0
        value = normalizers.normalizeRotationAngle(value)
        self._set_angle(value)

    def _get_angle(self):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.angle`. This must return an
        :ref:`type-angle`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _set_angle(self, value):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.angle`. **value** will be
        an :ref:`type-angle`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # --------------
    # Identification
    # --------------

    # index

    index = dynamicProperty(
        "base_index",
        """
        The index of the guideline within the ordered
        list of the parent glyph's guidelines. This
        attribute is read only. ::

            >>> guideline.index
            0
        """
    )

    def _get_base_index(self):
        value = self._get_index()
        value = normalizers.normalizeIndex(value)
        return value

    def _get_index(self):
        """
        Get the guideline's index.
        This must return an ``int``.

        Subclasses may override this method.
        """
        glyph = self.glyph
        if glyph is not None:
            parent = glyph
        else:
            parent = self.font
        if parent is None:
            return None
        return parent.guidelines.index(self)

    # name

    name = dynamicProperty(
        "base_name",
        """
        The name of the guideline. This will be a
        :ref:`type-string` or ``None``.

            >>> guideline.name
            'my guideline'
            >>> guideline.name = None
        """
    )

    def _get_base_name(self):
        value = self._get_name()
        if value is not None:
            value = normalizers.normalizeGuidelineName(value)
        return value

    def _set_base_name(self, value):
        if value is not None:
            value = normalizers.normalizeGuidelineName(value)
        self._set_name(value)

    def _get_name(self):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.name`. This must return a
        :ref:`type-string` or ``None``. The returned
        value will be normalized with
        :func:`normalizers.normalizeGuidelineName`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _set_name(self, value):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.name`. **value** will be
        a :ref:`type-string` or ``None``. It will
        have been normalized with
        :func:`normalizers.normalizeGuidelineName`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # color

    color = dynamicProperty(
        "base_color",
        """"
        The guideline's color. This will be a
        :ref:`type-color` or ``None``. ::

            >>> guideline.color
            None
            >>> guideline.color = (1, 0, 0, 0.5)
        """
    )

    def _get_base_color(self):
        value = self._get_color()
        if value is not None:
            value = normalizers.normalizeColor(value)
            value = Color(value)
        return value

    def _set_base_color(self, value):
        if value is not None:
            value = normalizers.normalizeColor(value)
        self._set_color(value)

    def _get_color(self):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.color`. This must return
        a :ref:`type-color` or ``None``. The
        returned value will be normalized with
        :func:`normalizers.normalizeColor`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _set_color(self, value):
        """
        This is the environment implementation of
        :attr:`BaseGuideline.color`. **value** will
        be a :ref:`type-color` or ``None``.
        It will have been normalized with
        :func:`normalizers.normalizeColor`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # --------------
    # Transformation
    # --------------

    def _transformBy(self, matrix, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseGuideline.transformBy`.

        **matrix** will be a :ref:`type-transformation`.
        that has been normalized with :func:`normalizers.normalizeTransformationMatrix`.

        Subclasses may override this method.
        """
        t = transform.Transform(*matrix)
        # coordinates
        x, y = t.transformPoint((self.x, self.y))
        self.x = x
        self.y = y
        # angle
        angle = math.radians(-self.angle)
        dx = math.cos(angle)
        dy = math.sin(angle)
        tdx, tdy = t.transformPoint((dx, dy))
        ta = math.atan2(tdy - t[5], tdx - t[4])
        self.angle = -math.degrees(ta)

    # -------------
    # Interpolation
    # -------------

    compatibilityReporterClass = GuidelineCompatibilityReporter

    def isCompatible(self, other):
        """
        Evaluate interpolation compatibility with **other**. ::

            >>> compatible, report = self.isCompatible(otherGuideline)
            >>> compatible
            True
            >>> compatible
            [Warning] Guideline: "xheight" + "cap_height"
            [Warning] Guideline: "xheight" has name xheight | "cap_height" has
                                  name cap_height

        This will return a ``bool`` indicating if the guideline is
        compatible for interpolation with **other** and a
        :ref:`type-string` of compatibility notes.
        """
        return super(BaseGuideline, self).isCompatible(other, BaseGuideline)

    def _isCompatible(self, other, reporter):
        """
        This is the environment implementation of
        :meth:`BaseGuideline.isCompatible`.

        Subclasses may override this method.
        """
        guideline1 = self
        guideline2 = other
        # guideline names
        if guideline1.name != guideline2.name:
            reporter.nameDifference = True
            reporter.warning = True

    # -------------
    # Normalization
    # -------------

    def round(self):
        """
        Round the guideline's coordinate.

            >>> guideline.round()

        This applies to the following:

        * x
        * y

        It does not apply to

        * angle
        """
        self._round()

    def _round(self, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseGuideline.round`.

        Subclasses may override this method.
        """
        x = self._get_x()
        if x is not None:
            self.x = normalizers.normalizeVisualRounding(normalizers.normalizeX(x))
        y = self._get_y()
        if y is not None:
            self.y = normalizers.normalizeVisualRounding(normalizers.normalizeY(y))
