"""
Test of rotation/positioning of a simple cubic particle.
The transformed particle is compared with the original one.
"""

import unittest
import PyFuTestInfrastructure as infrastruct
from bornagain import *


class RotationsCubeTest(unittest.TestCase):
    """
    Test of rotations and translations of a simple cube in a 3-layer system
    """

    def get_sample(self,
                   formfactor,
                   rot=None,
                   pos=None,
                   layout_rot=None,
                   layout_pos=None,
                   add_to="Vacuum"):
        mParticle = RefractiveMaterial("Particle", (0.86, 0.24, 0.18), 6e-4,
                                       2e-8)
        mMiddle = RefractiveMaterial("MiddleLayer", (0.70, 0.73, 0.78), 5e-5,
                                     2e-8)
        mSubstrate = RefractiveMaterial("Substrate", (0.55, 0.55, 0.55), 6e-6,
                                        2e-8)

        particle = Particle(mParticle, formfactor)
        if rot:
            particle.rotate(rot)
        if pos:
            particle.translate(pos)

        if layout_rot:
            particle.rotate(layout_rot)
        if layout_pos:
            particle.translate(layout_pos)

        vacuum_layer = Layer(Vacuum())
        middle_layer = Layer(mMiddle, 50)
        substrate = Layer(mSubstrate)

        if add_to == "Vacuum":
            vacuum_layer.deposit2D(Dilute2D(0.001, particle))
        else:
            middle_layer.deposit2D(Dilute2D(0.001, particle))

        sample = Sample()
        sample.addLayer(vacuum_layer)
        sample.addLayer(middle_layer)
        sample.addLayer(substrate)
        return sample

    def get_result(self, data, add_to="Vacuum"):
        ff = data[0]
        rot = data[1]
        pos = data[2]
        layout_rot = data[3]
        layout_pos = data[4]
        sample = self.get_sample(ff, rot, pos, layout_rot, layout_pos, add_to)
        simulation = infrastruct.get_simulation_MiniGISAS(sample)
        simulation.options().setUseAvgMaterials(False)
        return simulation.simulate()

    def testRotationZ(self):
        """
        Cube is Z-rotated either through the particle or through the layout.
        The result is compared with the unrotated cube.
        """
        box = Box(10, 10, 10)

        data_to_test = [
            # ff, rot, pos, layout_rot, layout_pos
            (box, None, None, None, None),  # reference
            (box, RotationZ(90. * deg), None, None, None),  # via particle
            (box, RotationZ(-90. * deg), None, None, None),
            (box, RotationZ(180. * deg), None, None, None),
            (box, None, None, RotationZ(90. * deg), None),  # via layout
            (box, RotationZ(45. * deg), None, RotationZ(45. * deg),
             None),  # cumulative
        ]

        reference = self.get_result(data_to_test[0])
        for i in range(1, len(data_to_test)):
            simulated = self.get_result(data_to_test[i])
            self.assertTrue(
                checkRelativeDifference(simulated, reference, 1e-10))

    def testRotationY(self):
        """
        Cube is Y-rotated either through the particle or through the layout.
        Under the current alignment model deposit2D bottom-anchors the rotated
        cube, so no compensating translation is needed; the result must match
        the unrotated cube.
        """
        box = Box(10, 10, 10)

        data_to_test = [
            # ff rot pos layout_rot layout_pos
            (box, None, None, None, None),  # reference
            (box, RotationY(90. * deg), None, None, None),  # via particle
            (box, None, None, RotationY(90. * deg), None),  # via layout
            (box, RotationY(45. * deg), None, RotationY(45. * deg),
             None),  # cumulative 90 deg
        ]

        reference = self.get_result(data_to_test[0])
        for i in range(1, len(data_to_test)):
            simulated = self.get_result(data_to_test[i])
            self.assertTrue(
                checkRelativeDifference(simulated, reference, 1e-10))

    def testRotationX(self):
        """
        Cube is X-rotated either through the particle or through the layout.
        Under the current alignment model deposit2D bottom-anchors the rotated
        cube, so no compensating translation is needed; the result must match
        the unrotated cube.
        """
        box = Box(10, 10, 10)

        data_to_test = [
            # ff rot pos layout_rot layout_pos
            (box, None, None, None, None),  # reference
            (box, RotationX(90. * deg), None, None, None),  # via particle
            (box, None, None, RotationX(90. * deg), None),  # via layout
            (box, RotationX(45. * deg), None, RotationX(45. * deg),
             None),  # cumulative 90 deg
        ]

        reference = self.get_result(data_to_test[0])
        for i in range(1, len(data_to_test)):
            simulated = self.get_result(data_to_test[i])
            self.assertTrue(
                checkRelativeDifference(simulated, reference, 1e-10))

    def testRotationsInMiddleLayer(self):
        """
        Cube deposited inside the middle layer, X-rotated about its reference
        point. Bottom-anchoring keeps it at the same place as the unrotated
        cube, so the two must scatter identically.
        """
        box = Box(10, 10, 10)

        data_to_test = [
            # ff rot pos layout_rot layout_pos
            (box, None, R3(0, 0, 20), None, None),  # reference
            (box, RotationX(90. * deg), R3(0, 0, 20), None, None),  # rotated
        ]

        reference = self.get_result(data_to_test[0], "Middle")
        for i in range(1, len(data_to_test)):
            simulated = self.get_result(data_to_test[i], "Middle")
            self.assertTrue(
                checkRelativeDifference(simulated, reference, 1e-10))


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