import shiboken2
from PySide2 import QtCore, QtWidgets, QtGui


class RectangleSlideInFromTopEffect(QtCore.QObject):
    def __init__(self, rectangle, parent):
        super().__init__(parent)
        if not isinstance(rectangle, QtWidgets.QWidget):
            raise TypeError(f"{rectangle} must be a QWidget")
        if rectangle.parent() is None:
            raise ValueError(f"{rectangle} must have a parent")
        self.m_rectangle = rectangle
        self.m_animation = QtCore.QPropertyAnimation(
            parent=self,
            targetObject=self.m_rectangle,
            propertyName=b"pos",
            duration=300,
            easingCurve=QtCore.QEasingCurve.OutQuad,
        )

        if shiboken2.isValid(self.m_rectangle):
            self.m_rectangle.move(QtCore.QPoint(0, -self.m_rectangle.parent().height()))
        else:
            raise ValueError(f"{rectangle} invalid")

        # self.m_rectangle.parent().installEventFilter(self)

    def slide_in(self):
        y0 = -self.m_rectangle.parent().height()
        y1 = 0
        self._start_animation(y0, y1)

    def slide_out(self):
        y0 = 0
        y1 = -self.m_rectangle.parent().height()
        self._start_animation(y0, y1)

    # def eventFilter(self, obj, event):
    #     if shiboken2.isValid(self.m_rectangle):
    #         if self.m_rectangle.parent() is obj:
    #             if event.type() == QtCore.QEvent.MouseButtonPress:
    #                 self.slide_in()
    #             elif event.type() == QtCore.QEvent.MouseButtonRelease:
    #                 self.slide_out()
    #     return super().eventFilter(obj, event)

    def _start_animation(self, y0, y1):
        self.m_animation.setStartValue(QtCore.QPoint(0, y0))
        self.m_animation.setEndValue(QtCore.QPoint(0, y1))
        self.m_animation.start()


class SlideHoverMenu(QtWidgets.QFrame):
    background_margins = 2

    def __init__(self, parent=None):
        super().__init__(parent)  # todo: ?
        self.parent = parent

        self.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.setFrameShadow(QtWidgets.QFrame.Raised)

        self.text = ""
        self.no_hide_rect = None

        self.text_label = QtWidgets.QLabel(self)
        self.text_label.setMargin(2)
        self.text_label.setStyleSheet("color: #FFFFFF")
        font = QtGui.QFont()
        font.setFamily("SF Pro Display")
        font.setPointSize(10)
        #font.setWeight(75)
        font.setBold(True)
        self.text_label.setFont(font)

        self.background_label = QtWidgets.QLabel(self)
        self.background_label.setStyleSheet("background: #AA32353B")

        self.background_lay = QtWidgets.QVBoxLayout(self.background_label)
        self.background_lay.addWidget(self.text_label)
        self.background_lay.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
        self.background_lay.setContentsMargins(self.background_margins, self.background_margins,
                                               self.background_margins, self.background_margins)
        #self.background_lay.setContentsMargins(0, 0, 0, 0)
        self.background_lay.setSpacing(0)

        #self.setStyleSheet("color: #BB32353B")
        #self.setContentsMargins(2, 2, 2, 2)

        self.effect = RectangleSlideInFromTopEffect(self.background_label, self)

    def __call__(self, position: QtCore.QPoint, text, *args, on_hide_callback=None):
        self.hide_callback = on_hide_callback
        position = self.parent.mapFromGlobal(position)

        self.text = ""
        self.add_to_menu(text)

        frame_rect = self.place_at(point=position, direction="right")
        if frame_rect.width() > self.parent.rect().width() - position.x():
            frame_rect = self.place_at(point=position, direction="left")

        self.no_hide_rect = self.rect() + QtCore.QMargins(5, 5, 5, 5)

        self.show_menu()

        # print(f"text_label: {self.text_label.geometry()}")
        # print(f"background_label: {self.background_label.geometry()}")
        # print(f"background_lay: {self.background_lay.geometry()}")
        # print(f"background_lay_siez: {self.background_lay.sizeHint()}")
        # print(f"self: {self.geometry()}")

    def add_to_menu(self, text: str):
        if self.text == "":
            self.text = text
        else:
            if self.text != text:
                self.text += '\n' + text
        self.text_label.setText(self.text)
        self.background_label.setFixedSize(self.text_label.size() +
                                           QtCore.QSize(2*self.background_margins, 2*self.background_margins))
        self.setFixedSize(self.background_lay.sizeHint())
        self.no_hide_rect = self.rect() + QtCore.QMargins(5, 5, 5, 5)
        #print(f"text: {self.text}")

    def place_at(self, point=None, direction=None) -> QtCore.QRect:
        dir_options = ("right", "left")
        if direction not in dir_options:
            raise ValueError(f"{direction} must be in {dir_options}")

        if direction == "right":
            self.move(point)
        else:
            self.move(point - QtCore.QPoint(self.width(), 0))

        return self.rect()

    def show_menu(self):
        QtCore.QTimer().singleShot(1000, self.hide_menu)
        self.show()
        self.effect.slide_in()

    def hide_menu(self):
        if self.no_hide_rect.contains(self.mapFromGlobal(QtGui.QCursor.pos())):
            QtCore.QTimer().singleShot(1000, self.hide_menu)
        else:
            self.effect.m_animation.finished.connect(self.close_menu)
            self.effect.slide_out()

    def close_menu(self):
        # find method of signal to check for isSignalConnected
        method_index = self.effect.m_animation.metaObject().indexOfSignal('finished()')
        method = self.effect.m_animation.metaObject().method(method_index)
        if self.effect.m_animation.isSignalConnected(method):
            self.effect.m_animation.finished.disconnect(self.close_menu)
        self.effect.m_animation.stop()
        self.close()
        self.hide_callback()


class SlideHoverMenuWithMarkers(SlideHoverMenu):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.scatter = list()
        self.line = None
        self.is_active = None
        self.last_pos = None

    def __call__(self, pos, p_str, *args, on_hide_callback=None):
        if self.is_active:
            if self.last_pos and self.last_pos != pos:
                self.close_menu()
            else:
                self.show_marker(args[1])
                self.add_to_menu(p_str)
                return

        self.last_pos = pos
        self.line = args[0]
        points = args[1]
        self.is_active = True
        self.show_marker(points)
        super().__call__(pos, p_str, *args, on_hide_callback=self.hide_marker)

    def show_marker(self, points):
        for x, y in points:
            self.scatter.append(self.line.axes.scatter(x, y, s=12, zorder=10,
                                                       color="black", marker='o', facecolors='none'))
        self.line.figure.canvas.draw()

    def hide_marker(self):
        assert self.line
        while self.scatter:
            self.scatter.pop().remove()
        self.line.figure.canvas.draw()
        self.is_active = False
