from qtpy.QtCore import QPointF
from qtpy.QtGui import QPen, QPainterPath
from qtpy.QtWidgets import QGraphicsPathItem, QMenu

from b_asic.GUI._preferences import GRID, LINECOLOR, PORTHEIGHT, PORTWIDTH
from b_asic.signal import Signal


class Arrow(QGraphicsPathItem):
    """Arrow/connection in signal flow graph GUI."""

    def __init__(self, source, destination, window, signal=None, parent=None):
        """
        Parameters
        ==========

        source :
            Source operation.
        destination :
            Destination operation.
        window :
            Window containing signal flow graph.
        signal : Signal, optional
            Let arrow represent *signal*.
        parent : optional
            Parent.
        """
        super().__init__(parent)
        self.source = source
        if signal is None:
            signal = Signal(source.port, destination.port)
        self.signal = signal
        self.destination = destination
        self._window = window
        self.moveLine()
        self.source.moved.connect(self.moveLine)
        self.destination.moved.connect(self.moveLine)

    def contextMenuEvent(self, event):
        """Open right-click menu."""
        menu = QMenu()
        menu.addAction("Delete", self.remove)
        menu.exec_(self.cursor().pos())

    def remove(self):
        """Remove line and connections to signals etc."""
        self.signal.remove_destination()
        self.signal.remove_source()
        self._window.scene.removeItem(self)
        if self in self._window.signalList:
            self._window.signalList.remove(self)

        if self in self._window.signalPortDict:
            for port1, port2 in self._window.signalPortDict[self]:
                for (
                    operation,
                    operation_ports,
                ) in self._window.portDict.items():
                    if (
                        port1 in operation_ports or port2 in operation_ports
                    ) and operation in self._window.opToSFG:
                        self._window.logger.info(
                            "Operation detected in existing SFG, removing SFG"
                            " with name:"
                            f" {self._window.opToSFG[operation].name}."
                        )
                        del self._window.sfg_dict[
                            self._window.opToSFG[operation].name
                        ]
                        self._window.opToSFG = {
                            op: self._window.opToSFG[op]
                            for op in self._window.opToSFG
                            if self._window.opToSFG[op]
                            is not self._window.opToSFG[operation]
                        }

        del self._window.signalPortDict[self]

    def moveLine(self):
        """
        Draw a line connecting ``self.source`` with ``self.destination``. Used as callback when moving operations.
        """
        ORTHOGONAL = True
        OFFSET = 2 * PORTWIDTH
        self.setPen(QPen(LINECOLOR, 3))
        source_flipped = self.source.operation.is_flipped()
        destination_flipped = self.destination.operation.is_flipped()
        x0 = (
            self.source.operation.x()
            + self.source.x()
            + (PORTWIDTH if not source_flipped else 0)
        )
        y0 = self.source.operation.y() + self.source.y() + PORTHEIGHT / 2
        x1 = (
            self.destination.operation.x()
            + self.destination.x()
            + (0 if not destination_flipped else PORTWIDTH)
        )
        y1 = (
            self.destination.operation.y()
            + self.destination.y()
            + PORTHEIGHT / 2
        )
        xmid = (x0 + x1) / 2
        ymid = (y0 + y1) / 2
        both_flipped = source_flipped and destination_flipped
        any_flipped = source_flipped or destination_flipped
        p = QPainterPath(QPointF(x0, y0))
        # TODO: Simplify or create a better router
        if not ORTHOGONAL:
            pass
        elif y0 == y1:
            if not any_flipped:
                if x0 <= x1:
                    pass
                else:
                    p.lineTo(QPointF(x0 + OFFSET, y0))
                    p.lineTo(QPointF(x0 + OFFSET, y0 + OFFSET))
                    p.lineTo(QPointF(x1 - OFFSET, y0 + OFFSET))
                    p.lineTo(QPointF(x1 - OFFSET, y0))
            elif both_flipped:
                if x1 <= x0:
                    pass
                else:
                    p.lineTo(QPointF(x0 + OFFSET, y0))
                    p.lineTo(QPointF(x0 + OFFSET, y0 + OFFSET))
                    p.lineTo(QPointF(x1 - OFFSET, y0 + OFFSET))
                    p.lineTo(QPointF(x1 - OFFSET, y0))
            elif source_flipped:
                if x0 <= x1:
                    p.lineTo(QPointF(x0 - OFFSET, y0))
                    p.lineTo(QPointF(x0 - OFFSET, y0 + OFFSET))
                    p.lineTo(QPointF(xmid, y0 + OFFSET))
                    p.lineTo(QPointF(xmid, y0))
                else:
                    p.lineTo(QPointF(x0 + OFFSET, y0))
                    p.lineTo(QPointF(x0 + OFFSET, y0 + OFFSET))
                    p.lineTo(QPointF(xmid, y0 + OFFSET))
                    p.lineTo(QPointF(xmid, y0))
            else:
                if x1 <= x0:
                    p.lineTo(QPointF(x0 + OFFSET, y0))
                    p.lineTo(QPointF(x0 + OFFSET, y0 + OFFSET))
                    p.lineTo(QPointF(xmid, y0 + OFFSET))
                    p.lineTo(QPointF(xmid, y0))
                else:
                    p.lineTo(QPointF(xmid, y0))
                    p.lineTo(QPointF(xmid, y0 + OFFSET))
                    p.lineTo(QPointF(x1 + OFFSET, y0 + OFFSET))
                    p.lineTo(QPointF(x1 + OFFSET, y0))

        elif abs(x0 - x1) <= GRID:
            if both_flipped or not any_flipped:
                offset = -OFFSET if both_flipped else OFFSET
                p.lineTo(QPointF(x0 + offset, y0))
                p.lineTo(QPointF(x0 + offset, ymid))
                p.lineTo(QPointF(x0 - offset, ymid))
                p.lineTo(QPointF(x0 - offset, y1))
            else:
                offset = -OFFSET if source_flipped else -OFFSET
                p.lineTo(QPointF(x0 + offset, y0))
                p.lineTo(QPointF(x0 + offset, y1))
        else:
            if not any_flipped:
                if x0 <= x1:
                    p.lineTo(QPointF(xmid, y0))
                    p.lineTo(QPointF(xmid, y1))
                else:
                    p.lineTo(x0 + OFFSET, y0)
                    p.lineTo(x0 + OFFSET, ymid)
                    p.lineTo(x1 - OFFSET, ymid)
                    p.lineTo(x1 - OFFSET, y1)
            elif both_flipped:
                if x0 >= x1:
                    p.lineTo(QPointF(xmid, y0))
                    p.lineTo(QPointF(xmid, y1))
                else:
                    p.lineTo(x0 - OFFSET, y0)
                    p.lineTo(x0 - OFFSET, ymid)
                    p.lineTo(x1 + OFFSET, ymid)
                    p.lineTo(x1 + OFFSET, y1)
            elif source_flipped:
                xmin = min(x0, x1) - OFFSET
                p.lineTo(QPointF(xmin, y0))
                p.lineTo(QPointF(xmin, y1))
            else:
                xmax = max(x0, x1) + OFFSET
                p.lineTo(QPointF(xmax, y0))
                p.lineTo(QPointF(xmax, y1))

        p.lineTo(QPointF(x1, y1))
        self.setPath(p)