import sys
import time
import numpy
import unittest
from PyQt5 import QtGui, QtWidgets
from PyQt5.QtTest import QTest

from PyQt5.QtCore import Qt
from unittest.mock import MagicMock
from unittest.mock import patch

# set up import paths
import path_prepare

from mpl_toolkits.mplot3d import Axes3D
from UnitTesting.TestUtils import QtSignalSpy
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from sas.qtgui.Calculators.GenericScatteringCalculator import GenericScatteringCalculator
from sas.qtgui.Calculators.GenericScatteringCalculator import Plotter3D

from sas.qtgui.MainWindow.DataManager import DataManager
from sas.qtgui.MainWindow.GuiManager import GuiManager
from sas.qtgui.Utilities.GuiUtils import *
from sas.sascalc.calculator import sas_gen

if not QtWidgets.QApplication.instance():
    app = QtWidgets.QApplication(sys.argv)

class GenericScatteringCalculatorTest(unittest.TestCase):
    """Test the GenericScatteringCalculator"""
    def setUp(self):
        """Create the GenericScatteringCalculator"""
        class dummy_manager(object):
            def communicator(self):
                return Communicate()

        self.widget = GenericScatteringCalculator(dummy_manager())

    def tearDown(self):
        """Destroy the GenericScatteringCalculator"""
        self.widget.close()
        self.widget = None


    def testDefaults(self):
        """Test the GUI in its default state"""
        self.assertIsInstance(self.widget, QtWidgets.QWidget)
        self.assertEqual(self.widget.windowTitle(), "Generic Scattering Calculator")

        self.assertIn('trigger_plot_3d', dir(self.widget))

        # Buttons
        self.assertEqual(self.widget.txtData.text(), "Default SLD Profile")
        self.assertEqual(self.widget.cmdLoad.text(), "Load")
        self.assertEqual(self.widget.cmdDraw.text(), "Draw")
        self.assertEqual(self.widget.cmdCompute.text(), "Compute")
        self.assertEqual(self.widget.cmdReset.text(), "Reset")
        self.assertEqual(self.widget.cmdClose.text(), "Close")
        self.assertEqual(self.widget.cmdHelp.text(), "Help")
        self.assertEqual(self.widget.cmdDrawpoints.text(), "Draw Points")
        self.assertEqual(self.widget.cmdSave.text(), "Save SLD Data")

        self.assertEqual(self.widget.txtBackground.text(), '0.0')
        self.assertEqual(self.widget.txtScale.text(), '1.0')
        self.assertEqual(self.widget.txtSolventSLD.text(), '0.0')
        self.assertEqual(self.widget.txtTotalVolume.text(), '216000.0')
        self.assertEqual(self.widget.txtUpFracIn.text(), '1.0')
        self.assertEqual(self.widget.txtUpFracOut.text(), '1.0')
        self.assertEqual(self.widget.txtUpTheta.text(), '0.0')
        self.assertEqual(self.widget.txtNoQBins.text(), '50')
        self.assertEqual(self.widget.txtQxMax.text(), '0.3')
        self.assertEqual(self.widget.txtNoPixels.text(), '1000')
        self.assertEqual(self.widget.txtMx.text(), '0')
        self.assertEqual(self.widget.txtMy.text(), '0')
        self.assertEqual(self.widget.txtMz.text(), '0')
        self.assertEqual(self.widget.txtNucl.text(), '6.97e-06')
        self.assertEqual(self.widget.txtXnodes.text(), '10')
        self.assertEqual(self.widget.txtYnodes.text(), '10')
        self.assertEqual(self.widget.txtZnodes.text(), '10')
        self.assertEqual(self.widget.txtXstepsize.text(), '6')
        self.assertEqual(self.widget.txtYstepsize.text(), '6')
        self.assertEqual(self.widget.txtZstepsize.text(), '6')

        # Comboboxes
        self.assertFalse(self.widget.cbOptionsCalc.isVisible())
        self.assertFalse(self.widget.cbOptionsCalc.isEditable())
        self.assertEqual(self.widget.cbOptionsCalc.count(), 2)
        self.assertEqual(self.widget.cbOptionsCalc.currentIndex(), 0)
        self.assertListEqual([self.widget.cbOptionsCalc.itemText(i) for i in
                              range(self.widget.cbOptionsCalc.count())],
                             ['Fixed orientation', 'Debye full avg.'])

        self.assertEqual(self.widget.cbShape.count(), 1)
        self.assertEqual(self.widget.cbShape.currentIndex(), 0)
        self.assertListEqual([self.widget.cbShape.itemText(i) for i in
                              range(self.widget.cbShape.count())],
                             ['Rectangular'])
                             #['Rectangular', 'Ellipsoid'])
        self.assertFalse(self.widget.cbShape.isEditable())
        # disable buttons
        self.assertFalse(self.widget.cmdSave.isEnabled())
        self.assertFalse(self.widget.cmdDraw.isEnabled())
        self.assertFalse(self.widget.cmdDrawpoints.isEnabled())

    def testHelpButton(self):
        """ Assure help file is shown """
        self.widget.manager.showHelp = MagicMock()
        self.widget.onHelp()
        self.assertTrue(self.widget.manager.showHelp.called_once())
        args = self.widget.manager.showHelp.call_args
        self.assertIn('sas_calculator_help.html', args[0][0])

    def testValidator(self):
        """ Test the inputs when validators had been defined """
        # Background, Volume and Scale should be positive
        txtEdit_positive = [self.widget.txtBackground,
                            self.widget.txtTotalVolume,
                            self.widget.txtScale]

        for item in txtEdit_positive:
            item.setText('-1')
            state = item.validator().validate(item.text(), 0)[0]
            self.assertEqual(state, QtGui.QValidator.Invalid)

        for item in txtEdit_positive:
            item.setText('2')
            state = item.validator().validate(item.text(), 0)[0]
            self.assertEqual(state, QtGui.QValidator.Acceptable)

        for item in txtEdit_positive:
            item.setText('abc')
            state = item.validator().validate(item.text(), 0)[0]
            self.assertEqual(state, QtGui.QValidator.Invalid)

        # Fraction of spin up between 0 and 1
        txtEdit_0_1 = [self.widget.txtUpFracIn, self.widget.txtUpFracOut]

        for item in txtEdit_0_1:
            item.setText('-1.04546')
            state = item.validator().validate(item.text(), 0)[0]
            self.assertEqual(state, QtGui.QValidator.Invalid)

        for item in txtEdit_0_1:
            item.setText('2.00000')
            state = item.validator().validate(item.text(), 0)[0]
            self.assertEqual(state, QtGui.QValidator.Invalid)

        for item in txtEdit_0_1:
            item.setText('0.000000005')
            state = item.validator().validate(item.text(), 0)[0]
            self.assertEqual(state, QtGui.QValidator.Acceptable)

        # Test text edits related to Q:
        # 0< Qmax < 1000, 2 <= Qbins <= 1000
        txtEdit_q_values = [self.widget.txtNoQBins, self.widget.txtQxMax]
        for item in txtEdit_q_values:
            item.setText('-1.01')
            state = item.validator().validate(item.text(), 0)[0]
            self.assertEqual(state, QtGui.QValidator.Invalid)

        for item in txtEdit_q_values:
            item.setText('1500.01')
            state = item.validator().validate(item.text(), 0)[0]
            self.assertEqual(state, QtGui.QValidator.Invalid)

        self.widget.txtNoQBins.setText('1.5')
        self.assertEqual(
            self.widget.txtNoQBins.validator().validate(item.text(), 0)[0],
            QtGui.QValidator.Invalid)

        self.widget.txtQxMax.setText('1.5')
        self.assertEqual(
            self.widget.txtQxMax.validator().validate(item.text(), 0)[0],
            QtGui.QValidator.Acceptable)

    def testLoadedSLDData(self):
        """
        Load sld data and check modifications of GUI
        """
        filename = os.path.join("UnitTesting", "sld_file.sld")
        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=[filename, ''])
        self.widget.loadFile()

        # check modification of text in Load button
        self.assertEqual(self.widget.cmdLoad.text(), 'Loading...')
        # wait a bit for data to be loaded
        time.sleep(0.1)
        QtWidgets.qApp.processEvents()

        # check updated values in ui, read from loaded file
        self.assertEqual(self.widget.txtData.text(), 'sld_file.sld')
        self.assertEqual(self.widget.txtTotalVolume.text(), '402408.0')
        self.assertEqual(self.widget.txtNoPixels.text(), '552')
        self.assertFalse(self.widget.txtNoPixels.isEnabled())

        # check disabled TextEdits according to data format
        self.assertFalse(self.widget.txtUpFracIn.isEnabled())
        self.assertFalse(self.widget.txtUpFracOut.isEnabled())
        self.assertFalse(self.widget.txtUpFracOut.isEnabled())
        self.assertFalse(self.widget.txtNoPixels.isEnabled())

        # check enabled draw buttons
        self.assertTrue(self.widget.cmdDraw.isEnabled())
        self.assertTrue(self.widget.cmdDrawpoints.isEnabled())
        self.widget.show()
        self.assertTrue(self.widget.isVisible())
        self.assertFalse(self.widget.cbOptionsCalc.isVisible())

        # check that text of loadButton is back to initial state
        self.assertEqual(self.widget.cmdLoad.text(), 'Load')
        # check values and enabled / disabled for
        # Mx,y,z x,y,znodes and x,y,zstepsize buttons
        self.assertFalse(self.widget.txtMx.isEnabled())
        self.assertAlmostEqual(float(self.widget.txtMx.text()), 8.0795e-07, 4)
        self.assertFalse(self.widget.txtMy.isEnabled())
        self.assertAlmostEqual(float(self.widget.txtMy.text()), 8.0795e-07, 4)
        self.assertFalse(self.widget.txtMz.isEnabled())
        self.assertAlmostEqual(float(self.widget.txtMz.text()), 3.1739e-07, 4)
        self.assertTrue(self.widget.txtNucl.isEnabled())
        self.assertEqual(self.widget.txtNucl.text(), '0')

        self.assertFalse(self.widget.txtXnodes.isEnabled())
        self.assertEqual(self.widget.txtXnodes.text(), '10')
        self.assertFalse(self.widget.txtYnodes.isEnabled())
        self.assertEqual(self.widget.txtYnodes.text(), '10')
        self.assertFalse(self.widget.txtZnodes.isEnabled())
        self.assertEqual(self.widget.txtZnodes.text(), '10')

        self.assertFalse(self.widget.txtXstepsize.isEnabled())
        self.assertEqual(self.widget.txtXstepsize.text(), '9')
        self.assertFalse(self.widget.txtYstepsize.isEnabled())
        self.assertEqual(self.widget.txtYstepsize.text(), '9')
        self.assertFalse(self.widget.txtZstepsize.isEnabled())
        self.assertEqual(self.widget.txtZstepsize.text(), '9')

        self.assertTrue(self.widget.sld_data.is_data)

        # self.assertTrue(self.widget.trigger_plot_3d)


    def testLoadedPDBButton(self):
        """
        Load pdb data and check modifications of GUI
        """
        filename = os.path.join("UnitTesting", "diamdsml.pdb")

        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=[filename, ''])
        self.widget.loadFile()

        # check modification of text in Load button
        self.assertEqual(self.widget.cmdLoad.text(), 'Loading...')

        time.sleep(1)
        QtWidgets.qApp.processEvents()

        # check updated values in ui, read from loaded file
        # TODO to be changed
        self.assertEqual(self.widget.txtData.text(), 'diamdsml.pdb')
        self.assertAlmostEqual(float(self.widget.txtTotalVolume.text()), 170.95058, 5)
        self.assertEqual(self.widget.txtNoPixels.text(), '18')

        # check disabled TextEdits according to data format
        self.assertFalse(self.widget.txtUpFracIn.isEnabled())
        self.assertFalse(self.widget.txtUpFracOut.isEnabled())
        self.assertFalse(self.widget.txtUpFracOut.isEnabled())
        self.assertFalse(self.widget.txtNoPixels.isEnabled())
        # check enabled draw buttons
        self.assertTrue(self.widget.cmdDraw.isEnabled())
        self.assertTrue(self.widget.cmdDrawpoints.isEnabled())
        # fixed orientation
        self.widget.show()
        self.assertTrue(self.widget.isVisible())
        self.assertTrue(self.widget.cbOptionsCalc.isVisible())
        # check that text of loadButton is back to initial state
        self.assertEqual(self.widget.cmdLoad.text(), 'Load')
        self.assertTrue(self.widget.cmdLoad.isEnabled())

        # check values and enabled / disabled for
        # Mx,y,z x,y,znodes and x,y,zstepsize buttons
        self.assertFalse(self.widget.txtMx.isEnabled())
        self.assertEqual(self.widget.txtMx.text(), '0')
        self.assertFalse(self.widget.txtMy.isEnabled())
        self.assertEqual(self.widget.txtMy.text(), '0')
        self.assertFalse(self.widget.txtMz.isEnabled())
        self.assertEqual(self.widget.txtMz.text(), '0')
        self.assertFalse(self.widget.txtNucl.isEnabled())
        self.assertAlmostEqual(float(self.widget.txtNucl.text()), 7.0003e-06, 4)

        self.assertFalse(self.widget.txtXnodes.isEnabled())
        self.assertEqual(self.widget.txtXnodes.text(), 'NaN')
        self.assertFalse(self.widget.txtYnodes.isEnabled())
        self.assertEqual(self.widget.txtYnodes.text(), 'NaN')
        self.assertFalse(self.widget.txtZnodes.isEnabled())
        self.assertEqual(self.widget.txtZnodes.text(), 'NaN')

        self.assertFalse(self.widget.txtXstepsize.isEnabled())
        self.assertEqual(self.widget.txtXstepsize.text(), 'NaN')
        self.assertFalse(self.widget.txtYstepsize.isEnabled())
        self.assertEqual(self.widget.txtYstepsize.text(), 'NaN')
        self.assertFalse(self.widget.txtZstepsize.isEnabled())
        self.assertEqual(self.widget.txtZstepsize.text(), 'NaN')

        self.assertTrue(self.widget.sld_data.is_data)

    # TODO
    def testLoadedOMFButton(self):
        """
        Load omf data and check modifications of GUI
        """
        filename = os.path.join("UnitTesting", "A_Raw_Example-1.omf")

        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=[filename, ''])
        self.widget.loadFile()
        self.assertEqual(self.widget.cmdLoad.text(), 'Loading...')
        time.sleep(2)
        QtWidgets.qApp.processEvents()

        self.assertEqual(self.widget.txtData.text(), 'A_Raw_Example-1.omf')
        self.assertEqual(self.widget.txtTotalVolume.text(), '128000000.0')
        self.assertEqual(self.widget.txtNoPixels.text(), '16000')

        # check disabled TextEdits according to data format
        self.assertFalse(self.widget.txtUpFracIn.isEnabled())
        self.assertFalse(self.widget.txtUpFracOut.isEnabled())
        self.assertFalse(self.widget.txtUpFracOut.isEnabled())
        self.assertFalse(self.widget.txtNoPixels.isEnabled())

        # check enabled draw buttons
        self.assertTrue(self.widget.cmdDraw.isEnabled())
        self.assertTrue(self.widget.cmdDrawpoints.isEnabled())

        # check that text of loadButton is back to initial state
        self.assertEqual(self.widget.cmdLoad.text(), 'Load')
        self.assertTrue(self.widget.cmdLoad.isEnabled())

        # check values and enabled / disabled for
        # Mx,y,z x,y,znodes and x,y,zstepsize buttons
        self.assertFalse(self.widget.txtMx.isEnabled())
        self.assertAlmostEqual(float(self.widget.txtMx.text()), 7.855e-09, 4)
        self.assertFalse(self.widget.txtMy.isEnabled())
        self.assertAlmostEqual(float(self.widget.txtMy.text()), 4.517e-08, 4)
        self.assertFalse(self.widget.txtMz.isEnabled())
        self.assertAlmostEqual(float(self.widget.txtMz.text()), 9.9511e-10, 4)
        self.assertTrue(self.widget.txtNucl.isEnabled())
        self.assertEqual(self.widget.txtNucl.text(), '0')

        self.assertFalse(self.widget.txtXnodes.isEnabled())
        self.assertEqual(self.widget.txtXnodes.text(), '40')
        self.assertFalse(self.widget.txtYnodes.isEnabled())
        self.assertEqual(self.widget.txtYnodes.text(), '40')
        self.assertFalse(self.widget.txtZnodes.isEnabled())
        self.assertEqual(self.widget.txtZnodes.text(), '10')

        self.assertFalse(self.widget.txtXstepsize.isEnabled())
        self.assertEqual(self.widget.txtXstepsize.text(), '20')
        self.assertFalse(self.widget.txtYstepsize.isEnabled())
        self.assertEqual(self.widget.txtYstepsize.text(), '20')
        self.assertFalse(self.widget.txtZstepsize.isEnabled())
        self.assertEqual(self.widget.txtZstepsize.text(), '20')

    def testReset(self):
        """
        Test reset button when GUI has been modified
        """
        # modify gui
        self.widget.txtBackground.setText('50.0')
        # apply reset
        self.widget.onReset()
        # check that we get back to the initial state
        self.assertEqual(self.widget.txtBackground.text(), '0.0')

    # TODO check plots
    def testCompute(self):
        """
        Test compute button
        """
        # load data
        filename = os.path.join("UnitTesting", "diamdsml.pdb")

        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=[filename, ''])
        self.widget.loadFile()
        time.sleep(1)
        QTest.mouseClick(self.widget.cmdCompute, Qt.LeftButton)
        # check modification of text of Compute button
        self.assertEqual(self.widget.cmdCompute.text(), 'Wait...')
        self.assertFalse(self.widget.cmdCompute.isEnabled())

        #self.widget.complete([numpy.ones(1), numpy.zeros(1), numpy.zeros(1)], update=None)
        #self.assertEqual(self.widget.cmdCompute.text(), 'Compute')
        #self.assertTrue(self.widget.cmdCompute.isEnabled())

    # TODO
    def testDrawButton(self):
        """
        Test Draw buttons for 3D plots with and without arrows
        """
        self.assertFalse(self.widget.cmdDraw.isEnabled())
        filename = os.path.join("UnitTesting", "diamdsml.pdb")
        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=[filename,''])
        self.widget.loadFile()
        self.assertEqual(self.widget.cmdLoad.text(), 'Loading...')
        time.sleep(1)
        QtWidgets.qApp.processEvents()

        self.assertTrue(self.widget.cmdDraw.isEnabled())
        QTest.mouseClick(self.widget.cmdDraw, Qt.LeftButton)

        self.assertTrue(self.widget.cmdDrawpoints.isEnabled())
        QTest.mouseClick(self.widget.cmdDrawpoints, Qt.LeftButton)

    def testCloseButton(self):
        closeButton = self.widget.cmdClose
        QTest.mouseClick(closeButton, Qt.LeftButton)

    def testSaveFile(self):
        """
        Test Save feature to .sld file
        """
        filename = os.path.join("UnitTesting", "sld_file.sld")

        QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=[filename, ''])
        self.widget.loadFile()

        time.sleep(0.1)
        QtWidgets.qApp.processEvents()

        filename1 = "test"
        QtWidgets.QFileDialog.getSaveFileName = MagicMock(return_value=[filename1, ''])

        #QTest.mouseClick(self.widget.cmdSave, Qt.LeftButton)
        self.widget.onSaveFile()
        QtWidgets.qApp.processEvents()

        self.assertTrue(os.path.isfile(filename1 + '.sld'))
        self.assertTrue(os.path.getsize(filename1 + '.sld') > 0)

        os.remove("test.sld")


class Plotter3DTest(unittest.TestCase):
    """ Test 3D plots in real space.
    The implementation is temporarily in the same script as the Generic SAS
    calculator"""
    def setUp(self):
        """create"""
        parent_test = MagicMock()
        self.plotter = Plotter3D(parent=parent_test, graph_title='test')
        self.data = sas_gen.MagSLD(numpy.array([1.0, 2.0, 3.0, 4.0]),
                                   numpy.array([10.0, 11.0, 12.0, 13.0]),
                                   numpy.array([0.1, 0.2, 0.3, 0.4]),
                                   numpy.zeros(4),
                                   numpy.zeros(4),
                                   numpy.zeros(4),
                                   numpy.zeros(4))
        self.data.sld_n = [0, 6.97e-06, 6.97e-06, 6.97e-06]
        self.data.set_pix_type('pixel')
        self.data.pix_symbol = numpy.repeat('pixel', 4)

    def tearDown(self):
        """ Destroy"""
        self.plotter = None
        self.data = None

    def testDataProperty(self):
        self.plotter.data = self.data
        self.assertEqual(self.plotter.data, self.data)
        self.assertTrue(self.plotter.graph_title, 'test')
        self.assertFalse(self.plotter.data.has_conect)

    def testShowNoPlot(self):
        FigureCanvas.draw_idle = MagicMock()
        FigureCanvas.draw = MagicMock()
        self.plotter.showPlot(data=None)
        self.assertFalse(FigureCanvas.draw_idle.called)
        self.assertFalse(FigureCanvas.draw.called)

    def testShow3DPlot(self):
        FigureCanvas.draw = MagicMock()
        Axes3D.plot = MagicMock()

        self.plotter.data = self.data
        self.plotter.showPlot(data=self.plotter.data)
        self.assertTrue(Axes3D.plot.called)
        self.assertTrue(FigureCanvas.draw.called)


if __name__ == "__main__":
    unittest.main()
