#!/usr/bin/env python3
import PySide6.QtCore as QtCore
import PySide6.QtGui as QtGui
import xasy2asy as xasy2asy
import xasyUtils as xasyUtils
import Widg_editBezier as Web

import InplaceAddObj
import math

class CurrentlySelctedType:
    none = -1
    node = 0
    ctrlPoint = 1

class InteractiveBezierEditor(InplaceAddObj.InplaceObjProcess):
    editAccepted = QtCore.Signal()
    editRejected = QtCore.Signal()

    def __init__(self, parent: QtCore.QObject, obj: xasy2asy.xasyDrawnItem, info: dict=None):
        super().__init__(parent)
        self.info = info or {}
        self.asyPathBackup = xasy2asy.asyPath.fromPath(obj.path)
        self.asyPath = obj.path
        self.curveMode = self.asyPath.containsCurve
        assert isinstance(self.asyPath, xasy2asy.asyPath)
        self.transf = obj.transfKeymap[obj.transfKey][0]
        self._active = True

        self.currentSelMode = None
        # (Node index, Node subindex for )
        self.currentSelIndex = (None, 0)

        self.nodeSelRects = []
        self.ctrlSelRects = []

        self.setSelectionBoundaries()

        self.lastSelPoint = None
        self.preCtrlOffset = None
        self.postCtrlOffset = None
        self.inTransformMode = False

        self.opt = None
        self.obj = obj

        self.prosectiveNodes = []
        self.prospectiveCtrlPts = []

        #The magnification isn't being set. Here I'm manually setting it to be the square root of the determinant.
        self.info['magnification'] = math.sqrt(abs(self.transf.xx * self.transf.yy - self.transf.xy * self.transf.yx))

    def setSelectionBoundaries(self):
        self.nodeSelRects = self.handleNodeSelectionBounds()

        if self.curveMode:
            self.ctrlSelRects = self.handleCtrlSelectionBoundaries()

    def handleNodeSelectionBounds(self):
        nodeSelectionBoundaries = []

        for node in self.asyPath.nodeSet:
            if node == 'cycle':
                nodeSelectionBoundaries.append(None)
                continue

            selEpsilon = 6/self.info['magnification']
            newRect = QtCore.QRectF(0, 0, 2 * selEpsilon, 2 * selEpsilon)
            x, y = self.transf * node
            x = int(round(x))
            y = int(round(y))
            newRect.moveCenter(QtCore.QPoint(x, y))

            nodeSelectionBoundaries.append(newRect)

        return nodeSelectionBoundaries

    def handleCtrlSelectionBoundaries(self):
        ctrlPointSelBoundaries = []

        for nodes in self.asyPath.controlSet:
            nodea, nodeb = nodes

            selEpsilon = 6/self.info['magnification']

            newRect = QtCore.QRectF(0, 0, 2 * selEpsilon, 2 * selEpsilon)
            newRectb = QtCore.QRectF(0, 0, 2 * selEpsilon, 2 * selEpsilon)

            x, y = self.transf * nodea
            x2, y2 = self.transf * nodeb

            x = int(round(x))
            y = int(round(y))

            x2 = int(round(x2))
            y2 = int(round(y2))

            newRect.moveCenter(QtCore.QPoint(x, y))
            newRectb.moveCenter(QtCore.QPoint(x2, y2))

            ctrlPointSelBoundaries.append((newRect, newRectb))

        return ctrlPointSelBoundaries


    def postDrawPreview(self, canvas: QtGui.QPainter):
        assert canvas.isActive()

        dashedPen = QtGui.QPen(QtCore.Qt.PenStyle.DashLine)
        dashedPen.setCosmetic(True)
        # draw the base points
        canvas.save()
        canvas.setWorldTransform(self.transf.toQTransform(), True)

        epsilonSize = 6/self.info['magnification']

        if self.info['autoRecompute'] or not self.curveMode:
            ctrlPtsColor = 'gray'
        else:
            ctrlPtsColor = 'red'

        canvas.setPen(dashedPen)

        canvas.drawPath(self.asyPath.toQPainterPath())

        nodePen = QtGui.QPen(QtGui.QColor('blue'))
        nodePen.setCosmetic(True)

        ctlPtsPen = QtGui.QPen(QtGui.QColor(ctrlPtsColor))
        ctlPtsPen.setCosmetic(True)

        for index in range(len(self.asyPath.nodeSet)):
            point = self.asyPath.nodeSet[index]

            if point != 'cycle':
                basePoint = QtCore.QPointF(point[0], point[1])
                canvas.setPen(nodePen)
                canvas.drawEllipse(basePoint, epsilonSize, epsilonSize)
            else:
                point = self.asyPath.nodeSet[0]
                basePoint = QtCore.QPointF(point[0], point[1])
            if self.curveMode:
                if index != 0:
                    canvas.setPen(ctlPtsPen)
                    postCtrolSet = self.asyPath.controlSet[index - 1][1]
                    postCtrlPoint = QtCore.QPointF(postCtrolSet[0], postCtrolSet[1])
                    canvas.drawEllipse(postCtrlPoint, epsilonSize, epsilonSize)

                    canvas.setPen(dashedPen)
                    canvas.drawLine(basePoint, postCtrlPoint)

                if index != len(self.asyPath.nodeSet) - 1:
                    canvas.setPen(ctlPtsPen)
                    preCtrlSet = self.asyPath.controlSet[index][0]
                    preCtrlPoint = QtCore.QPointF(preCtrlSet[0], preCtrlSet[1])
                    canvas.drawEllipse(preCtrlPoint, epsilonSize, epsilonSize)

                    canvas.setPen(dashedPen)
                    canvas.drawLine(basePoint, preCtrlPoint)

        canvas.restore()

    def getPreAndPostCtrlPts(self, index):
        isCycle = self.asyPath.nodeSet[-1] == 'cycle'

        if index == 0 and not isCycle:
            preCtrl = None
        else:
            preCtrl = self.asyPath.controlSet[index - 1][1]

        if index == len(self.asyPath.nodeSet) - 1 and not isCycle:
            postCtrl = None
        else:
            postCtrl = self.asyPath.controlSet[index % (len(self.asyPath.nodeSet) - 1)][0]

        return preCtrl, postCtrl

    def findLinkingNode(self, index, subindex):
        """index and subindex are of the control points list."""
        if subindex == 0:
            return index
        else:
            if self.asyPath.nodeSet[index + 1] == 'cycle':
                return 0
            else:
                return index + 1

    def resetObj(self):
        self.asyPath.setInfo(self.asyPathBackup)
        self.setSelectionBoundaries()

    def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None):
        self.lastSelPoint = pos
        if self.inTransformMode:
            return

        if self.prosectiveNodes and not self.inTransformMode:
            self.currentSelMode = CurrentlySelctedType.node
            self.currentSelIndex = (self.prosectiveNodes[0], 0)
            self.inTransformMode = True
            self.parentNodeIndex = self.currentSelIndex[0]
        elif self.prospectiveCtrlPts and not self.inTransformMode:
            self.currentSelMode = CurrentlySelctedType.ctrlPoint
            self.currentSelIndex = self.prospectiveCtrlPts[0]
            self.inTransformMode = True
            self.parentNodeIndex = self.findLinkingNode(*self.currentSelIndex)

        if self.inTransformMode:
            parentNode = self.asyPath.nodeSet[self.parentNodeIndex]

            # find the offset of each control point to the node
            if not self.curveMode:
                return

            preCtrl, postCtrl = self.getPreAndPostCtrlPts(self.parentNodeIndex)

            if parentNode == 'cycle':
                parentNode = self.asyPath.nodeSet[0]
                self.parentNodeIndex = 0

            if preCtrl is not None:
                self.preCtrlOffset = xasyUtils.funcOnList(
                    preCtrl, parentNode, lambda a, b: a - b)
            else:
                self.preCtrlOffset = None

            if postCtrl is not None:
                self.postCtrlOffset = xasyUtils.funcOnList(
                    postCtrl, parentNode, lambda a, b: a - b)
            else:
                self.postCtrlOffset = None

    def mouseMove(self, pos, event: QtGui.QMouseEvent):
        if self.currentSelMode is None and not self.inTransformMode:
            # in this case, search for prosective nodes.
            prospectiveNodes = []
            prospectiveCtrlpts = []

            for i in range(len(self.nodeSelRects)):
                rect = self.nodeSelRects[i]
                if rect is None:
                    continue
                if rect.contains(pos):
                    prospectiveNodes.append(i)

            self.prosectiveNodes = prospectiveNodes

            if not self.info['autoRecompute'] and self.curveMode:
                for i in range(len(self.ctrlSelRects)):
                    recta, rectb = self.ctrlSelRects[i]

                    if recta.contains(pos):
                        prospectiveCtrlpts.append((i, 0))

                    if rectb.contains(pos):
                        prospectiveCtrlpts.append((i, 1))

                self.prospectiveCtrlPts = prospectiveCtrlpts
            else:
                self.prospectiveCtrlPts = []


        if self.inTransformMode:
            index, subindex = self.currentSelIndex
            newNode = (self.transf.inverted().toQTransform().map(pos.x(), pos.y()))
            if self.currentSelMode == CurrentlySelctedType.node:
                # static throughout the moving
                if self.asyPath.nodeSet[index] == 'cycle':
                    return

                self.asyPath.setNode(index, newNode)
                # if also move node:

                if self.curveMode:
                    checkPre, checkPost = self.getPreAndPostCtrlPts(index)

                    if 1 == 1: # TODO: Replace this with an option to also move control pts.
                        if checkPre is not None:
                            self.asyPath.controlSet[index - 1][1] = xasyUtils.funcOnList(
                                newNode, self.preCtrlOffset, lambda a, b: a + b
                            )
                        if checkPost is not None:
                            self.asyPath.controlSet[index][0] = xasyUtils.funcOnList(
                                newNode, self.postCtrlOffset, lambda a, b: a + b
                            )

                    if self.info['autoRecompute']:
                        self.quickRecalculateCtrls()


            elif self.currentSelMode == CurrentlySelctedType.ctrlPoint and self.curveMode:
                self.asyPath.controlSet[index][subindex] = newNode
                parentNode = self.asyPath.nodeSet[self.parentNodeIndex]

                if parentNode == 'cycle':
                    parentNode = self.asyPath.nodeSet[0]
                    isCycle = True
                else:
                    isCycle = False

                if self.parentNodeIndex == 0 and self.asyPath.nodeSet[-1] == 'cycle':
                    isCycle = True

                rawNewNode = xasyUtils.funcOnList(newNode, parentNode, lambda a, b: a - b)
                rawAngle = math.atan2(rawNewNode[1], rawNewNode[0])
                newNorm = xasyUtils.twonorm(rawNewNode)


                if self.info['editBezierlockMode'] >= Web.LockMode.angleLock:
                    otherIndex = 1 - subindex       # 1 if 0, 0 otherwise.
                    if otherIndex == 0:
                        if index < (len(self.asyPath.controlSet) - 1) or isCycle:
                            newIndex = 0 if isCycle else index + 1

                            oldOtherCtrlPnt = xasyUtils.funcOnList(
                                self.asyPath.controlSet[newIndex][0], parentNode, lambda a, b: a - b)

                            if self.info['editBezierlockMode'] >= Web.LockMode.angleAndScaleLock:
                                rawNorm = newNorm
                            else:
                                rawNorm = xasyUtils.twonorm(oldOtherCtrlPnt)

                            newPnt = (rawNorm * math.cos(rawAngle + math.pi),
                                rawNorm * math.sin(rawAngle + math.pi))

                            self.asyPath.controlSet[newIndex][0] = xasyUtils.funcOnList(
                                newPnt, parentNode, lambda a, b: a + b)
                    else:
                        if index > 0 or isCycle:
                            newIndex = -1 if isCycle else index - 1
                            oldOtherCtrlPnt = xasyUtils.funcOnList(
                                self.asyPath.controlSet[newIndex][1], parentNode, lambda a, b: a - b)

                            if self.info['editBezierlockMode'] >= Web.LockMode.angleAndScaleLock:
                                rawNorm = newNorm
                            else:
                                rawNorm = xasyUtils.twonorm(oldOtherCtrlPnt)

                            newPnt = (rawNorm * math.cos(rawAngle + math.pi),
                                      rawNorm * math.sin(rawAngle + math.pi))
                            self.asyPath.controlSet[newIndex][1] = xasyUtils.funcOnList(
                                newPnt, parentNode, lambda a, b: a + b)

    def recalculateCtrls(self):
        self.quickRecalculateCtrls()
        self.setSelectionBoundaries()

    def quickRecalculateCtrls(self):
        self.asyPath.controlSet.clear()
        self.asyPath.computeControls()

    def mouseRelease(self):
        if self.inTransformMode:
            self.inTransformMode = False
            self.currentSelMode = None

            self.setSelectionBoundaries()

    def forceFinalize(self):
        self.objectUpdated.emit()

    def createOptWidget(self, info):
        self.opt = Web.Widg_editBezier(self.info, self.curveMode)
        self.opt.ui.btnOk.clicked.connect(self.editAccepted)
        self.opt.ui.btnCancel.clicked.connect(self.editRejected)
        self.opt.ui.btnForceRecompute.clicked.connect(self.recalculateCtrls)

        return self.opt

    def getObject(self):
        pass

    def getXasyObject(self):
        pass
