#!/usr/bin/env python3

import PyQt5.QtCore as QtCore
import PyQt5.QtGui as QtGui
import xasy2asy as xasy2asy

import PrimitiveShape
import math

import Widg_addPolyOpt
import Widg_addLabel


class InplaceObjProcess(QtCore.QObject):
   objectCreated = QtCore.pyqtSignal(QtCore.QObject)
   objectUpdated = QtCore.pyqtSignal()

   def __init__(self, parent=None):
       super().__init__(parent)
       self._active = False
       pass

   @property
   def active(self):
       return self._active

   def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None):
       raise NotImplementedError

   def mouseMove(self, pos, event: QtGui.QMouseEvent):
       raise NotImplementedError

   def mouseRelease(self):
       raise NotImplementedError

   def forceFinalize(self):
       raise NotImplementedError

   def getPreview(self):
       return None

   def getObject(self):
       raise NotImplementedError

   def getXasyObject(self):
       raise NotImplementedError

   def postDrawPreview(self, canvas: QtGui.QPainter):
       pass

   def createOptWidget(self, info):
       return None


class AddCircle(InplaceObjProcess):
   def __init__(self, parent=None):
       super().__init__(parent)
       self.center = QtCore.QPointF(0, 0)
       self.radius = 0

   def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None):
       x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos)
       self.radius = 0
       self.center.setX(x)
       self.center.setY(y)
       self.fill = info['fill']
       self._active = True

   def mouseMove(self, pos, event):
       self.radius = PrimitiveShape.PrimitiveShape.euclideanNorm(pos, self.center)

   def mouseRelease(self):
       self.objectCreated.emit(self.getXasyObject())
       self._active = False

   def getPreview(self):
       x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(self.center)
       boundRect = QtCore.QRectF(x - self.radius, y - self.radius, 2 * self.radius, 2 * self.radius)
       # because the internal image is flipped...
       newPath = QtGui.QPainterPath()
       newPath.addEllipse(boundRect)
       # newPath.addRect(boundRect)
       return newPath

   def getObject(self):
       return PrimitiveShape.PrimitiveShape.circle(self.center, self.radius)

   def getXasyObject(self):
       if self.fill:
           newObj = xasy2asy.xasyFilledShape(self.getObject(), None)
       else:
           newObj = xasy2asy.xasyShape(self.getObject(), None)
       return newObj

   def forceFinalize(self):
       self.mouseRelease()


class AddLabel(InplaceObjProcess):
   def __init__(self, parent=None):
       super().__init__(parent)
       self.alignMode = None
       self.opt = None
       self.text = None
       self.anchor = QtCore.QPointF(0, 0)
       self._active = False
       self.fontSize = 12

   def createOptWidget(self, info):
       self.opt = Widg_addLabel.Widg_addLabel(info)
       return self.opt

   def getPreview(self):
       return None

   def mouseRelease(self):
       self.objectCreated.emit(self.getXasyObject())
       self._active = False

   def mouseMove(self, pos, event):
       x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos)
       self.anchor.setX(x)
       self.anchor.setY(y)

   def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None):
       if self.opt is not None:
           self.text = self.opt.labelText
       x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos)
       self.anchor.setX(x)
       self.anchor.setY(y)

       self.alignMode = info['align']
       self.fontSize = info['fontSize']
       self._active = True

   def getObject(self):
       finalTuple = PrimitiveShape.PrimitiveShape.pos_to_tuple(self.anchor)
       return {'txt': self.text, 'align': str(self.alignMode), 'anchor': finalTuple}

   def getXasyObject(self):
       text = self.text
       align = str(self.alignMode)
       anchor = PrimitiveShape.PrimitiveShape.pos_to_tuple(self.anchor)
       newLabel = xasy2asy.xasyText(text=text, location=anchor, pen=None,
                               align=align, asyengine=None, fontsize=self.fontSize)
       newLabel.asyfied = False
       return newLabel

   def forceFinalize(self):
       self.mouseRelease()


class AddBezierShape(InplaceObjProcess):
   def __init__(self, parent=None):
       super().__init__(parent)
       self.asyengine = None
       self.basePath = None
       self.basePathPreview = None
       self.closedPath = None
       self.info = None
       self.fill = False
       self.opt = None

       # list of "committed" points with Linkage information.
       # Linkmode should be to the last point.
       # (x, y, linkmode), (u, v, lm2) <==> (x, y) <=lm2=> (u, v)
       self.pointsList = []
       self.currentPoint = QtCore.QPointF(0, 0)
       self.pendingPoint = None
       self.useLegacy = False

   def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None):
       x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos)
       self.currentPoint.setX(x)
       self.currentPoint.setY(y)
       self.info = info

       if not self._active:
           self._active = True
           self.fill = info['fill']
           self.asyengine = info['asyengine']
           self.closedPath = info['closedPath']
           self.useBezierBase = info['useBezier']
           self.useLegacy = self.info['options']['useLegacyDrawMode']
           self.pointsList.clear()
           self.pointsList.append((x, y, None))
       else:
           # see http://doc.qt.io/archives/qt-4.8/qt.html#MouseButton-enum
           if (int(mouseEvent.buttons()) if mouseEvent is not None else 0) & 0x2 and self.useLegacy:
               self.forceFinalize()

   def _getLinkType(self):
       if self.info['useBezier']:
           return '..'
       else:
           return '--'

   def mouseMove(self, pos, event):
       # in postscript coords.
       if self._active:
           x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos)

           if self.useLegacy or int(event.buttons()) != 0:
               self.currentPoint.setX(x)
               self.currentPoint.setY(y)
           else:
               self.forceFinalize()


   def createOptWidget(self, info):
       return None
       # self.opt = Widg_addBezierInPlace.Widg_addBezierInplace(info)
       # return self.opt

   def finalizeClosure(self):
       if self.active:
           self.closedPath = True
           self.forceFinalize()

   def mouseRelease(self):
       x, y = self.currentPoint.x(), self.currentPoint.y()
       self.pointsList.append((x, y, self._getLinkType()))
       # self.updateBasePath()

   def updateBasePath(self):
       self.basePath = xasy2asy.asyPath(asyengine=self.asyengine, forceCurve=self.useBezierBase)
       newNode = [(x, y) for x, y, _ in self.pointsList]
       newLink = [lnk for *args, lnk in self.pointsList[1:]]
       if self.useLegacy:
           newNode += [(self.currentPoint.x(), self.currentPoint.y())]
           newLink += [self._getLinkType()]
       if self.closedPath:
           newNode.append('cycle')
           newLink.append(self._getLinkType())
       self.basePath.initFromNodeList(newNode, newLink)

       if self.useBezierBase:
           self.basePath.computeControls()

   def updateBasePathPreview(self):
       self.basePathPreview = xasy2asy.asyPath(
           asyengine=self.asyengine, forceCurve=self.useBezierBase)
       newNode = [(x, y) for x, y, _ in self.pointsList] + [(self.currentPoint.x(), self.currentPoint.y())]
       newLink = [lnk for *args, lnk in self.pointsList[1:]] + [self._getLinkType()]
       if self.closedPath:
           newNode.append('cycle')
           newLink.append(self._getLinkType())
       self.basePathPreview.initFromNodeList(newNode, newLink)

       if self.useBezierBase:
           self.basePathPreview.computeControls()

   def forceFinalize(self):
       self.updateBasePath()
       self._active = False
       self.pointsList.clear()
       self.objectCreated.emit(self.getXasyObject())
       self.basePath = None

   def getObject(self):
       if self.basePath is None:
           raise RuntimeError('BasePath is None')
       self.basePath.asyengine = self.asyengine
       return self.basePath

   def getPreview(self):
       if self._active:
           if self.pointsList:
               self.updateBasePathPreview()
               newPath = self.basePathPreview.toQPainterPath()
               return newPath

   def getXasyObject(self):
       if self.fill:
           return xasy2asy.xasyFilledShape(self.getObject(), None)
       else:
           return xasy2asy.xasyShape(self.getObject(), None)


class AddPoly(InplaceObjProcess):
   def __init__(self, parent=None):
       super().__init__(parent)
       self.center = QtCore.QPointF(0, 0)
       self.currPos = QtCore.QPointF(0, 0)
       self.sides = None
       self.inscribed = None
       self.centermode = None
       self.asyengine = None
       self.fill = None
       self.opt = None

   def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None):
       self._active = True
       self.sides = info['sides']
       self.inscribed = info['inscribed']
       self.centermode = info['centermode']
       self.fill = info['fill']


       x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos)
       self.center.setX(x)
       self.center.setY(y)
       self.currPos = QtCore.QPointF(self.center)

   def mouseMove(self, pos, event):
       x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos)
       self.currPos.setX(x)
       self.currPos.setY(y)

   def mouseRelease(self):
       if self.active:
           self.objectCreated.emit(self.getXasyObject())
           self._active = False

   def forceFinalize(self):
       self.mouseRelease()

   def getObject(self):
       if self.inscribed:
           return PrimitiveShape.PrimitiveShape.inscribedRegPolygon(self.sides, self.center, self._rad(),
                                                                    self._angle())
       else:
           return PrimitiveShape.PrimitiveShape.exscribedRegPolygon(self.sides, self.center, self._rad(),
                                                                    self._angle())

   def getPreview(self):
       if self.inscribed:
           poly = PrimitiveShape.PrimitiveShape.inscribedRegPolygon(self.sides, self.center, self._rad(),
                                                                    self._angle(), qpoly=True)
       else:
           poly = PrimitiveShape.PrimitiveShape.exscribedRegPolygon(self.sides, self.center, self._rad(),
                                                                    self._angle(), qpoly=True)
       newPath = QtGui.QPainterPath()
       newPath.addPolygon(poly)
       return newPath

   def createOptWidget(self, info):
       self.opt = Widg_addPolyOpt.Widg_addPolyOpt(info)
       return self.opt

   def _rad(self):
       return PrimitiveShape.PrimitiveShape.euclideanNorm(self.currPos, self.center)

   def _angle(self):
       dist_x = self.currPos.x() - self.center.x()
       dist_y = self.currPos.y() - self.center.y()
       if dist_x == 0 and dist_y == 0:
           return 0
       else:
           return math.atan2(dist_y, dist_x)

   def getXasyObject(self):
       if self.fill:
           newObj = xasy2asy.xasyFilledShape(self.getObject(), None)
       else:
           newObj = xasy2asy.xasyShape(self.getObject(), None)
       return newObj

class AddFreehand(InplaceObjProcess):
   # TODO: At the moment this is just a copy-paste of the AddBezierObj.
   # Must find a better algorithm for constructing the obj rather than
   # a node for every pixel the mouse moves.
   def __init__(self, parent=None):
       super().__init__(parent)
       self.asyengine = None
       self.basePath = None
       self.basePathPreview = None
       self.closedPath = None
       self.info = None
       self.fill = False
       self.opt = None

       # list of "committed" points with Linkage information.
       # Linkmode should be to the last point.
       # (x, y, linkmode), (u, v, lm2) <==> (x, y) <=lm2=> (u, v)
       self.pointsList = []
       self.currentPoint = QtCore.QPointF(0, 0)
       self.pendingPoint = None
       self.useLegacy = False

   def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None):
       x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos)
       self.currentPoint.setX(x)
       self.currentPoint.setY(y)
       self.info = info

       if not self._active:
           self._active = True
           self.fill = info['fill']
           self.asyengine = info['asyengine']
           self.closedPath = info['closedPath']
           self.useBezierBase = info['useBezier']
           self.useLegacy = self.info['options']['useLegacyDrawMode']
           self.pointsList.clear()
           self.pointsList.append((x, y, None))
       else:
           # see http://doc.qt.io/archives/qt-4.8/qt.html#MouseButton-enum
           if (int(mouseEvent.buttons()) if mouseEvent is not None else 0) & 0x2 and self.useLegacy:
               self.forceFinalize()

   def _getLinkType(self):
       if self.info['useBezier']:
           return '..'
       else:
           return '--'

   def mouseMove(self, pos, event):
       # in postscript coords.
       if self._active:
           x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos)

           if self.useLegacy or int(event.buttons()) != 0:
               self.currentPoint.setX(x)
               self.currentPoint.setY(y)
               self.pointsList.append((x, y, self._getLinkType()))


   def createOptWidget(self, info):
       return None

   def mouseRelease(self):
       self.updateBasePath()
       self._active = False
       self.pointsList.clear()
       self.objectCreated.emit(self.getXasyObject())
       self.basePath = None

   def updateBasePath(self):
       self.basePath = xasy2asy.asyPath(asyengine=self.asyengine, forceCurve=self.useBezierBase)
       newNode = [(x, y) for x, y, _ in self.pointsList]
       newLink = [lnk for *args, lnk in self.pointsList[1:]]
       if self.useLegacy:
           newNode += [(self.currentPoint.x(), self.currentPoint.y())]
           newLink += [self._getLinkType()]
       if self.closedPath:
           newNode.append('cycle')
           newLink.append(self._getLinkType())
       self.basePath.initFromNodeList(newNode, newLink)

       if self.useBezierBase:
           self.basePath.computeControls()

   def updateBasePathPreview(self):
       self.basePathPreview = xasy2asy.asyPath(
           asyengine=self.asyengine, forceCurve=self.useBezierBase)
       newNode = [(x, y) for x, y, _ in self.pointsList] + [(self.currentPoint.x(), self.currentPoint.y())]
       newLink = [lnk for *args, lnk in self.pointsList[1:]] + [self._getLinkType()]
       if self.closedPath:
           newNode.append('cycle')
           newLink.append(self._getLinkType())
       self.basePathPreview.initFromNodeList(newNode, newLink)

       if self.useBezierBase:
           self.basePathPreview.computeControls()

   def getObject(self):
       if self.basePath is None:
           raise RuntimeError('BasePath is None')
       self.basePath.asyengine = self.asyengine
       return self.basePath

   def getPreview(self):
       if self._active:
           if self.pointsList:
               self.updateBasePathPreview()
               newPath = self.basePathPreview.toQPainterPath()
               return newPath

   def getXasyObject(self):
       self.fill = False
       return xasy2asy.xasyShape(self.getObject(), None)