from __future__ import annotations

import uuid

from pyqtgraph import TextItem, mkPen, GraphicsObject, mkBrush
from numpy import log10

from gui_qt.Qt import QtCore, QtGui
from gui_qt.lib.pg_objects import LogInfiniteLine, PlotItem


__all__ = ['LineObject', 'MultipointObject', 'RectangleObject', 'TextObject', 'EllipseObject']

from nmreval.lib.colors import BaseColor


class BaseObject:
    def __init__(self, color=None):
        self.id = str(uuid.uuid4())
        self.color = color

        self.drawing = None

    def __str__(self):
        raise NotImplementedError

    def get_values(self):
        raise NotImplementedError

    def set_values(self, *args, **kwargs):
        raise NotImplementedError


class LineObject(BaseObject):
    def __init__(self, pos: float, angle: float, **kwargs):
        super().__init__(**kwargs)

        self.pos = pos
        self.angle = angle

        self.drawing = LogInfiniteLine(pos=self.pos, angle=self.angle, pen=mkPen(color=self.color.rgb()))

    def __str__(self):
        return f'{"x" if self.angle==90 else "y"}={self.pos}'

    def get_values(self):
        return {'type': 'line', 'color': self.color, 'pos': self.pos, 'angle': self.angle}

    def set_values(self, pos=None, angle=None, color=None):
        if pos != self.pos:
            self.drawing.setPos(pos)

        if angle != self.angle:
            self.drawing.setAngle(angle)

        if color != self.color:
            self.drawing.setPen(mkPen(color.rgb()))


class MultipointObject(BaseObject):
    def __init__(self, pts: list, closed: bool = False, **kwargs):
        super().__init__(**kwargs)
        x, y = zip(*pts)
        self.closed = closed

        if self.closed:
            x += (x[0],)
            y += (y[0],)
        self._x = x
        self._y = y

        self.drawing = PlotItem(x=self._x, y=self._y, pen=mkPen(color=self.color.rgb()))

    def __str__(self):
        return f'{len(self._y)-int(self.closed)}-pts'

    def get_values(self):
        dic = {'type': 'multipts', 'color': self.color, 'x': None, 'y': None, 'closed': self.closed}

        if self.closed:
            dic['x'] = self._x[:-1]
            dic['y'] = self._y[:-1]
        else:
            dic['x'] = self._x
            dic['y'] = self._y
        return dic

    def set_values(self, pts: list, closed: bool = None, color=None):

        if color != self.color:
            self.drawing.setPen(mkPen(color.rgb()))

        x, y = zip(*pts)

        self._x = x
        self._y = y

        if closed:
            self._x += (x[0],)
            self._y += (y[0],)

        self.drawing.setData(self._x, self._y)


class Rectangle(GraphicsObject):
    # adapted from pyqtgraph example on custom objects

    def __init__(self, left: tuple, right: tuple, color: BaseColor, fill: BaseColor):
        GraphicsObject.__init__(self)

        self.log_mode = [False, False]
        self.setData(left, right, color, fill)

    def generatePicture(self):
        ## pre-computing a QPicture object allows paint() to run much more quickly,
        ## rather than re-drawing the shapes every time.
        print(self.x_disp, self.y_disp, self.dx, self.dy, self.color, self.fill)
        self.picture = QtGui.QPicture()
        p = QtGui.QPainter(self.picture)
        if self.fill is not None:
            p.setBrush(mkBrush(self.fill.rgb()))
        p.setPen(mkPen(self.color.rgb()))
        p.drawRect(QtCore.QRectF(self.x_disp, self.y_disp, self.dx, self.dy))
        p.end()

    def paint(self, p, *_):
        p.drawPicture(0, 0, self.picture)

    def boundingRect(self):
        ## boundingRect _must_ indicate the entire area that will be drawn on
        ## or else we will get artifacts and possibly crashing.
        ## (in this case, QPicture does all the work of computing the bouning rect for us)
        return QtCore.QRectF(self.picture.boundingRect())

    def setLogMode(self, x_mode, y_mode):
        if self.log_mode == [x_mode, y_mode]:
            return

        if self.log_mode[0] != x_mode:
            if x_mode:
                x0 = self.p0[0]
                xmax = log10(x0+self.delta[0])
                self.x_disp = log10(x0)
                self.dx = xmax - self.x_disp
            else:
                self.x_disp = self.p0[0]
                self.dx = self.delta[0]

        if self.log_mode[1] != y_mode:
            if y_mode:
                y0 = self.p0[1]
                ymax = log10(y0 + self.delta[1])
                self.y_disp = log10(y0)
                self.dy = ymax - self.y_disp
            else:
                self.y_disp = self.p0[1]
                self.dy = self.delta[1]

        self.generatePicture()

        self.log_mode = [x_mode, y_mode]

        self.informViewBoundsChanged()

    def setData(self, left, right, color, fill):
        self.p0 = left
        self.delta = [right[0]-self.p0[0], right[1]-self.p0[1]]

        self.x_disp = self.p0[0]
        self.dx = self.delta[0]
        self.y_disp = self.p0[1]
        self.dy = self.delta[1]

        self.color = color
        self.fill = fill

        l = [self.log_mode[0], self.log_mode[1]]
        self.log_mode = [None, None]

        self.setLogMode(*l)


class RectangleObject(BaseObject):
    def __init__(self, left: tuple, right: tuple, fill: BaseColor, **kwargs):
        super().__init__(**kwargs)
        self.fill = fill
        self.left = left
        self.right = right

        self.drawing = Rectangle(left, right, self.color, self.fill)

    def __str__(self):
        return f'Rectangle {self.left} to {self.right}'

    def get_values(self):
        return {'type': 'rectangle', 'color': self.color, 'left': self.left, 'right': self.right, 'fill': self.fill}

    def set_values(self, left=None, right=None, color=None, fill=None):
        if left is not None:
            self.left = left

        if right is not None:
            self.right = right

        if fill is not None:
            self.fill = fill

        if color is not None:
            self.color = color

        self.drawing.setData(self.left, self.right, self.color, self.fill)


class TextObject(BaseObject):
    def __init__(self, text, pos, **kwargs):
        super().__init__(**kwargs)

        self.pos = pos
        self.drawing = TextItem(text, anchor=(0.5, 0.5), color=self.color.rgb())
        self.drawing.setPos(*pos)

    def __str__(self):
        return f'{self.drawing.toPlainText()} at {self.pos}'


class EllipseObject(BaseObject):
    def __init__(self, center: tuple, axes: tuple, fill: BaseColor, **kwargs):
        super().__init__(**kwargs)

        self.center = center
        self.axes = axes
        self.fill = fill

        self.drawing = Ellipse(self.center, self.axes, self.color, self.fill)

    def __str__(self):
        return f'Ellipse at {self.center}'

    def get_values(self):
        return {'type': 'ellipse', 'color': self.color, 'center': self.center, 'axes': self.axes, 'fill': self.fill}

    def set_values(self, center=None, axes=None, color=None, fill=None):
        if center is not None:
            self.center = center

        if axes is not None:
            self.axes = axes

        if fill is not None:
            self.fill = fill

        if color is not None:
            self.color = color

        self.drawing.setData(self.center, self.axes, self.color, self.fill)


class Ellipse(GraphicsObject):
    # adapted from pyqtgraph example on custom objects

    def __init__(self, center: tuple, axes: tuple, color: BaseColor, fill: BaseColor):
        GraphicsObject.__init__(self)

        self.log_mode = [False, False]

        self.setData(center, axes, color, fill)

        self.generatePicture()

    def generatePicture(self):
        ## pre-computing a QPicture object allows paint() to run much more quickly,
        ## rather than re-drawing the shapes every time.
        self.picture = QtGui.QPicture()
        p = QtGui.QPainter(self.picture)
        p.setPen(mkPen(self.color.rgb()))
        if self.fill is not None:
            p.setBrush(mkBrush(self.fill.rgb()))
        p.drawEllipse(QtCore.QPointF(*self.center_disp), *self.ax_disp)
        p.end()

    def paint(self, p, *_):
        p.drawPicture(0, 0, self.picture)

    def boundingRect(self):
        ## boundingRect _must_ indicate the entire area that will be drawn on
        ## or else we will get artifacts and possibly crashing.
        ## (in this case, QPicture does all the work of computing the bouning rect for us)
        return QtCore.QRectF(self.picture.boundingRect())

    def setLogMode(self, x_mode, y_mode):
        if self.log_mode == [x_mode, y_mode]:
            return

        if self.log_mode[0] != x_mode:
            if x_mode:
                x0 = self.center[0]
                xmax = log10(x0+self.ax[0])
                self.center_disp = (log10(x0), self.center_disp[1])
                self.ax_disp = (xmax, self.ax_disp[1])
            else:
                self.center_disp = (self.center[0], self.center_disp[1])
                self.ax_disp = (self.ax[0], self.ax_disp[1])

        if self.log_mode[1] != y_mode:
            if y_mode:
                y0 = self.center[1]
                ymax = log10(y0+self.ax[1])
                self.center_disp = (self.center_disp[0], log10(y0))
                self.ax_disp = (self.ax_disp[0], ymax)
            else:
                self.ax_disp = (self.ax_disp[0], self.ax[1])
                self.center_disp = (self.center_disp[0], self.center[1])

        self.generatePicture()

        self.log_mode = [x_mode, y_mode]

        self.informViewBoundsChanged()

    def setData(self, center, axes, color, fill):
        self.center = center
        self.ax = axes

        self.center_disp = self.center
        self.ax_disp = self.ax

        self.color = color
        self.fill = fill

        l = [self.log_mode[0], self.log_mode[1]]
        self.log_mode = [None, None]

        self.setLogMode(*l)
