"""
Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
This file is part of GNU Radio

GNU Radio Companion is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

GNU Radio Companion is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
"""

from __future__ import absolute_import
from .Constants import LINE_SELECT_SENSITIVITY

from six.moves import zip


class Element(object):
    """
    GraphicalElement is the base class for all graphical elements.
    It contains an X,Y coordinate, a list of rectangular areas that the element occupies,
    and methods to detect selection of those areas.
    """

    @classmethod
    def make_cls_with_base(cls, super_cls):
        name = super_cls.__name__
        bases = (super_cls,) + cls.__bases__[1:]
        namespace = cls.__dict__.copy()
        return type(name, bases, namespace)

    def __init__(self):
        """
        Make a new list of rectangular areas and lines, and set the coordinate and the rotation.
        """
        self.coordinate = (0, 0)
        self.rotation = 0
        self.highlighted = False

        self.area = []
        self.line = []

    def is_horizontal(self, rotation=None):
        """
        Is this element horizontal?
        If rotation is None, use this element's rotation.

        Args:
            rotation: the optional rotation

        Returns:
            true if rotation is horizontal
        """
        rotation = rotation or self.rotation
        return rotation in (0, 180)

    def is_vertical(self, rotation=None):
        """
        Is this element vertical?
        If rotation is None, use this element's rotation.

        Args:
            rotation: the optional rotation

        Returns:
            true if rotation is vertical
        """
        rotation = rotation or self.rotation
        return rotation in (90, 270)

    def create_labels(self):
        """
        Create labels (if applicable) and call on all children.
        Call this base method before creating labels in the element.
        """
        for child in self.get_children():
            child.create_labels()

    def create_shapes(self):
        """
        Create shapes (if applicable) and call on all children.
        Call this base method before creating shapes in the element.
        """
        for child in self.get_children():
            child.create_shapes()

    def draw(self, widget, cr):
        raise NotImplementedError()

    def rotate(self, rotation):
        """
        Rotate all of the areas by 90 degrees.

        Args:
            rotation: multiple of 90 degrees
        """
        self.rotation = (self.rotation + rotation) % 360

    def move(self, delta_coor):
        """
        Move the element by adding the delta_coor to the current coordinate.

        Args:
            delta_coor: (delta_x,delta_y) tuple
        """
        x, y = self.coordinate
        dx, dy = delta_coor
        self.coordinate = (x + dx, y + dy)

    def what_is_selected(self, coor, coor_m=None):
        """
        One coordinate specified:
            Is this element selected at given coordinate?
            ie: is the coordinate encompassed by one of the areas or lines?
        Both coordinates specified:
            Is this element within the rectangular region defined by both coordinates?
            ie: do any area corners or line endpoints fall within the region?

        Args:
            coor: the selection coordinate, tuple x, y
            coor_m: an additional selection coordinate.

        Returns:
            self if one of the areas/lines encompasses coor, else None.
        """
        # function to test if p is between a and b (inclusive)
        def in_between(p, a, b):
            # return min(a, b) <= p <= max(a, b)
            return a <= p <= b or b <= p <= a
        # relative coordinate
        x, y = [a - b for a, b in zip(coor, self.coordinate)]
        if coor_m:
            x_m, y_m = [a - b for a, b in zip(coor_m, self.coordinate)]
            # handle rectangular areas
            if self.area:
                x1, y1, w, h = self.area
                if (
                    in_between(x1,     x, x_m) and in_between(y1,     y, y_m) or
                    in_between(x1 + w, x, x_m) and in_between(y1,     y, y_m) or
                    in_between(x1,     x, x_m) and in_between(y1 + h, y, y_m) or
                    in_between(x1 + w, x, x_m) and in_between(y1 + h, y, y_m)
                ):
                    return self
            # handle horizontal or vertical lines
            elif self.line:
                last_point = self.line[0]
                for x2, y2 in self.line[1:]:
                    (x1, y1), last_point = last_point, (x2, y2)
                    if (
                        in_between(x1, x, x_m) and in_between(y1, y, y_m) or
                        in_between(x2, x, x_m) and in_between(y2, y, y_m)
                    ):
                        return self
            return None
        else:
            # handle rectangular areas
            if self.area:
                x1, y1, w, h = self.area
                if in_between(x, x1, x1+w) and in_between(y, y1, y1+h):
                    return self
            # handle horizontal or vertical lines
            elif self.line:
                last_point = self.line[0]
                for x2, y2 in self.line[1:]:
                    (x1, y1), last_point = last_point, (x2, y2)
                    if x1 == x2:
                        x1, x2 = x1 - LINE_SELECT_SENSITIVITY, x2 + LINE_SELECT_SENSITIVITY
                    if y1 == y2:
                        y1, y2 = y1 - LINE_SELECT_SENSITIVITY, y2 + LINE_SELECT_SENSITIVITY
                    if in_between(x, x1, x2) and in_between(y, y1, y2):
                        return self
            return None

    def mouse_over(self):
        pass

    def mouse_out(self):
        pass