"""OnscreenText module: contains the OnscreenText class.

See the :ref:`onscreentext` page in the programming manual for explanation of
this class.
"""

__all__ = ['OnscreenText', 'Plain', 'ScreenTitle', 'ScreenPrompt', 'NameConfirm', 'BlackOnWhite']

from panda3d.core import LColor, Mat4, NodePath, Point3, TextNode, TextProperties, Vec3
from . import DirectGuiGlobals as DGG
import warnings

## These are the styles of text we might commonly see.  They set the
## overall appearance of the text according to one of a number of
## pre-canned styles.  You can further customize the appearance of the
## text by specifying individual parameters as well.
Plain = 1
ScreenTitle = 2
ScreenPrompt = 3
NameConfirm = 4
BlackOnWhite = 5


class OnscreenText(NodePath):

    def __init__(self, text = '',
                 style = Plain,
                 pos = (0, 0),
                 roll = 0,
                 scale = None,
                 fg = None,
                 bg = None,
                 shadow = None,
                 shadowOffset = (0.04, 0.04),
                 frame = None,
                 align = None,
                 wordwrap = None,
                 drawOrder = None,
                 decal = 0,
                 font = None,
                 parent = None,
                 sort = 0,
                 mayChange = True,
                 direction = None):
        """
        Make a text node from string, put it into the 2d sg and set it
        up with all the indicated parameters.

        Parameters:

          text: the actual text to display.  This may be omitted and
              specified later via setText() if you don't have it
              available, but it is better to specify it up front.

          style: one of the pre-canned style parameters defined at the
              head of this file.  This sets up the default values for
              many of the remaining parameters if they are
              unspecified; however, a parameter may still be specified
              to explicitly set it, overriding the pre-canned style.

          pos: the x, y position of the text on the screen.

          scale: the size of the text.  This may either be a single
              float (and it will usually be a small number like 0.07)
              or it may be a 2-tuple of floats, specifying a different
              x, y scale.

          fg: the (r, g, b, a) foreground color of the text.  This is
              normally a 4-tuple of floats or ints.

          bg: the (r, g, b, a) background color of the text.  If the
              fourth value, a, is nonzero, a card is created to place
              behind the text and set to the given color.

          shadow: the (r, g, b, a) color of the shadow behind the text.
              If the fourth value, a, is nonzero, a little drop shadow
              is created and placed behind the text.

          frame: the (r, g, b, a) color of the frame drawn around the
              text.  If the fourth value, a, is nonzero, a frame is
              created around the text.

          align: one of TextNode.ALeft, TextNode.ARight, or TextNode.ACenter.

          wordwrap: either the width to wordwrap the text at, or None
              to specify no automatic word wrapping.

          drawOrder: the drawing order of this text with respect to
              all other things in the 'fixed' bin within render2d.
              The text will actually use drawOrder through drawOrder +
              2.

          decal: if this is True, the text is decalled onto its
              background card.  Useful when the text will be parented
              into the 3-D scene graph.

          font: the font to use for the text.

          parent: the NodePath to parent the text to initially.

          mayChange: pass true if the text or its properties may need
              to be changed at runtime, false if it is static once
              created (which leads to better memory optimization).

          direction: this can be set to 'ltr' or 'rtl' to override the
              direction of the text.
        """
        if parent is None:
            from direct.showbase import ShowBaseGlobal
            parent = ShowBaseGlobal.aspect2d

        # make a text node
        textNode = TextNode('')
        self.textNode = textNode

        # We ARE a node path.  Initially, we're an empty node path.
        NodePath.__init__(self)

        # Choose the default parameters according to the selected
        # style.
        if style == Plain:
            scale = scale or 0.07
            fg = fg or (0, 0, 0, 1)
            bg = bg or (0, 0, 0, 0)
            shadow = shadow or (0, 0, 0, 0)
            frame = frame or (0, 0, 0, 0)
            if align is None:
                align = TextNode.ACenter
        elif style == ScreenTitle:
            scale = scale or 0.15
            fg = fg or (1, 0.2, 0.2, 1)
            bg = bg or (0, 0, 0, 0)
            shadow = shadow or (0, 0, 0, 1)
            frame = frame or (0, 0, 0, 0)
            if align is None:
                align = TextNode.ACenter
        elif style == ScreenPrompt:
            scale = scale or 0.1
            fg = fg or (1, 1, 0, 1)
            bg = bg or (0, 0, 0, 0)
            shadow = shadow or (0, 0, 0, 1)
            frame = frame or (0, 0, 0, 0)
            if align is None:
                align = TextNode.ACenter
        elif style == NameConfirm:
            scale = scale or 0.1
            fg = fg or (0, 1, 0, 1)
            bg = bg or (0, 0, 0, 0)
            shadow = shadow or (0, 0, 0, 0)
            frame = frame or (0, 0, 0, 0)
            if align is None:
                align = TextNode.ACenter
        elif style == BlackOnWhite:
            scale = scale or 0.1
            fg = fg or (0, 0, 0, 1)
            bg = bg or (1, 1, 1, 1)
            shadow = shadow or (0, 0, 0, 0)
            frame = frame or (0, 0, 0, 0)
            if align is None:
                align = TextNode.ACenter
        else:
            raise ValueError

        if not isinstance(scale, tuple):
            # If the scale is already a tuple, it's a 2-d (x, y) scale.
            # Otherwise, it's a uniform scale--make it a tuple.
            scale = (scale, scale)

        # Save some of the parameters for posterity.
        self.__scale = scale
        self.__pos = pos
        self.__roll = roll
        self.__wordwrap = wordwrap

        if decal:
            textNode.setCardDecal(True)

        if font is None:
            font = DGG.getDefaultFont()

        textNode.setFont(font)
        textNode.setTextColor(fg[0], fg[1], fg[2], fg[3])
        textNode.setAlign(align)

        if wordwrap:
            textNode.setWordwrap(wordwrap)

        if bg[3] != 0:
            # If we have a background color, create a card.
            textNode.setCardColor(bg[0], bg[1], bg[2], bg[3])
            textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1)

        if shadow[3] != 0:
            # If we have a shadow color, create a shadow.
            # Can't use the *shadow interface because it might be a VBase4.
            #textNode.setShadowColor(*shadow)
            textNode.setShadowColor(shadow[0], shadow[1], shadow[2], shadow[3])
            textNode.setShadow(*shadowOffset)

        if frame[3] != 0:
            # If we have a frame color, create a frame.
            textNode.setFrameColor(frame[0], frame[1], frame[2], frame[3])
            textNode.setFrameAsMargin(0.1, 0.1, 0.1, 0.1)

        if direction is not None:
            if isinstance(direction, str):
                direction = direction.lower()
                if direction == 'rtl':
                    direction = TextProperties.D_rtl
                elif direction == 'ltr':
                    direction = TextProperties.D_ltr
                else:
                    raise ValueError('invalid direction')
            textNode.setDirection(direction)

        # Create a transform for the text for our scale and position.
        # We'd rather do it here, on the text itself, rather than on
        # our NodePath, so we have one fewer transforms in the scene
        # graph.
        self.updateTransformMat()

        if drawOrder is not None:
            textNode.setBin('fixed')
            textNode.setDrawOrder(drawOrder)

        self.setText(text)
        if not text:
            # If we don't have any text, assume we'll be changing it later.
            self.mayChange = 1
        else:
            self.mayChange = mayChange

        # Ok, now update the node.
        if not self.mayChange:
            # If we aren't going to change the text later, we can
            # throw away the TextNode.
            self.textNode = textNode.generate()

        self.isClean = 0

        # Set ourselves up as the NodePath that points to this node.
        self.assign(parent.attachNewNode(self.textNode, sort))

    def cleanup(self):
        self.textNode = None
        if self.isClean == 0:
            self.isClean = 1
            self.removeNode()

    def destroy(self):
        self.cleanup()

    def freeze(self):
        pass

    def thaw(self):
        pass

    # Allow changing of several of the parameters after the text has
    # been created.  These should be used with caution; it is better
    # to set all the parameters up front.  These functions are
    # primarily intended for interactive placement of the initial
    # text, and for those rare occasions when you actually want to
    # change a text's property after it has been created.

    def setDecal(self, decal):
        self.textNode.setCardDecal(decal)

    def getDecal(self):
        return self.textNode.getCardDecal()

    decal = property(getDecal, setDecal)

    def setFont(self, font):
        self.textNode.setFont(font)

    def getFont(self):
        return self.textNode.getFont()

    font = property(getFont, setFont)

    def clearText(self):
        self.textNode.clearText()

    def setText(self, text):
        assert not isinstance(text, bytes)
        self.textNode.setWtext(text)

    def appendText(self, text):
        assert not isinstance(text, bytes)
        self.textNode.appendWtext(text)

    def getText(self):
        return self.textNode.getWtext()

    text = property(getText, setText)

    def setTextX(self, x):
        """
        .. versionadded:: 1.10.8
        """
        self.setTextPos(x, self.__pos[1])

    def setX(self, x):
        """
        .. deprecated:: 1.11.0
           Use `.setTextX()` method instead.
        """
        if __debug__:
            warnings.warn("Use `.setTextX()` method instead.", DeprecationWarning, stacklevel=2)
        self.setTextPos(x, self.__pos[1])

    def setTextY(self, y):
        """
        .. versionadded:: 1.10.8
        """
        self.setTextPos(self.__pos[0], y)

    def setY(self, y):
        """
        .. deprecated:: 1.11.0
           Use `.setTextY()` method instead.
        """
        if __debug__:
            warnings.warn("Use `.setTextY()` method instead.", DeprecationWarning, stacklevel=2)
        self.setTextPos(self.__pos[0], y)

    def setTextPos(self, x, y=None):
        """
        Position the onscreen text in 2d screen space

        .. versionadded:: 1.10.8
        """
        if y is None:
            self.__pos = tuple(x)
        else:
            self.__pos = (x, y)
        self.updateTransformMat()

    def getTextPos(self):
        """
        .. versionadded:: 1.10.8
        """
        return self.__pos

    text_pos = property(getTextPos, setTextPos)

    def setPos(self, x, y):
        """setPos(self, float, float)
        Position the onscreen text in 2d screen space

        .. deprecated:: 1.11.0
           Use `.setTextPos()` method or `.text_pos` property instead.
        """
        if __debug__:
            warnings.warn("Use `.setTextPos()` method or `.text_pos` property instead.", DeprecationWarning, stacklevel=2)
        self.__pos = (x, y)
        self.updateTransformMat()

    def getPos(self):
        """
        .. deprecated:: 1.11.0
           Use `.getTextPos()` method or `.text_pos` property instead.
        """
        if __debug__:
            warnings.warn("Use `.getTextPos()` method or `.text_pos` property instead.", DeprecationWarning, stacklevel=2)
        return self.__pos

    pos = property(getPos)

    def setTextR(self, r):
        """setTextR(self, float)
        Rotates the text around the screen's normal.

        .. versionadded:: 1.10.8
        """
        self.__roll = -r
        self.updateTransformMat()

    def getTextR(self):
        return -self.__roll

    text_r = property(getTextR, setTextR)

    def setRoll(self, roll):
        """setRoll(self, float)
        Rotate the onscreen text around the screen's normal.

        .. deprecated:: 1.11.0
           Use ``setTextR(-roll)`` instead (note the negated sign).
        """
        if __debug__:
            warnings.warn("Use ``setTextR(-roll)`` instead (note the negated sign).", DeprecationWarning, stacklevel=2)
        self.__roll = roll
        self.updateTransformMat()

    def getRoll(self):
        """
        .. deprecated:: 1.11.0
           Use ``-getTextR()`` instead (note the negated sign).
        """
        if __debug__:
            warnings.warn("Use ``-getTextR()`` instead (note the negated sign).", DeprecationWarning, stacklevel=2)
        return self.__roll

    roll = property(getRoll, setRoll)

    def setTextScale(self, sx, sy = None):
        """setTextScale(self, float, float)
        Scale the text in 2d space.  You may specify either a single
        uniform scale, or two scales, or a tuple of two scales.

        .. versionadded:: 1.10.8
        """

        if sy is None:
            if isinstance(sx, tuple):
                self.__scale = sx
            else:
                self.__scale = (sx, sx)
        else:
            self.__scale = (sx, sy)
        self.updateTransformMat()

    def getTextScale(self):
        """
        .. versionadded:: 1.10.8
        """
        return self.__scale

    text_scale = property(getTextScale, setTextScale)

    def setScale(self, sx, sy = None):
        """setScale(self, float, float)
        Scale the text in 2d space.  You may specify either a single
        uniform scale, or two scales, or a tuple of two scales.

        .. deprecated:: 1.11.0
           Use `.setTextScale()` method or `.text_scale` property instead.
        """
        if __debug__:
            warnings.warn("Use `.setTextScale()` method or `.text_scale` property instead.", DeprecationWarning, stacklevel=2)
        if sy is None:
            if isinstance(sx, tuple):
                self.__scale = sx
            else:
                self.__scale = (sx, sx)
        else:
            self.__scale = (sx, sy)
        self.updateTransformMat()

    def getScale(self):
        """
        .. deprecated:: 1.11.0
           Use `.getTextScale()` method or `.text_scale` property instead.
        """
        if __debug__:
            warnings.warn("Use `.getTextScale()` method or `.text_scale` property instead.", DeprecationWarning, stacklevel=2)
        return self.__scale

    scale = property(getScale, setScale)

    def updateTransformMat(self):
        assert isinstance(self.textNode, TextNode)
        mat = (
            Mat4.scaleMat(Vec3.rfu(self.__scale[0], 1, self.__scale[1])) *
            Mat4.rotateMat(self.__roll, Vec3.back()) *
            Mat4.translateMat(Point3.rfu(self.__pos[0], 0, self.__pos[1]))
        )
        self.textNode.setTransform(mat)

    def setWordwrap(self, wordwrap):
        self.__wordwrap = wordwrap

        if wordwrap:
            self.textNode.setWordwrap(wordwrap)
        else:
            self.textNode.clearWordwrap()

    def getWordwrap(self):
        return self.__wordwrap

    wordwrap = property(getWordwrap, setWordwrap)

    def __getFg(self):
        return self.textNode.getTextColor()

    def setFg(self, fg):
        self.textNode.setTextColor(fg[0], fg[1], fg[2], fg[3])

    fg = property(__getFg, setFg)

    def __getBg(self):
        if self.textNode.hasCard():
            return self.textNode.getCardColor()
        else:
            return LColor(0)

    def setBg(self, bg):
        if bg[3] != 0:
            # If we have a background color, create a card.
            self.textNode.setCardColor(bg[0], bg[1], bg[2], bg[3])
            self.textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
        else:
            # Otherwise, remove the card.
            self.textNode.clearCard()

    bg = property(__getBg, setBg)

    def __getShadow(self):
        return self.textNode.getShadowColor()

    def setShadow(self, shadow):
        if shadow[3] != 0:
            # If we have a shadow color, create a shadow.
            self.textNode.setShadowColor(shadow[0], shadow[1], shadow[2], shadow[3])
            self.textNode.setShadow(0.04, 0.04)
        else:
            # Otherwise, remove the shadow.
            self.textNode.clearShadow()

    shadow = property(__getShadow, setShadow)

    def __getFrame(self):
        return self.textNode.getFrameColor()

    def setFrame(self, frame):
        if frame[3] != 0:
            # If we have a frame color, create a frame.
            self.textNode.setFrameColor(frame[0], frame[1], frame[2], frame[3])
            self.textNode.setFrameAsMargin(0.1, 0.1, 0.1, 0.1)
        else:
            # Otherwise, remove the frame.
            self.textNode.clearFrame()

    frame = property(__getFrame, setFrame)

    def configure(self, option=None, **kw):
        # These is for compatibility with DirectGui functions
        if not self.mayChange:
            print('OnscreenText.configure: mayChange == 0')
            return
        for option, value in kw.items():
            # Use option string to access setter function
            try:
                if option == 'pos':
                    self.setTextPos(value[0], value[1])
                elif option == 'roll':
                    self.setTextR(-value)
                elif option == 'scale':
                    self.setTextScale(value)
                elif option == 'x':
                    self.setTextX(value)
                elif option == 'y':
                    self.setTextY(value)
                else:
                    setter = getattr(self, 'set' + option[0].upper() + option[1:])
                    setter(value)
            except AttributeError:
                print('OnscreenText.configure: invalid option: %s' % option)

    # Allow index style references
    def __setitem__(self, key, value):
        self.configure(*(), **{key: value})

    def cget(self, option):
        # Get current configuration setting.
        # This is for compatibility with DirectGui functions
        if option == 'pos':
            return self.__pos
        elif option == 'roll':
            return self.__roll
        elif option == 'scale':
            return self.__scale
        elif option == 'x':
            return self.__pos[0]
        elif option == 'y':
            return self.__pos[1]

        getter = getattr(self, 'get' + option[0].upper() + option[1:])
        return getter()

    def __getAlign(self):
        return self.textNode.getAlign()

    def setAlign(self, align):
        self.textNode.setAlign(align)

    align = property(__getAlign, setAlign)

    # Allow index style refererences
    __getitem__ = cget
