# /*##########################################################################
#
# Copyright (c) 2016-2021 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""Basic tests for ColorBar featues and sub widgets of Colorbar module"""

__authors__ = ["H. Payno"]
__license__ = "MIT"
__date__ = "24/04/2018"

from silx.gui.utils.testutils import TestCaseQt
from silx.gui.plot.ColorBar import _ColorScale
from silx.gui.plot.ColorBar import ColorBarWidget
from silx.gui.colors import Colormap
from silx.math.colormap import LinearNormalization, LogarithmicNormalization
from silx.gui.plot import Plot2D
from silx.gui import qt
import numpy


class TestColorScale(TestCaseQt):
    """Test that interaction with the colorScale is correct"""

    def setUp(self):
        super(TestColorScale, self).setUp()
        self.colorScaleWidget = _ColorScale(colormap=None, parent=None)
        self.colorScaleWidget.show()
        self.qWaitForWindowExposed(self.colorScaleWidget)

    def tearDown(self):
        self.qapp.processEvents()
        self.colorScaleWidget.setAttribute(qt.Qt.WA_DeleteOnClose)
        self.colorScaleWidget.close()
        del self.colorScaleWidget
        super(TestColorScale, self).tearDown()

    def testNoColormap(self):
        """Test _ColorScale without a colormap"""
        colormap = self.colorScaleWidget.getColormap()
        self.assertIsNone(colormap)

    def testRelativePositionLinear(self):
        self.colorMapLin1 = Colormap(
            name="gray", normalization=Colormap.LINEAR, vmin=0.0, vmax=1.0
        )
        self.colorScaleWidget.setColormap(self.colorMapLin1)

        self.assertTrue(
            self.colorScaleWidget.getValueFromRelativePosition(0.25) == 0.25
        )
        self.assertTrue(self.colorScaleWidget.getValueFromRelativePosition(0.5) == 0.5)
        self.assertTrue(self.colorScaleWidget.getValueFromRelativePosition(1.0) == 1.0)

        self.colorMapLin2 = Colormap(
            name="viridis", normalization=Colormap.LINEAR, vmin=-10, vmax=0
        )
        self.colorScaleWidget.setColormap(self.colorMapLin2)

        self.assertTrue(
            self.colorScaleWidget.getValueFromRelativePosition(0.25) == -7.5
        )
        self.assertTrue(self.colorScaleWidget.getValueFromRelativePosition(0.5) == -5.0)
        self.assertTrue(self.colorScaleWidget.getValueFromRelativePosition(1.0) == 0.0)

    def testRelativePositionLog(self):
        self.colorMapLog1 = Colormap(
            name="temperature", normalization=Colormap.LOGARITHM, vmin=1.0, vmax=100.0
        )

        self.colorScaleWidget.setColormap(self.colorMapLog1)

        val = self.colorScaleWidget.getValueFromRelativePosition(1.0)
        self.assertAlmostEqual(val, 100.0)

        val = self.colorScaleWidget.getValueFromRelativePosition(0.5)
        self.assertAlmostEqual(val, 10.0)

        val = self.colorScaleWidget.getValueFromRelativePosition(0.0)
        self.assertTrue(val == 1.0)


class TestNoAutoscale(TestCaseQt):
    """Test that ticks and color displayed are correct in the case of a colormap
    with no autoscale
    """

    def setUp(self):
        super(TestNoAutoscale, self).setUp()
        self.plot = Plot2D()
        self.colorBar = self.plot.getColorBarWidget()
        self.colorBar.setVisible(True)  # Makes sure the colormap is visible
        self.tickBar = self.colorBar.getColorScaleBar().getTickBar()
        self.colorScale = self.colorBar.getColorScaleBar().getColorScale()

        self.plot.show()
        self.qWaitForWindowExposed(self.plot)

    def tearDown(self):
        self.qapp.processEvents()
        self.tickBar = None
        self.colorScale = None
        del self.colorBar
        self.plot.setAttribute(qt.Qt.WA_DeleteOnClose)
        self.plot.close()
        del self.plot
        super(TestNoAutoscale, self).tearDown()

    def testLogNormNoAutoscale(self):
        colormapLog = Colormap(
            name="gray", normalization=Colormap.LOGARITHM, vmin=1.0, vmax=100.0
        )

        data = numpy.linspace(10, 1e10, 9).reshape(3, 3)
        self.plot.addImage(data=data, colormap=colormapLog, legend="toto")
        self.plot.setActiveImage("toto")

        # test Ticks
        self.tickBar.setTicksNumber(10)
        self.tickBar.computeTicks()

        ticksTh = numpy.linspace(1.0, 100.0, 10)
        ticksTh = 10**ticksTh
        numpy.array_equal(self.tickBar.ticks, ticksTh)

        # test ColorScale
        val = self.colorScale.getValueFromRelativePosition(1.0)
        self.assertAlmostEqual(val, 100.0)

        val = self.colorScale.getValueFromRelativePosition(0.0)
        self.assertTrue(val == 1.0)

    def testLinearNormNoAutoscale(self):
        colormapLog = Colormap(
            name="gray", normalization=Colormap.LINEAR, vmin=-4, vmax=5
        )

        data = numpy.linspace(1, 9, 9).reshape(3, 3)
        self.plot.addImage(data=data, colormap=colormapLog, legend="toto")
        self.plot.setActiveImage("toto")

        # test Ticks
        self.tickBar.setTicksNumber(10)
        self.tickBar.computeTicks()

        numpy.array_equal(self.tickBar.ticks, numpy.linspace(-4, 5, 10))

        # test ColorScale
        val = self.colorScale.getValueFromRelativePosition(1.0)
        self.assertTrue(val == 5.0)

        val = self.colorScale.getValueFromRelativePosition(0.0)
        self.assertTrue(val == -4.0)


class TestColorBarWidget(TestCaseQt):
    """Test interaction with the ColorBarWidget"""

    def setUp(self):
        super(TestColorBarWidget, self).setUp()
        self.plot = Plot2D()
        self.colorBar = self.plot.getColorBarWidget()
        self.colorBar.setVisible(True)  # Makes sure the colormap is visible

        self.plot.show()
        self.qWaitForWindowExposed(self.plot)

    def tearDown(self):
        self.qapp.processEvents()
        del self.colorBar
        self.plot.setAttribute(qt.Qt.WA_DeleteOnClose)
        self.plot.close()
        del self.plot
        super(TestColorBarWidget, self).tearDown()

    def testEmptyColorBar(self):
        colorBar = ColorBarWidget(parent=None)
        colorBar.show()
        self.qWaitForWindowExposed(colorBar)

    def testNegativeColormaps(self):
        """test the behavior of the ColorBarWidget in the case of negative
        values

        Note : colorbar is modified by the Plot directly not ColorBarWidget
        """
        colormapLog = Colormap(
            name="gray", normalization=Colormap.LOGARITHM, vmin=None, vmax=None
        )

        data = numpy.array([-5, -4, 0, 2, 3, 5, 10, 20, 30])
        data = data.reshape(3, 3)
        self.plot.addImage(data=data, colormap=colormapLog, legend="toto")
        self.plot.setActiveImage("toto")

        # default behavior when with log and negative values: should set vmin
        # to 1 and vmax to 10
        self.assertTrue(self.colorBar.getColorScaleBar().minVal == 2)
        self.assertTrue(self.colorBar.getColorScaleBar().maxVal == 30)

        # if data is positive
        data[data < 1] = data.max()
        self.plot.addImage(data=data, colormap=colormapLog, legend="toto", replace=True)
        self.plot.setActiveImage("toto")

        self.assertTrue(self.colorBar.getColorScaleBar().minVal == data.min())
        self.assertTrue(self.colorBar.getColorScaleBar().maxVal == data.max())

    def testPlotAssocation(self):
        """Make sure the ColorBarWidget is properly connected with the plot"""
        colormap = Colormap(
            name="gray", normalization=Colormap.LINEAR, vmin=None, vmax=None
        )

        # make sure that default settings are the same (but a copy of the
        self.colorBar.setPlot(self.plot)
        self.assertTrue(self.colorBar.getColormap() is self.plot.getDefaultColormap())

        data = numpy.linspace(0, 10, 100).reshape(10, 10)
        self.plot.addImage(data=data, colormap=colormap, legend="toto")
        self.plot.setActiveImage("toto")

        # make sure the modification of the colormap has been done
        self.assertFalse(self.colorBar.getColormap() is self.plot.getDefaultColormap())
        self.assertTrue(self.colorBar.getColormap() is colormap)

        # test that colorbar is updated when default plot colormap changes
        self.plot.clear()
        plotColormap = Colormap(
            name="gray", normalization=Colormap.LOGARITHM, vmin=None, vmax=None
        )
        self.plot.setDefaultColormap(plotColormap)
        self.assertTrue(self.colorBar.getColormap() is plotColormap)

    def testColormapWithoutRange(self):
        """Test with a colormap with vmin==vmax"""
        colormap = Colormap(
            name="gray", normalization=Colormap.LINEAR, vmin=1.0, vmax=1.0
        )
        self.colorBar.setColormap(colormap)


class TestColorBarUpdate(TestCaseQt):
    """Test that the ColorBar is correctly updated when the signal 'sigChanged'
    of the colormap is emitted
    """

    def setUp(self):
        super(TestColorBarUpdate, self).setUp()
        self.plot = Plot2D()
        self.colorBar = self.plot.getColorBarWidget()
        self.colorBar.setVisible(True)  # Makes sure the colormap is visible
        self.colorBar.setPlot(self.plot)

        self.plot.show()
        self.qWaitForWindowExposed(self.plot)
        self.data = numpy.random.rand(9).reshape(3, 3)

    def tearDown(self):
        self.qapp.processEvents()
        del self.colorBar
        self.plot.setAttribute(qt.Qt.WA_DeleteOnClose)
        self.plot.close()
        del self.plot
        super(TestColorBarUpdate, self).tearDown()

    def testUpdateColorMap(self):
        colormap = Colormap(name="gray", normalization="linear", vmin=0, vmax=1)

        # check inital state
        self.plot.addImage(data=self.data, colormap=colormap, legend="toto")
        self.plot.setActiveImage("toto")

        self.assertTrue(self.colorBar.getColorScaleBar().minVal == 0)
        self.assertTrue(self.colorBar.getColorScaleBar().maxVal == 1)
        self.assertTrue(self.colorBar.getColorScaleBar().getTickBar()._vmin == 0)
        self.assertTrue(self.colorBar.getColorScaleBar().getTickBar()._vmax == 1)
        self.assertIsInstance(
            self.colorBar.getColorScaleBar().getTickBar()._normalizer,
            LinearNormalization,
        )

        # update colormap
        colormap.setVMin(0.5)
        self.assertTrue(self.colorBar.getColorScaleBar().minVal == 0.5)
        self.assertTrue(self.colorBar.getColorScaleBar().getTickBar()._vmin == 0.5)

        colormap.setVMax(0.8)
        self.assertTrue(self.colorBar.getColorScaleBar().maxVal == 0.8)
        self.assertTrue(self.colorBar.getColorScaleBar().getTickBar()._vmax == 0.8)

        colormap.setNormalization("log")
        self.assertIsInstance(
            self.colorBar.getColorScaleBar().getTickBar()._normalizer,
            LogarithmicNormalization,
        )

    # TODO : should also check that if the colormap is changing then values (especially in log scale)
    # should be coherent if in autoscale
