# -*- coding: utf-8 -*-
#
# Licensed under the terms of the Qwt License
# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
# (see LICENSE file for more details)

"""
QwtPlotGrid
-----------

.. autoclass:: QwtPlotGrid
   :members:
"""

from qtpy.QtCore import QLineF, QObject, Qt
from qtpy.QtGui import QPen

from qwt._math import qwtFuzzyGreaterOrEqual, qwtFuzzyLessOrEqual
from qwt.plot import QwtPlotItem
from qwt.qthelpers import qcolor_from_str
from qwt.scale_div import QwtScaleDiv


class QwtPlotGrid_PrivateData(QObject):
    def __init__(self):
        QObject.__init__(self)

        self.xEnabled = True
        self.yEnabled = True
        self.xMinEnabled = False
        self.yMinEnabled = False
        self.xScaleDiv = QwtScaleDiv()
        self.yScaleDiv = QwtScaleDiv()
        self.majorPen = QPen()
        self.minorPen = QPen()


class QwtPlotGrid(QwtPlotItem):
    """
    A class which draws a coordinate grid

    The `QwtPlotGrid` class can be used to draw a coordinate grid.
    A coordinate grid consists of major and minor vertical
    and horizontal grid lines. The locations of the grid lines
    are determined by the X and Y scale divisions which can
    be assigned with `setXDiv()` and `setYDiv()`.
    The `draw()` member draws the grid within a bounding
    rectangle.
    """

    def __init__(self, title="Grid"):
        QwtPlotItem.__init__(self, title)
        self.__data = QwtPlotGrid_PrivateData()
        self.setItemInterest(QwtPlotItem.ScaleInterest, True)
        self.setZ(10.0)

    @classmethod
    def make(
        cls,
        plot=None,
        z=None,
        enablemajor=None,
        enableminor=None,
        color=None,
        width=None,
        style=None,
        mincolor=None,
        minwidth=None,
        minstyle=None,
    ):
        """
        Create and setup a new `QwtPlotGrid` object (convenience function).

        :param plot: Plot to attach the curve to
        :type plot: qwt.plot.QwtPlot or None
        :param z: Z-value
        :type z: float or None
        :param enablemajor: Tuple of two boolean values (x, y) for enabling major grid lines
        :type enablemajor: bool or None
        :param enableminor: Tuple of two boolean values (x, y) for enabling minor grid lines
        :type enableminor: bool or None
        :param color: Pen color for both major and minor grid lines (default: Qt.gray)
        :type color: QColor or str or None
        :param width: Pen width for both major and minor grid lines (default: 1.0)
        :type width: float or None
        :param style: Pen style for both major and minor grid lines (default: Qt.DotLine)
        :type style: Qt.PenStyle or None
        :param mincolor: Pen color for minor grid lines only (default: Qt.gray)
        :type mincolor: QColor or str or None
        :param minwidth: Pen width for minor grid lines only (default: 1.0)
        :type minwidth: float or None
        :param minstyle: Pen style for minor grid lines only (default: Qt.DotLine)
        :type minstyle: Qt.PenStyle or None

        .. seealso::

            :py:meth:`setMinorPen()`, :py:meth:`setMajorPen()`
        """
        item = cls()
        if z is not None:
            item.setZ(z)
        color = qcolor_from_str(color, Qt.gray)
        width = 1.0 if width is None else float(width)
        style = Qt.DotLine if style is None else style
        item.setPen(QPen(color, width, style))
        if mincolor is not None or minwidth is not None or minstyle is not None:
            mincolor = qcolor_from_str(mincolor, Qt.gray)
            minwidth = 1.0 if width is None else minwidth
            minstyle = Qt.DotLine if style is None else minstyle
            item.setMinorPen(QPen(mincolor, minwidth, minstyle))
        if enablemajor is not None:
            if isinstance(enablemajor, tuple) and len(enablemajor) == 2:
                item.enableX(enablemajor[0])
                item.enableY(enablemajor[1])
            else:
                raise TypeError(
                    "Invalid enablemajor %r (expecting tuple of two booleans)"
                    % enablemajor
                )
        if enableminor is not None:
            if isinstance(enableminor, tuple) and len(enableminor) == 2:
                item.enableXMin(enableminor[0])
                item.enableYMin(enableminor[1])
            else:
                raise TypeError(
                    "Invalid enableminor %r (expecting tuple of two booleans)"
                    % enableminor
                )
        if plot is not None:
            item.attach(plot)
        return item

    def rtti(self):
        """
        :return: Return `QwtPlotItem.Rtti_PlotGrid`
        """
        return QwtPlotItem.Rtti_PlotGrid

    def enableX(self, on):
        """
        Enable or disable vertical grid lines

        :param bool on: Enable (true) or disable

        .. seealso::

            :py:meth:`enableXMin()`
        """
        if self.__data.xEnabled != on:
            self.__data.xEnabled = on
            self.legendChanged()
            self.itemChanged()

    def enableY(self, on):
        """
        Enable or disable horizontal grid lines

        :param bool on: Enable (true) or disable

        .. seealso::

            :py:meth:`enableYMin()`
        """
        if self.__data.yEnabled != on:
            self.__data.yEnabled = on
            self.legendChanged()
            self.itemChanged()

    def enableXMin(self, on):
        """
        Enable or disable  minor vertical grid lines.

        :param bool on: Enable (true) or disable

        .. seealso::

            :py:meth:`enableX()`
        """
        if self.__data.xMinEnabled != on:
            self.__data.xMinEnabled = on
            self.legendChanged()
            self.itemChanged()

    def enableYMin(self, on):
        """
        Enable or disable  minor horizontal grid lines.

        :param bool on: Enable (true) or disable

        .. seealso::

            :py:meth:`enableY()`
        """
        if self.__data.yMinEnabled != on:
            self.__data.yMinEnabled = on
            self.legendChanged()
            self.itemChanged()

    def setXDiv(self, scaleDiv):
        """
        Assign an x axis scale division

        :param qwt.scale_div.QwtScaleDiv scaleDiv: Scale division
        """
        if self.__data.xScaleDiv != scaleDiv:
            self.__data.xScaleDiv = scaleDiv
            self.itemChanged()

    def setYDiv(self, scaleDiv):
        """
        Assign an y axis scale division

        :param qwt.scale_div.QwtScaleDiv scaleDiv: Scale division
        """
        if self.__data.yScaleDiv != scaleDiv:
            self.__data.yScaleDiv = scaleDiv
            self.itemChanged()

    def setPen(self, *args):
        """
        Build and/or assign a pen for both major and minor grid lines

        .. py:method:: setPen(color, width, style)
            :noindex:

            Build and assign a pen for both major and minor grid lines

            In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
            non cosmetic (see `QPen.isCosmetic()`). This method signature has
            been introduced to hide this incompatibility.

            :param QColor color: Pen color
            :param float width: Pen width
            :param Qt.PenStyle style: Pen style

        .. py:method:: setPen(pen)
            :noindex:

            Assign a pen for both major and minor grid lines

            :param QPen pen: New pen

        .. seealso::

            :py:meth:`pen()`, :py:meth:`brush()`
        """
        if len(args) == 3:
            color, width, style = args
            self.setPen(QPen(color, width, style))
        elif len(args) == 1:
            (pen,) = args
            if self.__data.majorPen != pen or self.__data.minorPen != pen:
                self.__data.majorPen = pen
                self.__data.minorPen = pen
                self.legendChanged()
                self.itemChanged()
        else:
            raise TypeError(
                "%s().setPen() takes 1 or 3 argument(s) (%s given)"
                % (self.__class__.__name__, len(args))
            )

    def setMajorPen(self, *args):
        """
        Build and/or assign a pen for both major grid lines

        .. py:method:: setMajorPen(color, width, style)
            :noindex:

            Build and assign a pen for both major grid lines

            In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
            non cosmetic (see `QPen.isCosmetic()`). This method signature has
            been introduced to hide this incompatibility.

            :param QColor color: Pen color
            :param float width: Pen width
            :param Qt.PenStyle style: Pen style

        .. py:method:: setMajorPen(pen)
            :noindex:

            Assign a pen for the major grid lines

            :param QPen pen: New pen

        .. seealso::

            :py:meth:`majorPen()`, :py:meth:`setMinorPen()`,
            :py:meth:`setPen()`, :py:meth:`pen()`, :py:meth:`brush()`
        """
        if len(args) == 3:
            color, width, style = args
            self.setMajorPen(QPen(color, width, style))
        elif len(args) == 1:
            (pen,) = args
            if self.__data.majorPen != pen:
                self.__data.majorPen = pen
                self.legendChanged()
                self.itemChanged()
        else:
            raise TypeError(
                "%s().setMajorPen() takes 1 or 3 argument(s) (%s "
                "given)" % (self.__class__.__name__, len(args))
            )

    def setMinorPen(self, *args):
        """
        Build and/or assign a pen for both minor grid lines

        .. py:method:: setMinorPen(color, width, style)
            :noindex:

            Build and assign a pen for both minor grid lines

            In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it
            non cosmetic (see `QPen.isCosmetic()`). This method signature has
            been introduced to hide this incompatibility.

            :param QColor color: Pen color
            :param float width: Pen width
            :param Qt.PenStyle style: Pen style

        .. py:method:: setMinorPen(pen)
            :noindex:

            Assign a pen for the minor grid lines

            :param QPen pen: New pen

        .. seealso::

            :py:meth:`minorPen()`, :py:meth:`setMajorPen()`,
            :py:meth:`setPen()`, :py:meth:`pen()`, :py:meth:`brush()`
        """
        if len(args) == 3:
            color, width, style = args
            self.setMinorPen(QPen(color, width, style))
        elif len(args) == 1:
            (pen,) = args
            if self.__data.minorPen != pen:
                self.__data.minorPen = pen
                self.legendChanged()
                self.itemChanged()
        else:
            raise TypeError(
                "%s().setMinorPen() takes 1 or 3 argument(s) (%s "
                "given)" % (self.__class__.__name__, len(args))
            )

    def draw(self, painter, xMap, yMap, canvasRect):
        """
        Draw the grid

        The grid is drawn into the bounding rectangle such that
        grid lines begin and end at the rectangle's borders. The X and Y
        maps are used to map the scale divisions into the drawing region
        screen.

        :param QPainter painter: Painter
        :param qwt.scale_map.QwtScaleMap xMap: X axis map
        :param qwt.scale_map.QwtScaleMap yMap: Y axis
        :param QRectF canvasRect: Contents rectangle of the plot canvas
        """
        minorPen = QPen(self.__data.minorPen)
        minorPen.setCapStyle(Qt.FlatCap)
        painter.setPen(minorPen)
        if self.__data.xEnabled and self.__data.xMinEnabled:
            self.drawLines(
                painter,
                canvasRect,
                Qt.Vertical,
                xMap,
                self.__data.xScaleDiv.ticks(QwtScaleDiv.MinorTick),
            )
            self.drawLines(
                painter,
                canvasRect,
                Qt.Vertical,
                xMap,
                self.__data.xScaleDiv.ticks(QwtScaleDiv.MediumTick),
            )
        if self.__data.yEnabled and self.__data.yMinEnabled:
            self.drawLines(
                painter,
                canvasRect,
                Qt.Horizontal,
                yMap,
                self.__data.yScaleDiv.ticks(QwtScaleDiv.MinorTick),
            )
            self.drawLines(
                painter,
                canvasRect,
                Qt.Horizontal,
                yMap,
                self.__data.yScaleDiv.ticks(QwtScaleDiv.MediumTick),
            )
        majorPen = QPen(self.__data.majorPen)
        majorPen.setCapStyle(Qt.FlatCap)
        painter.setPen(majorPen)
        if self.__data.xEnabled:
            self.drawLines(
                painter,
                canvasRect,
                Qt.Vertical,
                xMap,
                self.__data.xScaleDiv.ticks(QwtScaleDiv.MajorTick),
            )
        if self.__data.yEnabled:
            self.drawLines(
                painter,
                canvasRect,
                Qt.Horizontal,
                yMap,
                self.__data.yScaleDiv.ticks(QwtScaleDiv.MajorTick),
            )

    def drawLines(self, painter, canvasRect, orientation, scaleMap, values):
        x1 = canvasRect.left()
        x2 = canvasRect.right() - 1.0
        y1 = canvasRect.top()
        y2 = canvasRect.bottom() - 1.0
        for val in values:
            value = scaleMap.transform(val)
            if orientation == Qt.Horizontal:
                if qwtFuzzyGreaterOrEqual(value, y1) and qwtFuzzyLessOrEqual(value, y2):
                    painter.drawLine(QLineF(x1, value, x2, value))
            else:
                if qwtFuzzyGreaterOrEqual(value, x1) and qwtFuzzyLessOrEqual(value, x2):
                    painter.drawLine(QLineF(value, y1, value, y2))

    def majorPen(self):
        """
        :return: the pen for the major grid lines

        .. seealso::

            :py:meth:`setMajorPen()`, :py:meth:`setMinorPen()`,
            :py:meth:`setPen()`
        """
        return self.__data.majorPen

    def minorPen(self):
        """
        :return: the pen for the minor grid lines

        .. seealso::

            :py:meth:`setMinorPen()`, :py:meth:`setMajorPen()`,
            :py:meth:`setPen()`
        """
        return self.__data.minorPen

    def xEnabled(self):
        """
        :return: True if vertical grid lines are enabled

        .. seealso::

            :py:meth:`enableX()`
        """
        return self.__data.xEnabled

    def yEnabled(self):
        """
        :return: True if horizontal grid lines are enabled

        .. seealso::

            :py:meth:`enableY()`
        """
        return self.__data.yEnabled

    def xMinEnabled(self):
        """
        :return: True if minor vertical grid lines are enabled

        .. seealso::

            :py:meth:`enableXMin()`
        """
        return self.__data.xMinEnabled

    def yMinEnabled(self):
        """
        :return: True if minor horizontal grid lines are enabled

        .. seealso::

            :py:meth:`enableYMin()`
        """
        return self.__data.yMinEnabled

    def xScaleDiv(self):
        """
        :return: the scale division of the x axis
        """
        return self.__data.xScaleDiv

    def yScaleDiv(self):
        """
        :return: the scale division of the y axis
        """
        return self.__data.yScaleDiv

    def updateScaleDiv(self, xScaleDiv, yScaleDiv):
        """
        Update the grid to changes of the axes scale division

        :param qwt.scale_map.QwtScaleMap xMap: Scale division of the x-axis
        :param qwt.scale_map.QwtScaleMap yMap: Scale division of the y-axis

        .. seealso::

            :py:meth:`updateAxes()`
        """
        self.setXDiv(xScaleDiv)
        self.setYDiv(yScaleDiv)
