# prevent circular imports
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from ..FlowView import FlowView

from typing import Dict, Optional

from qtpy.QtWidgets import QGraphicsItem
from qtpy.QtGui import QPen, QPainter, QColor, QPainterPath
from qtpy.QtCore import Qt, QRectF, QPointF, QLineF
from ...utils import MovementEnum, generate_name

class DrawingObject(QGraphicsItem):
    """GUI implementation for 'drawing objects' in the scene, written by hand using a stylus pen"""

    def __init__(self, flow_view: FlowView, load_data: Optional[Dict] = None):
        super(DrawingObject, self).__init__()

        self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable |
                      QGraphicsItem.ItemSendsScenePositionChanges)
        self.setAcceptHoverEvents(True)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)  # for rendering flow pictures

        self.flow_view = flow_view
        self.color = None
        self.base_stroke_weight = None
        self.type_ = 'pen'  # so far the only available, but I already save it so I could add more types in the future
        self.points = []
        self.stroke_weights = []
        self.pen_stroke_weight = 0  # approx. avg of self.stroke_weights
        self.rect = None
        self.path: Optional[QPainterPath] = None
        self.width = -1
        self.height = -1
        self.finished = False

        # viewport_pos enables global floating points for precise pen positions
        self.viewport_pos: Optional[QPointF] = None
        # if the drawing gets loaded, its correct global floating pos is already correct (gets set by flow then)

        self.movement_state = None  # ugly - should get replaced later, see NodeItem, same issue
        self.movement_pos_from = None

        if load_data is not None: 
            if 'viewport pos' in load_data:
                self.viewport_pos = load_data['viewport pos']

            if 'points' in load_data:
                p_c = load_data['points']
                for p in p_c:
                    if type(p) == list:
                        x = p[0]
                        y = p[1]
                        w = p[2]
                        self.points.append(QPointF(x, y))
                        self.stroke_weights.append(w)
                    elif type(p) == dict:  # backwards compatibility
                        x = p['x']
                        y = p['y']
                        w = p['w']
                        self.points.append(QPointF(x, y))
                        self.stroke_weights.append(w)
                self.finished = True

            self.color = QColor(load_data['color'])
            self.base_stroke_weight = load_data['base stroke weight']

    def __str__(self):
        return generate_name(self, 'Drawing')
    
    def paint(self, painter, option, widget=None):

        if not self.finished:
            for i in range(1, len(self.points)):
                pen = QPen()
                pen.setColor(self.color)
                pen_width = (self.stroke_weights[i] + 0.2) * self.base_stroke_weight
                pen.setWidthF(pen_width)
                if i == 1 or i == len(self.points) - 1:
                    pen.setCapStyle(Qt.RoundCap)
                painter.setPen(pen)
                painter.setRenderHint(QPainter.Antialiasing)
                painter.setRenderHint(QPainter.HighQualityAntialiasing)
                painter.drawLine(self.points[i - 1], self.points[i])
            return

        if not self.path and self.finished:
            if len(self.points) == 0:
                return

            self.path = QPainterPath()
            self.path.moveTo(self.points[0])
            avg_weight = self.stroke_weights[0]
            for i in range(1, len(self.points)):
                self.path.lineTo(self.points[i])
                avg_weight += self.stroke_weights[i]
            self.pen_stroke_weight = (avg_weight/len(self.points) + 0.2)*self.base_stroke_weight

        pen = QPen()
        pen.setColor(self.color)
        pen.setWidthF(self.pen_stroke_weight)
        painter.setPen(pen)
        painter.setRenderHint(QPainter.Antialiasing)
        # painter.setRenderHint(QPainter.HighQualityAntialiasing)
        painter.drawPath(self.path)

    def append_point(self, posF_in_view: QPointF) -> bool:
        """
        Only used for active drawing.
        Appends a point (floating, in viewport coordinates) only if the distance to the last one isn't too small
        """

        p: QPointF = (self.viewport_pos + posF_in_view) - self.pos()
        p.setX(round(p.x(), 2))
        p.setY(round(p.y(), 2))

        if len(self.points) > 0:
            line = QLineF(self.points[-1], p)
            if line.length() < 0.5:
                return False

        self.points.append(p)
        return True

    def finish(self):
        """
        Computes the correct center position and updates the relative position for all points.
        """

        # Correct bounding rect (so far (0,0) is at the start of the line, but it should be in the middle)

        rect_center = self.get_points_rect_center()
        for p in self.points:
            p.setX(p.x()-rect_center.x())
            p.setY(p.y()-rect_center.y())
        self.setPos(self.pos()+rect_center)

        self.rect = self.get_points_rect()

        self.finished = True

    def get_points_rect(self):
        """Computes the 'bounding rect' for all points"""

        if len(self.points) == 0:
            return QRectF(0, 0, 0, 0)
        x_coords = [p.x() for p in self.points]
        y_coords = [p.y() for p in self.points]
        left = min(x_coords)
        right = max(x_coords)
        up = min(y_coords)
        down = max(y_coords)

        rect = QRectF(left, up, right - left, down - up)

        self.width = rect.width()
        self.height = rect.height()
        return rect

    def get_points_rect_center(self):
        """Returns the center point for the 'bounding rect' for all points"""

        return self.get_points_rect().center()

    def boundingRect(self):
        if self.rect:
            return self.rect
        else:
            return self.get_points_rect()

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionChange:
            self.flow_view.viewport().update()
            if self.movement_state == MovementEnum.mouse_clicked:
                self.movement_state = MovementEnum.position_changed

        return QGraphicsItem.itemChange(self, change, value)

    def mousePressEvent(self, event):
        """Used for Moving-Commands in Flow - may be replaced later with a nicer determination of a move action."""

        self.movement_state = MovementEnum.mouse_clicked
        self.movement_pos_from = self.pos()
        return QGraphicsItem.mousePressEvent(self, event)

    def mouseReleaseEvent(self, event):
        """Used for Moving-Commands in Flow - may be replaced later with a nicer determination of a move action."""

        if self.movement_state == MovementEnum.position_changed:
            self.flow_view._move_selected_copmonents__cmd(
                pos_diff=self.pos() - self.movement_pos_from,
                already_moved=True,
            )
        self.movement_state = None
        return QGraphicsItem.mouseReleaseEvent(self, event)

    def data_(self):
        drawing_dict = {
            'pos x': self.pos().x(),
            'pos y': self.pos().y(),
            'color': self.color.name(),
            'type': self.type_,
            'base stroke weight': self.base_stroke_weight
        }
        points_list = []
        for i in range(len(self.points)):
            p = self.points[i]
            points_list.append([p.x(), p.y(), self.stroke_weights[i]])
        drawing_dict['points'] = points_list
        return drawing_dict


# alternative QGraphcsItem-based implementation:
# 
#   ...
#     def paint(self, painter, option, widget=None):
#
#         self.setBrush(Qt.NoBrush)
#
#         if self.finished:
#             super().paint(painter, option,widget)
#         else:
#             if len(self.stroke_weights) == 0:
#                 return
#
#             # pen
#             pen = QPen(self.color)
#             pen.setWidthF(self.stroke_weights[-1])
#             pen.setCapStyle(Qt.RoundCap)
#             self.setPen(pen)
#
#             # path
#             path = QPainterPath(self.points[0])
#             for i, p in enumerate(self.points, start=1):
#                 path.lineTo(p)
#             self.setPath(path)
#             self.path_generated = True
#
#             super().paint(painter, option, widget)
#
#   ...
#
#     def finish(self):
#
#         self.finished = True
#         self.update()
# 
# it is a bit worse in appearance but might be a lot faster
