# /*##########################################################################
#
# Copyright (c) 2016-2020 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.
#
# ###########################################################################*/
"""Module testing silx.app.view"""

__authors__ = ["V. Valls"]
__license__ = "MIT"
__date__ = "07/06/2018"


import weakref
import numpy
import h5py
import pytest

from silx.gui import qt
from silx.app.view.Viewer import Viewer
from silx.app.view.About import About
from silx.app.view.DataPanel import DataPanel
from silx.app.view.CustomNxdataWidget import CustomNxdataWidget
from silx.gui.hdf5._utils import Hdf5DatasetMimeData
from silx.gui.utils.testutils import TestCaseQt
from silx.io import commonh5


@pytest.fixture(scope="module")
def data_h5(tmpdir_factory):
    filename = tmpdir_factory.mktemp("data").join("data.h5")
    filename = str(filename)
    f = h5py.File(filename, "w")
    g = f.create_group("arrays")
    g.create_dataset("scalar", data=10)
    g.create_dataset("integers", data=numpy.array([10, 20, 30]))
    f.close()
    return filename


@pytest.fixture(scope="module")
def data2_h5(tmpdir_factory):
    filename = tmpdir_factory.mktemp("data").join("data2.h5")
    filename = str(filename)
    f = h5py.File(filename, "w")
    g = f.create_group("arrays")
    g.create_dataset("scalar", data=20)
    g.create_dataset("integers", data=numpy.array([10, 20, 30]))
    f.close()
    return filename


@pytest.fixture(scope="class")
def data_class_attr(request, data_h5, data2_h5):
    """Provides test_options as class attribute

    Used as transition from TestCase to pytest
    """
    request.cls.data_h5 = data_h5
    request.cls.data2_h5 = data2_h5


@pytest.mark.usefixtures("qapp")
class TestViewer(TestCaseQt):
    """Test for Viewer class"""

    def testConstruct(self):
        widget = Viewer()
        self.qWaitForWindowExposed(widget)

    def testDestroy(self):
        widget = Viewer()
        ref = weakref.ref(widget)
        widget = None
        self.qWaitForDestroy(ref)


@pytest.mark.usefixtures("qapp")
class TestAbout(TestCaseQt):
    """Test for About box class"""

    def testConstruct(self):
        widget = About()
        self.qWaitForWindowExposed(widget)

    def testLicense(self):
        widget = About()
        widget.getHtmlLicense()
        self.qWaitForWindowExposed(widget)

    def testDestroy(self):
        widget = About()
        ref = weakref.ref(widget)
        widget = None
        self.qWaitForDestroy(ref)


@pytest.mark.usefixtures("qapp")
@pytest.mark.usefixtures("data_class_attr")
class TestDataPanel(TestCaseQt):
    def testConstruct(self):
        widget = DataPanel()
        self.qWaitForWindowExposed(widget)

    def testDestroy(self):
        widget = DataPanel()
        ref = weakref.ref(widget)
        widget = None
        self.qWaitForDestroy(ref)

    def testHeaderLabelPaintEvent(self):
        widget = DataPanel()
        data = numpy.array([1, 2, 3, 4, 5])
        widget.setData(data)
        # Expected to execute HeaderLabel.paintEvent
        widget.setVisible(True)
        self.qWaitForWindowExposed(widget)

    def testData(self):
        widget = DataPanel()
        data = numpy.array([1, 2, 3, 4, 5])
        widget.setData(data)
        self.assertIs(widget.getData(), data)
        self.assertIs(widget.getCustomNxdataItem(), None)

    def testDataNone(self):
        widget = DataPanel()
        widget.setData(None)
        self.assertIs(widget.getData(), None)
        self.assertIs(widget.getCustomNxdataItem(), None)

    def testCustomDataItem(self):
        class CustomDataItemMock(object):
            def getVirtualGroup(self):
                return None

            def text(self):
                return ""

        data = CustomDataItemMock()
        widget = DataPanel()
        widget.setCustomDataItem(data)
        self.assertIs(widget.getData(), None)
        self.assertIs(widget.getCustomNxdataItem(), data)

    def testCustomDataItemNone(self):
        data = None
        widget = DataPanel()
        widget.setCustomDataItem(data)
        self.assertIs(widget.getData(), None)
        self.assertIs(widget.getCustomNxdataItem(), data)

    def testRemoveDatasetsFrom(self):
        f = h5py.File(self.data_h5, mode="r")
        try:
            widget = DataPanel()
            widget.setData(f["arrays/scalar"])
            widget.removeDatasetsFrom(f)
            self.assertIs(widget.getData(), None)
        finally:
            widget.setData(None)
            f.close()

    def testReplaceDatasetsFrom(self):
        f = h5py.File(self.data_h5, mode="r")
        f2 = h5py.File(self.data2_h5, mode="r")
        try:
            widget = DataPanel()
            widget.setData(f["arrays/scalar"])
            self.assertEqual(widget.getData()[()], 10)
            widget.replaceDatasetsFrom(f, f2)
            self.assertEqual(widget.getData()[()], 20)
        finally:
            widget.setData(None)
            f.close()
            f2.close()


@pytest.mark.usefixtures("qapp")
@pytest.mark.usefixtures("data_class_attr")
class TestCustomNxdataWidget(TestCaseQt):
    def testConstruct(self):
        widget = CustomNxdataWidget()
        self.qWaitForWindowExposed(widget)

    def testDestroy(self):
        widget = CustomNxdataWidget()
        ref = weakref.ref(widget)
        widget = None
        self.qWaitForDestroy(ref)

    def testCreateNxdata(self):
        widget = CustomNxdataWidget()
        model = widget.model()
        model.createNewNxdata()
        model.createNewNxdata("Foo")
        widget.setVisible(True)
        self.qWaitForWindowExposed(widget)

    def testCreateNxdataFromDataset(self):
        widget = CustomNxdataWidget()
        model = widget.model()
        signal = commonh5.Dataset("foo", data=numpy.array([[[5]]]))
        model.createFromSignal(signal)
        widget.setVisible(True)
        self.qWaitForWindowExposed(widget)

    def testCreateNxdataFromNxdata(self):
        widget = CustomNxdataWidget()
        model = widget.model()
        data = numpy.array([[[5]]])
        nxdata = commonh5.Group("foo")
        nxdata.attrs["NX_class"] = "NXdata"
        nxdata.attrs["signal"] = "signal"
        nxdata.create_dataset("signal", data=data)
        model.createFromNxdata(nxdata)
        widget.setVisible(True)
        self.qWaitForWindowExposed(widget)

    def testCreateBadNxdata(self):
        widget = CustomNxdataWidget()
        model = widget.model()
        signal = commonh5.Dataset("foo", data=numpy.array([[[5]]]))
        model.createFromSignal(signal)
        axis = commonh5.Dataset("foo", data=numpy.array([[[5]]]))
        nxdataIndex = model.index(0, 0)
        item = model.itemFromIndex(nxdataIndex)
        item.setAxesDatasets([axis])
        nxdata = item.getVirtualGroup()
        self.assertIsNotNone(nxdata)
        self.assertFalse(item.isValid())

    def testRemoveDatasetsFrom(self):
        f = h5py.File(self.data_h5, mode="r")
        try:
            widget = CustomNxdataWidget()
            model = widget.model()
            dataset = f["arrays/integers"]
            model.createFromSignal(dataset)
            widget.removeDatasetsFrom(f)
        finally:
            model.clear()
            f.close()

    def testReplaceDatasetsFrom(self):
        f = h5py.File(self.data_h5, mode="r")
        f2 = h5py.File(self.data2_h5, mode="r")
        try:
            widget = CustomNxdataWidget()
            model = widget.model()
            dataset = f["arrays/integers"]
            model.createFromSignal(dataset)
            widget.replaceDatasetsFrom(f, f2)
        finally:
            model.clear()
            f.close()
            f2.close()


@pytest.mark.usefixtures("qapp")
class TestCustomNxdataWidgetInteraction(TestCaseQt):
    """Test CustomNxdataWidget with user interaction"""

    def setUp(self):
        TestCaseQt.setUp(self)

        self.widget = CustomNxdataWidget()
        self.model = self.widget.model()
        data = numpy.array([[[5]]])
        dataset = commonh5.Dataset("foo", data=data)
        self.model.createFromSignal(dataset)
        self.selectionModel = self.widget.selectionModel()

    def tearDown(self):
        self.selectionModel = None
        self.model.clear()
        self.model = None
        self.widget = None
        TestCaseQt.tearDown(self)

    def testSelectedNxdata(self):
        index = self.model.index(0, 0)
        self.selectionModel.setCurrentIndex(
            index, qt.QItemSelectionModel.ClearAndSelect
        )
        nxdata = self.widget.selectedNxdata()
        self.assertEqual(len(nxdata), 1)
        self.assertIsNot(nxdata[0], None)

    def testSelectedItems(self):
        index = self.model.index(0, 0)
        self.selectionModel.setCurrentIndex(
            index, qt.QItemSelectionModel.ClearAndSelect
        )
        items = self.widget.selectedItems()
        self.assertEqual(len(items), 1)
        self.assertIsNot(items[0], None)
        self.assertIsInstance(items[0], qt.QStandardItem)

    def testRowsAboutToBeRemoved(self):
        self.model.removeRow(0)
        self.qWaitForWindowExposed(self.widget)

    def testPaintItems(self):
        self.widget.expandAll()
        self.widget.setVisible(True)
        self.qWaitForWindowExposed(self.widget)

    def testCreateDefaultContextMenu(self):
        nxDataIndex = self.model.index(0, 0)
        menu = self.widget.createDefaultContextMenu(nxDataIndex)
        self.assertIsNot(menu, None)
        self.assertIsInstance(menu, qt.QMenu)

        signalIndex = self.model.index(0, 0, nxDataIndex)
        menu = self.widget.createDefaultContextMenu(signalIndex)
        self.assertIsNot(menu, None)
        self.assertIsInstance(menu, qt.QMenu)

        axesIndex = self.model.index(1, 0, nxDataIndex)
        menu = self.widget.createDefaultContextMenu(axesIndex)
        self.assertIsNot(menu, None)
        self.assertIsInstance(menu, qt.QMenu)

    def testDropNewDataset(self):
        dataset = commonh5.Dataset("foo", numpy.array([1, 2, 3, 4]))
        mimedata = Hdf5DatasetMimeData(dataset=dataset)
        self.model.dropMimeData(mimedata, qt.Qt.CopyAction, -1, -1, qt.QModelIndex())
        self.assertEqual(self.model.rowCount(qt.QModelIndex()), 2)

    def testDropNewNxdata(self):
        data = numpy.array([[[5]]])
        nxdata = commonh5.Group("foo")
        nxdata.attrs["NX_class"] = "NXdata"
        nxdata.attrs["signal"] = "signal"
        nxdata.create_dataset("signal", data=data)
        mimedata = Hdf5DatasetMimeData(dataset=nxdata)
        self.model.dropMimeData(mimedata, qt.Qt.CopyAction, -1, -1, qt.QModelIndex())
        self.assertEqual(self.model.rowCount(qt.QModelIndex()), 2)

    def testDropAxisDataset(self):
        dataset = commonh5.Dataset("foo", numpy.array([1, 2, 3, 4]))
        mimedata = Hdf5DatasetMimeData(dataset=dataset)
        nxDataIndex = self.model.index(0, 0)
        axesIndex = self.model.index(1, 0, nxDataIndex)
        self.model.dropMimeData(mimedata, qt.Qt.CopyAction, -1, -1, axesIndex)
        self.assertEqual(self.model.rowCount(qt.QModelIndex()), 1)
        item = self.model.itemFromIndex(axesIndex)
        self.assertIsNot(item.getDataset(), None)

    def testMimeData(self):
        nxDataIndex = self.model.index(0, 0)
        signalIndex = self.model.index(0, 0, nxDataIndex)
        mimeData = self.model.mimeData([signalIndex])
        self.assertIsNot(mimeData, None)
        self.assertIsInstance(mimeData, qt.QMimeData)

    def testRemoveNxdataItem(self):
        nxdataIndex = self.model.index(0, 0)
        item = self.model.itemFromIndex(nxdataIndex)
        self.model.removeNxdataItem(item)

    def testAppendAxisToNxdataItem(self):
        nxdataIndex = self.model.index(0, 0)
        item = self.model.itemFromIndex(nxdataIndex)
        self.model.appendAxisToNxdataItem(item)

    def testRemoveAxisItem(self):
        nxdataIndex = self.model.index(0, 0)
        axesIndex = self.model.index(1, 0, nxdataIndex)
        item = self.model.itemFromIndex(axesIndex)
        self.model.removeAxisItem(item)
