from math import atan2, degrees

from .. import functions as fn
from ..Point import Point
from ..Qt import QtCore, QtGui, QtWidgets
from .GraphicsObject import GraphicsObject

__all__ = ['TextItem']

class TextItem(GraphicsObject):
    """
    GraphicsItem displaying unscaled text (the text will always appear normal even inside a scaled ViewBox). 
    """
    def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0),
                 border=None, fill=None, angle=0, rotateAxis=None, ensureInBounds=False):
        """
        ================  =================================================================================
        **Arguments:**
        *text*            The text to display
        *color*           The color of the text (any format accepted by pg.mkColor)
        *html*            If specified, this overrides both *text* and *color*
        *anchor*          A QPointF or (x,y) sequence indicating what region of the text box will
                          be anchored to the item's position. A value of (0,0) sets the upper-left corner
                          of the text box to be at the position specified by setPos(), while a value of (1,1)
                          sets the lower-right corner.
        *border*          A pen to use when drawing the border
        *fill*            A brush to use when filling within the border
        *angle*           Angle in degrees to rotate text. Default is 0; text will be displayed upright.
        *rotateAxis*      If None, then a text angle of 0 always points along the +x axis of the scene.
                          If a QPointF or (x,y) sequence is given, then it represents a vector direction
                          in the parent's coordinate system that the 0-degree line will be aligned to. This
                          Allows text to follow both the position and orientation of its parent while still
                          discarding any scale and shear factors.
        *ensureInBounds*  Ensures that the entire TextItem will be visible when using autorange, but may
                          produce runaway scaling in certain circumstances (See issue #2642). Setting to 
                          "True" retains legacy behavior.
        ================  =================================================================================


        The effects of the `rotateAxis` and `angle` arguments are added independently. So for example:

          * rotateAxis=None, angle=0 -> normal horizontal text
          * rotateAxis=None, angle=90 -> normal vertical text
          * rotateAxis=(1, 0), angle=0 -> text aligned with x axis of its parent
          * rotateAxis=(0, 1), angle=0 -> text aligned with y axis of its parent
          * rotateAxis=(1, 0), angle=90 -> text orthogonal to x axis of its parent
        """
                     
        self.anchor = Point(anchor)
        self.rotateAxis = None if rotateAxis is None else Point(rotateAxis)
        #self.angle = 0
        GraphicsObject.__init__(self)
        self.textItem = QtWidgets.QGraphicsTextItem()
        self.textItem.setParentItem(self)
        self._lastTransform = None
        self._lastScene = None

        # Note: The following is pretty scuffed; ideally there would likely be 
        # some inheritance changes, But this is the least-intrusive thing that 
        # works for now
        if ensureInBounds:
            self.dataBounds = None
        
        self._bounds = QtCore.QRectF()
        if html is None:
            self.setColor(color)
            self.setText(text)
        else:
            self.setHtml(html)
        self.fill = fn.mkBrush(fill)
        self.border = fn.mkPen(border)
        self.setAngle(angle)

    def setText(self, text, color=None):
        """
        Set the text of this item. 
        
        This method sets the plain text of the item; see also setHtml().
        """
        if color is not None:
            self.setColor(color)
        self.setPlainText(text)

    def setPlainText(self, text):
        """
        Set the plain text to be rendered by this item. 
        
        See QtWidgets.QGraphicsTextItem.setPlainText().
        """
        if text != self.toPlainText():
            self.textItem.setPlainText(text)
            self.updateTextPos()

    def toPlainText(self):
        return self.textItem.toPlainText()
        
    def setHtml(self, html):
        """
        Set the HTML code to be rendered by this item. 
        
        See QtWidgets.QGraphicsTextItem.setHtml().
        """
        if self.toHtml() != html:
            self.textItem.setHtml(html)
            self.updateTextPos()
        
    def toHtml(self):
        return self.textItem.toHtml()
        
    def setTextWidth(self, *args):
        """
        Set the width of the text.
        
        If the text requires more space than the width limit, then it will be
        wrapped into multiple lines.
        
        See QtWidgets.QGraphicsTextItem.setTextWidth().
        """
        self.textItem.setTextWidth(*args)
        self.updateTextPos()
        
    def setFont(self, *args):
        """
        Set the font for this text. 
        
        See QtWidgets.QGraphicsTextItem.setFont().
        """
        self.textItem.setFont(*args)
        self.updateTextPos()
        
    def setAngle(self, angle):
        """
        Set the angle of the text in degrees.

        This sets the rotation angle of the text as a whole, measured
        counter-clockwise from the x axis of the parent. Note that this rotation
        angle does not depend on horizontal/vertical scaling of the parent.
        """
        self.angle = angle
        self.updateTransform(force=True)

    def setAnchor(self, anchor):
        self.anchor = Point(anchor)
        self.updateTextPos()

    def setColor(self, color):
        """
        Set the color for this text.
        
        See QtWidgets.QGraphicsItem.setDefaultTextColor().
        """
        self.color = fn.mkColor(color)
        self.textItem.setDefaultTextColor(self.color)
        
    def updateTextPos(self):
        # update text position to obey anchor
        r = self.textItem.boundingRect()
        tl = self.textItem.mapToParent(r.topLeft())
        br = self.textItem.mapToParent(r.bottomRight())
        offset = (br - tl) * self.anchor
        self.textItem.setPos(-offset)

    def dataBounds(self, ax, frac=1.0, orthoRange=None):
        """
        Returns only the anchor point for when calulating view ranges.
        
        Sacrifices some visual polish for fixing issue #2642.
        """
        if orthoRange:
            range_min, range_max = orthoRange[0], orthoRange[1]
            if not range_min <= self.anchor[ax] <= range_max:
                return [None, None]

        return [self.anchor[ax], self.anchor[ax]]
        
    def boundingRect(self):
        return self.textItem.mapRectToParent(self.textItem.boundingRect())

    def viewTransformChanged(self):
        # called whenever view transform has changed.
        # Do this here to avoid double-updates when view changes.
        self.updateTransform()
        
    def paint(self, p, *args):
        # this is not ideal because it requires the transform to be updated at every draw.
        # ideally, we would have a sceneTransformChanged event to react to..
        s = self.scene()
        ls = self._lastScene
        if s is not ls:
            if ls is not None:
                ls.sigPrepareForPaint.disconnect(self.updateTransform)
            self._lastScene = s
            if s is not None:
                s.sigPrepareForPaint.connect(self.updateTransform)
            self.updateTransform()
            p.setTransform(self.sceneTransform())
        
        if self.border.style() != QtCore.Qt.PenStyle.NoPen or self.fill.style() != QtCore.Qt.BrushStyle.NoBrush:
            p.setPen(self.border)
            p.setBrush(self.fill)
            p.setRenderHint(p.RenderHint.Antialiasing, True)
            p.drawPolygon(self.textItem.mapToParent(self.textItem.boundingRect()))
        
    def setVisible(self, v):
        GraphicsObject.setVisible(self, v)
        if v:
            self.updateTransform()
    
    @QtCore.Slot()
    def updateTransform(self, force=False):
        if not self.isVisible():
            return

        # update transform such that this item has the correct orientation
        # and scaling relative to the scene, but inherits its position from its
        # parent.
        # This is similar to setting ItemIgnoresTransformations = True, but 
        # does not break mouse interaction and collision detection.
        p = self.parentItem()
        if p is None:
            pt = QtGui.QTransform()
        else:
            pt = p.sceneTransform()
        
        if not force and pt == self._lastTransform:
            return

        t = fn.invertQTransform(pt)
        # reset translation
        t.setMatrix(t.m11(), t.m12(), t.m13(), t.m21(), t.m22(), t.m23(), 0, 0, t.m33())
        
        # apply rotation
        angle = -self.angle
        if self.rotateAxis is not None:
            d = pt.map(self.rotateAxis) - pt.map(Point(0, 0))
            a = degrees(atan2(d.y(), d.x()))
            angle += a
        t.rotate(angle)  
        self.setTransform(t)
        self._lastTransform = pt
        self.updateTextPos()
