"""
MathApp classes for representing geometric points
"""
from abc import ABCMeta, abstractmethod
from ggame.mathapp import _MathVisual, MathApp
from ggame.asset import Color, LineStyle, CircleAsset, ImageAsset


class _Point(_MathVisual, metaclass=ABCMeta):
    """
    Abstract base class for all point classes.

    :param Asset asset: A valid ggame Asset object
    :param tuple(float,float) pos: The position (physical or logical)
    """

    _posinputsdef = ["pos"]
    _nonposinputsdef = []

    def __init__(self, asset, *args, **kwargs):
        super().__init__(asset, *args, **kwargs)
        self.touchAsset()
        self.center = (0.5, 0.5)

    def __call__(self):
        return self._posinputs.pos()

    def step(self):
        """
        Perform periodic processing.
        """
        self.touchAsset()

    def physicalPointTouching(self, ppos):
        """
        Determine if a physical point is considered to be touching this point.

        :param tuple(int,int) ppos: Physical screen coordinates.
        :rtype: boolean
        :returns: True if touching, False otherwise.
        """
        return (
            MathApp.distance(ppos, self._pposinputs.pos)  # pylint: disable=no-member
            < self._sstdinputs.size
        )

    def translate(self, pdisp):
        """
        Perform necessary processing in response to being moved by the mouse/UI.

        :param tuple(int,int) pdisp: Translation vector (x,y) in physical screen
            units.
        :returns: None
        """
        ldisp = MathApp.translatePhysicalToLogical(pdisp)
        pos = self._posinputs.pos()
        self._posinputs = self._posinputs._replace(
            pos=self.eval((pos[0] + ldisp[0], pos[1] + ldisp[1]))
        )
        self.touchAsset()

    def distanceTo(self, otherpoint):
        """
        Compute the distance to another :class:`_Point` object.

        :param _Point otherpoint: A reference to the other :class:`_Point`
        :rtype: float
        :returns: The distance (in logical units) to the other point
        """
        try:
            return MathApp.distance(self(), otherpoint())
        except AttributeError:
            return otherpoint  # presumably a scalar - use this distance

    @abstractmethod
    def _buildAsset(self):
        pass


class Point(_Point):
    """
    Basic point object representing any point in a geometrical sense.
    An instantiated Point object is *callable* and will return a tuple
    with its logical position as an (x,y) pair.


    :param tuple(float,float) pos: Position in physical or logical units.

    :param \\**kwargs:
        See below

    :Optional Keyword Arguments:
        * **positioning** (*str*) One of 'logical' (default) or 'physical'
        * **size** (*int*) Radius of the point (in pixels)
        * **color** (*Color*) Valid :class:`~ggame.asset.Color` object
        * **style** (*LineStyle*) Valid :class:`~ggame.asset.LineStyle` object

    Example:

    .. literalinclude:: ../examples/pointpoint.py

    """

    _defaultsize = 5
    _defaultstyle = LineStyle(0, Color(0, 1))

    def __init__(self, *args, **kwargs):
        super().__init__(
            CircleAsset(self._defaultsize, self._defaultstyle, self._defaultcolor),
            *args,
            **kwargs
        )

    def _buildAsset(self):
        return CircleAsset(
            self._stdinputs.size(), self._stdinputs.style(), self._stdinputs.color()
        )


class ImagePoint(_Point):
    """
    :class:`~ggame.point.Point` object that uses an image as its on-screen
        representation.

    :param str url: Location of an image file (png, jpg)

    :param \\*args:
        See below
    :param \\**kwargs:
        See below

    :Required Arguments:
        * **pos** (*tuple(float,float)*) Position in physical or logical units.


    :Optional Keyword Arguments:
        * **positioning** (*str*) One of 'logical' (default) or 'physical'
        * **frame** (*Frame*) The sub-frame location of image within the image file
        * **qty** (*int*) The number of sub-frames, when used as a sprite sheet
        * **direction** (*str*) One of 'horizontal' (default) or 'vertical'
        * **margin** (*int*) Pixels between sub-frames if sprite sheet
    """

    def __init__(self, url, *args, **kwargs):
        frame = kwargs.get("frame", None)
        qty = kwargs.get("qty", 1)
        direction = kwargs.get("direction", "horizontal")
        margin = kwargs.get("margin", 0)
        self._imageasset = ImageAsset(url, frame, qty, direction, margin)
        super().__init__(self._imageasset, *args, **kwargs)

    def _buildAsset(self):
        return self._imageasset

    def physicalPointTouching(self, ppos):
        """
        Determine if a physical point is considered to be touching point's
        image.

        :param tuple(int,int) ppos: Physical screen coordinates.
        :rtype: boolean
        :returns: True if touching, False otherwise.
        """
        self.setExtents()  # ensure xmin, xmax are correct
        x, y = ppos
        return self.xmax >= x >= self.xmin and self.ymax >= y >= self.ymin
