from PyQt5.QtGui import QPen, QPainterPath, QBrush, QPainterPathStroker, QPainter, QCursor
from PyQt5.QtWidgets import QGraphicsItem, QGraphicsPathItem
from PyQt5.QtCore import Qt, QPointF, QRectF

class Grabber(QGraphicsPathItem):
    """
    Extends QGraphicsPathItem to create grabber for line for moving a particular segment
    """
    circle = QPainterPath()
    circle.addEllipse(QRectF(-5, -5, 10, 10))

    def __init__(self, annotation_line, index, direction):
        super(Grabber, self).__init__()
        self.m_index = index
        self.m_annotation_item = annotation_line
        self._direction = direction
        self.setPath(Grabber.circle)
        # set graphical settings for this item
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setAcceptHoverEvents(True)

    def itemChange(self, change, value):
        """ move position of grabber after resize"""
        if change == QGraphicsItem.ItemPositionChange and self.isEnabled():
            p = QPointF(self.pos())
            if self._direction == Qt.Horizontal:
                p.setX(value.x())
            elif self._direction == Qt.Vertical:
                p.setY(value.y())
            movement = p - self.pos()
            self.m_annotation_item.movePoints(self.m_index, movement)
            return p
        return super(Grabber, self).itemChange(change, value)

    def paint(self, painter, option, widget):
        """paints the path of grabber only if it is selected
        """
        if self.isSelected():
            # show line of grabber
            self.m_annotation_item.setSelected(True)
            painter.setBrush(QBrush(Qt.cyan))
        color = Qt.black if self.isSelected() else Qt.white
        width = 2 if self.isSelected() else -1
        painter.setPen(QPen(color, width, Qt.SolidLine))
        painter.drawPath(self.path())

        # To paint path of shape
        # color = Qt.red if self.isSelected() else Qt.black
        # painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine))
        # painter.drawPath(self.shape())

    def shape(self):
        """Overrides shape method and set shape to segment on which grabber is located"""
        index = self.m_index
        startPoint = QPointF(self.m_annotation_item.path().elementAt(index))
        endPoint = QPointF(self.m_annotation_item.path().elementAt(index + 1))
        startPoint = self.mapFromParent(startPoint)
        endPoint = self.mapFromParent(endPoint)
        path = QPainterPath(startPoint)
        path.lineTo(endPoint)
        # generate outlines for path
        stroke = QPainterPathStroker()
        stroke.setWidth(8)
        return stroke.createStroke(path)

    def boundingRect(self):
        return self.shape().boundingRect()

    def mousePressEvent(self, event):
        print('grabber clicked', self)
        super(Grabber, self).mousePressEvent(event)

    def hoverEnterEvent(self, event):
        """
        Changes cursor to horizontal movement or vertical movement
         depending on the direction of the grabber on mouse enter
        """
        if self._direction == Qt.Horizontal:
            self.setCursor(QCursor(Qt.SplitHCursor))
        else:
            self.setCursor(QCursor(Qt.SplitVCursor))
        super(Grabber, self).hoverEnterEvent(event)

    def hoverLeaveEvent(self, event):
        """
        reverts cursor to default on mouse leave
        """
        self.setCursor(QCursor(Qt.ArrowCursor))
        super(Grabber, self).hoverLeaveEvent(event)


class Line(QGraphicsPathItem):
    """
    Extends QGraphicsPathItem to draw zig-zag line consisting of multiple points
    """
    penStyle = Qt.SolidLine

    def __init__(self, startPoint, endPoint, **args):
        QGraphicsItem.__init__(self, **args)
        self.startPoint = startPoint
        self.endPoint = endPoint
        #stores all points of line
        self.points = []
        self.points.extend([startPoint, endPoint])
        self.startGripItem = None
        self.endGripItem = None
        self._selected = False
        self.m_grabbers = []
        # stores current pen style of line
        self.penStyle = Line.penStyle
        # set graphical settings for this item
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setAcceptHoverEvents(True)
        # initiates path
        self.createPath()

    def createPath(self):
        """
        creates initial path and stores it's points
        :return:
        """
        offset = 30
        x0, y0 = self.startPoint.x(), self.startPoint.y()
        x1, y1 = self.endPoint.x(), self.endPoint.y()
        self.points = [self.startPoint, QPointF((x0 + x1) / 2, y0), QPointF((x0 + x1) / 2, y1), self.endPoint]
        if self.startGripItem and self.startGripItem.m_location in ["left", "right"]:
            if self.endGripItem and self.endGripItem.m_location in ["top", "bottom"]:
                if self.endGripItem.m_location == "top": offset = -offset
                self.points = [self.startPoint, QPointF((x0 + x1) / 2, y0), QPointF((x0 + x1) / 2, y1 + offset),
                               QPointF(self.endPoint.x(), y1 + offset), self.endPoint]

        if self.startGripItem and self.startGripItem.m_location in ["top", "bottom"]:
            self.points = [self.startPoint, QPointF(x0, (y0 + y1) / 2), QPointF(x1, (y0 + y1) / 2), self.endPoint]
            if self.endGripItem and self.endGripItem.m_location in ["left", "right"]:
                self.points = [self.startPoint, QPointF(x0, (y0 + y1) / 2), QPointF(x1 - offset, (y0 + y1) / 2),
                               QPointF(x1 - offset, self.endPoint.y()), self.endPoint]
        # draw line
        path = QPainterPath(self.startPoint)
        for i in range(1, len(self.points)):
            path.lineTo(self.points[i])
        self.setPath(path)
        if self.endGripItem:
            self.addGrabber()

    def updatePath(self):
        """ update path when svg item moves
        """
        path = QPainterPath(self.startPoint)
        self.updatePoints()

        for i in range(1, len(self.points) - 1):
            path.lineTo(self.points[i])
        path.lineTo(self.endPoint)
        self.setPath(path)

    def updatePoints(self):
        """
        updates points of line when grabber is moved
        :return:
        """
        if self.startGripItem.m_location in ["left", "right"]:
            point = self.points[1]
            self.points[1] = QPointF(point.x(), self.startPoint.y())
            if self.endGripItem.m_location in ["left", "right"]:
                point = self.points[len(self.points) - 2]
                self.points[len(self.points) - 2] = QPointF(point.x(), self.endPoint.y())
            else:
                point = self.points[len(self.points) - 2]
                self.points[len(self.points) - 2] = QPointF(self.endPoint.x(), point.y())

        else:
            point = self.points[1]
            self.points[1] = QPointF(self.startPoint.x(), point.y())
            if self.endGripItem.m_location in ["left", "right"]:
                point = self.points[len(self.points) - 2]
                self.points[len(self.points) - 2] = QPointF(point.x(), self.endPoint.y())
            else:
                point = self.points[len(self.points) - 2]
                self.points[len(self.points) - 2] = QPointF(self.endPoint.x(), point.y())

    def shape(self):
        """generates outline for path
        """
        qp = QPainterPathStroker()
        qp.setWidth(8)
        path = qp.createStroke(self.path())
        return path

    def paint(self, painter, option, widget):
        color = Qt.red if self.isSelected() else Qt.black
        painter.setPen(QPen(color, 2, self.penStyle))
        painter.drawPath(self.path())

        # To paint path of shape
        # painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine))
        # painter.drawPath(self.shape())
        if self.isSelected():
            self.showGripItem()
            self._selected = True
        elif self._selected:
            self.hideGripItem()
            self._selected = False

    def movePoints(self, index, movement):
        """move points of line
        """
        for i in [index, index + 1]:
            point = self.points[i]
            point += movement
            self.points[i] = point
            self.updatePath()
        self.updateGrabber([index])

    def addGrabber(self):
        """adds grabber when line is moved
        """
        if self.startGripItem.m_location in ["left", "right"]:
            direction = [Qt.Horizontal, Qt.Vertical]
        else:
            direction = [Qt.Vertical, Qt.Horizontal]
        for i in range(1, len(self.points) - 2):
            item = Grabber(self, i, direction[i - 1])
            item.setParentItem(self)
            item.setPos(self.pos())
            self.scene().addItem(item)
            self.m_grabbers.append(item)

    def updateGrabber(self, index_no_updates=None):
        """updates all grabber of line when it is moved
        """
        index_no_updates = index_no_updates or []
        for grabber in self.m_grabbers:
            if grabber.m_index in index_no_updates: continue
            index = grabber.m_index
            startPoint = self.points[index]
            endPoint = self.points[index + 1]
            pos = (startPoint + endPoint) / 2
            grabber.setEnabled(False)
            grabber.setPos(pos)
            grabber.setEnabled(True)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSceneHasChanged and self.scene():
            # self.addGrabber()
            # self.updateGrabber()
            return
        return super(Line, self).itemChange(change, value)

    def updateLine(self, startPoint=None, endPoint=None):

        """This function is used to update connecting line when it add on
           canvas and when it's grip item  moves
        :return:
        """
        self.prepareGeometryChange()
        if startPoint:
            self.startPoint = startPoint
        if endPoint:
            self.endPoint = endPoint
            self.createPath()
            self.updateGrabber()
            return

        if self.startGripItem and self.endGripItem:
            item = self.startGripItem
            self.startPoint = item.parentItem().mapToScene(item.pos())
            item = self.endGripItem
            self.endPoint = item.parentItem().mapToScene(item.pos())
            self.updatePath()
            self.updateGrabber()

    def removeFromCanvas(self):
        """This function is used to remove connecting line from canvas
        :return:
        """
        if self.scene():
            self.scene().removeItem(self)

    def showGripItem(self):
        """hides grip items which contains line
        """
        if self.startGripItem: self.startGripItem.show()
        if self.endGripItem: self.endGripItem.show()
        # for grabber in self.m_grabber:
        #     grabber.setSelected(True)

    def hideGripItem(self):
        """hides grip items which contains line
        """
        if self.startGripItem: self.startGripItem.hide()
        if self.endGripItem: self.endGripItem.hide()

    def setStartGripItem(self, item):
        self.startGripItem = item

    def setEndGripItem(self, item):
        self.endGripItem = item

    def setPenStyle(self, style):
        """change current pen style for line"""
        self.penStyle = style