"""
    Unit tests for specific models
    @author: Mathieu Doucet / UTK
"""
from __future__ import print_function

import unittest, math, time

# Disable "missing docstring" complaint
# pylint: disable-msg=C0111
# Disable "too many methods" complaint
# pylint: disable-msg=R0904
# Disable "could be a function" complaint
# pylint: disable-msg=R0201

from sasmodels.sasview_model import _make_standard_model
EllipsoidModel = _make_standard_model('ellipsoid')
SphereModel = _make_standard_model('sphere')
CylinderModel = _make_standard_model('cylinder')
CoreShellModel = _make_standard_model('core_shell_sphere')

import sas.sascalc.realspace.VolumeCanvas as VolumeCanvas

class TestRealSpaceModel(unittest.TestCase):
    """ Unit tests for sphere model """

    def setUp(self):
        self.model = VolumeCanvas.VolumeCanvas()
        self.model.add('cylinder', 'cyl')
        self.model.add('sphere', 'sph')
        self.model.add('ellipsoid', 'elli')
        self.model.add('singlehelix', 'shelix')

    def testAdding(self):
        self.assertEqual('cyl', self.model.add('cylinder', 'cyl'))

    def testDeleting(self):
        self.model.add('ellipsoid','elli2')
        self.model.delete('elli2')
        self.assertTrue('elli2' not in self.model.getShapeList())

    def testsetParam(self):
        self.model.setParam('q_max', 0.2)
        self.model.setParam('shelix.radius_helix', 12)

    def testgetParamList(self):
        #print self.model.getParamList()
        #print self.model.getParamList('shelix')
        pass

    def testPr_Iq(self):
        self.model.getPr()
        #print "pr is calculated", self.model.hasPr
        result = self.model.getIq(0.1)
        #print "I(0.1) is calculated: ", result

class TestSphere(unittest.TestCase):
    """ Unit tests for sphere model """

    def setUp(self):
        self.canvas = VolumeCanvas.VolumeCanvas()


    def testSetQmax(self):
        old_value = self.canvas.getParam('q_max')
        new_value = old_value + 0.1
        self.canvas.setParam('q_max', new_value)
        self.assertEqual(self.canvas.getParam("Q_MAx"), new_value)

    def testSetDensity(self):
        self.canvas.setParam('lores_density', 0.1)
        handle = self.canvas.add('sphere')
        self.canvas.setParam("%s.radius" % handle, 5.0)
        vol = 4/3*math.pi*5*5*5
        npts_1 = vol/0.1
        value_1 = self.canvas.getIq(0.001)

        # Change density, the answer should be the same
        self.canvas.setParam('lores_density', 0.2)
        npts_2 = vol/0.2
        value_2 = self.canvas.getIq(0.001)

        self.assertTrue( (value_1-value_2)/value_1 < 0.1)

    def testSetDensityTiming(self):
        """Testing change in computation time with density"""
        handle = self.canvas.add('sphere')
        self.canvas.setParam("%s.radius" % handle, 15.0)

        self.canvas.setParam('lores_density', 0.6)
        t_0 = time.time()
        self.canvas.getIq(0.001)
        t_1 = time.time()-t_0

        # Change density, the answer should be the same
        self.canvas.setParam('lores_density', 0.1)
        t_0 = time.time()
        self.canvas.getIq(0.001)
        t_2 = time.time()-t_0

        self.assertTrue( t_2 < t_1 and (t_1-t_2)/t_2 > 2)

    def testGetParamList(self):
        """ Test GetParamList on empty canvas"""
        self.assertTrue('lores_density' in self.canvas.getParamList())
        handle = self.canvas.add('sphere')

    def testGetParamListWithShape(self):
        """ Test GetParamList on filled canvas"""
        self.canvas.add('sphere')
        self.assertTrue('lores_density' in self.canvas.getParamList())

    def testAdd(self):
        handle = "s1"
        self.assertEqual(handle, self.canvas.add('sphere', handle))

        #TODO: test for current list of shape
        self.assertEqual( [handle] , self.canvas.getShapeList())

    def testSetRadius(self):
        handle = self.canvas.add('sphere')
        self.canvas.setParam("%s.radius" % handle, 24.0)
        self.assertEqual(self.canvas.getParam("%s.radius" % handle), 24.0)

    def testGetIq(self):
        """ Test the output of I(q) to the analytical solution
            If the normalization is wrong, we will have to fix it.

            getIq() should call getPr() behind the scenes so that
            the user doesnt have to do it if he doesn't need to.
        """
        sphere = SphereModel()
        sphere.setParam('scale', 1.0)
        sphere.setParam('background', 0.0)
        sphere.setParam('sld', 1.0)
        sphere.setParam('sld_solvent', 0.0)
        sphere.setParam('radius', 10.0)

        handle = self.canvas.add('sphere')
        self.canvas.setParam('%s.radius' % handle, 10.0)
        self.canvas.setParam('%s.contrast' % handle, 1.0)


        sim_1 = self.canvas.getIq(0.001)
        ana_1 = sphere.run(0.001)
        sim_2 = self.canvas.getIq(0.01)
        ana_2 = sphere.run(0.01)

        # test the shape of the curve (calculate relative error
        # on the output and it should be compatible with zero
        # THIS WILL DEPEND ON THE NUMBER OF SPACE POINTS:
        # that why we need some error analysis.
        self.assertTrue( (sim_2*ana_1/sim_1 - ana_2)/ana_2 < 0.1)

        # test the absolute amplitude
        self.assertTrue( math.fabs(sim_2-ana_2)/ana_2 < 0.1)

    def testGetIq2(self):
        """ Test two different q values
        """
        handle = self.canvas.add('sphere')
        self.canvas.setParam('%s.radius' % handle, 10.0)

        sim_1 = self.canvas.getIq(0.001)
        sim_2 = self.canvas.getIq(0.01)

        self.assertNotAlmostEqual(sim_2, sim_1, 3)

    def testGetIq_Identical(self):
        """ Test for identical model / no param change
        """
        handle = self.canvas.add('sphere')
        self.canvas.setParam('%s.radius' % handle, 10.0)

        sim_1 = self.canvas.getIq(0.01)
        sim_2 = self.canvas.getIq(0.01)

        self.assertEqual(sim_2, sim_1)

    def testGetIq_Identical2(self):
        """ Test for identical model after a parameter change
            Should be different only of the space points
            are regenerated and the random seed is different
        """
        handle = self.canvas.add('sphere')
        self.canvas.setParam('%s.radius' % handle, 10.0)

        self.canvas.setParam('lores_density', 0.1)
        sim_1 = self.canvas.getIq(0.01)

        # Try to fool the code by changing to a different value
        self.canvas.setParam('lores_density', 0.2)
        self.canvas.getIq(0.01)

        self.canvas.setParam('lores_density', 0.1)
        sim_2 = self.canvas.getIq(0.01)

        self.assertTrue((sim_2-sim_1)/sim_1<0.05)

    def testGetIq_time(self):
        """ Time profile
        """
        handle = self.canvas.add('sphere')
        self.canvas.setParam('%s.radius' % handle, 15.0)


        self.canvas.setParam('lores_density', 0.1)
        t_0 = time.time()
        sim_1 = self.canvas.getIq(0.01)
        delta_1 = time.time()-t_0

        self.canvas.setParam('lores_density', 0.1)

        t_0 = time.time()
        sim_2 = self.canvas.getIq(0.01)
        delta_2 = time.time()-t_0

        self.assertTrue((delta_2-delta_1)/delta_1<0.05)


    def testGetPr(self):
        """Compare the output of P(r) to the theoretical value"""
        #TODO: find a way to compare you P(r) to the known
        # analytical value.
        pass

    def testLogic1(self):
        """ Test that the internal logic is set so that the user
            get the right output after changing a parameter
        """

        handle = self.canvas.add('sphere')
        self.canvas.setParam('%s.radius' % handle, 10.0)
        result_1 = self.canvas.getIq(0.1)
        self.canvas.setParam('%s.radius' % handle, 20.0)
        result_2 = self.canvas.getIq(0.1)
        self.assertNotAlmostEqual(result_1, result_2, 2)

class TestCanvas(unittest.TestCase):
    """ Unit tests for all shapes in canvas model """

    def setUp(self):
        self.canvas = VolumeCanvas.VolumeCanvas()
        self.canvas.params['lores_density'] = 0.05

    def testGetIq_cylinder(self):
        handle = self.canvas.add('cylinder','cyl')
        self.canvas.setParam('%s.radius' % handle, 15.0)
        self.canvas.setParam('%s.length' % handle, 50.0)
        self.assertEqual(50,self.canvas.getParam('%s.length'%handle))
        result_1 = self.canvas.getIq(0.1)
        result_2 = self.canvas.getIq(0.1)
        self.assertEqual(result_1,result_2)

        self.canvas.delete(handle)
        handle2 = self.canvas.add('cylinder','cyl2')
        self.assertEqual(40,self.canvas.getParam('%s.length'%handle2))
        result_3 = self.canvas.getIq(0.1)
        self.assertNotEqual(result_1, result_3)

    def testGetIq_ellipsoid(self):
        handle = self.canvas.add('ellipsoid','elli')
        self.canvas.setParam('%s.radius_x' % handle, 35)
        self.canvas.setParam('%s.radius_y' % handle, 20)
        self.canvas.setParam('%s.radius_z' % handle, 10)
        result_1 = self.canvas.getIq(0.1)
        result_2 = self.canvas.getIq(0.1)
        self.assertEqual(result_1,result_2)

        self.canvas.delete(handle)
        self.assertEqual(False,self.canvas.hasPr)
        handle2 = self.canvas.add('ellipsoid','elli2')
        result_3 = self.canvas.getIq(0.1)
        self.assertNotEqual(result_1, result_3)

    def testGetIq_singlehelix(self):
        handle = self.canvas.add('singlehelix','shelix')
        self.canvas.setParam('%s.radius_helix' % handle, 11)
        self.canvas.setParam('%s.radius_tube' % handle, 4)
        self.canvas.setParam('%s.pitch' % handle, 30)
        self.canvas.setParam('%s.turns' % handle, 3.2)
        result_1 = self.canvas.getIq(0.1)
        result_2 = self.canvas.getIq(0.1)
        self.assertEqual(result_1,result_2)

        self.canvas.delete(handle)
        self.assertEqual(False,self.canvas.hasPr)
        handle2 = self.canvas.add('singlehelix','shelix2')
        result_3 = self.canvas.getIq(0.1)
        self.assertNotEqual(result_1, result_3)

class TestOrdering(unittest.TestCase):
    """ Unit tests for all shapes in canvas model """

    def setUp(self):
        radius = 15
        thickness = 5
        core_vol = 4.0/3.0*math.pi*radius*radius*radius
        outer_radius = radius+thickness
        shell_vol = 4.0/3.0*math.pi*outer_radius*outer_radius*outer_radius - core_vol
        self.shell_sld = -1.0*core_vol/shell_vol

        self.canvas = VolumeCanvas.VolumeCanvas()
        self.canvas.params['lores_density'] = 0.1

        # Core shell model
        sphere = CoreShellModel()
        sphere.setParam('scale', 1.0)
        sphere.setParam('background', 0.0)
        sphere.setParam('sld_core', 1.0)
        sphere.setParam('sld_shell', self.shell_sld)
        sphere.setParam('sld_solvent', 0.0)
        # Core radius
        sphere.setParam('radius', radius)
        # Shell thickness
        sphere.setParam('thickness', thickness)
        self.sphere = sphere
        self.radius = radius
        self.outer_radius = outer_radius

    def set_coreshell_on_canvas(self, order1=None, order2=None):

        handle = self.canvas.add('sphere')
        self.canvas.setParam('scale' , 1.0)
        self.canvas.setParam('background' , 0.0)

        self.canvas.setParam('%s.radius' % handle, self.outer_radius)
        self.canvas.setParam('%s.contrast' % handle, self.shell_sld)
        if order1 is not None:
            self.canvas.setParam('%s.order' % handle, order1)

        handle2 = self.canvas.add('sphere')
        self.canvas.setParam('%s.radius' % handle2, self.radius)
        self.canvas.setParam('%s.contrast' % handle2, 1.0)
        if order2 is not None:
            self.canvas.setParam('%s.order' % handle2, order2)


    def testDefaultOrder(self):
        self.set_coreshell_on_canvas()

        ana = self.sphere.run(0.05)
        val, err = self.canvas.getIqError(0.05)
        self.assertTrue(math.fabs(ana-val)<2.0*err)

    def testRightOrder(self):
        self.set_coreshell_on_canvas(3.0, 6.0)

        ana = self.sphere.run(0.05)
        val, err = self.canvas.getIqError(0.05)
        #print 'right', ana, val, err
        self.assertTrue(math.fabs(ana-val)/ana < 1.1)

    def testWrongOrder(self):
        self.set_coreshell_on_canvas(1, 0)

        sphere = SphereModel()
        sphere.setParam('scale', 1.0)
        sphere.setParam('background', 0.0)
        sphere.setParam('radius', self.outer_radius)
        sphere.setParam('sld', self.shell_sld)
        sphere.setParam('sld_solvent', 0.0)

        ana = sphere.run(0.05)
        val, err = self.canvas.getIqError(0.05)
        #print 'wrong', ana, val, err
        self.assertTrue(math.fabs(ana-val)/ana < 1.1)


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