import unittest
import numpy  # type: ignore

from parameterized import parameterized  # type: ignore

from BatchArrangement import BatchArrangement

# pylint: disable=protected-access

class TestBatchArrangement(unittest.TestCase):

    @staticmethod
    def createOne():
        machineDescr_One = {
            "machineID": "bigrep_onev3",
            "isStudio": False,
            "machine_size": [1000, 1000],
            "machine_preferedStartPoint": [200, 200],
            "adhesionType": "brim",
            "adhesionMargin": 10,
            "tandemMode": False,
            "batch_printing_90deg_rotation": True,
            "securityDistance0": 70,
            "securityDistance1": 10
        }

        return BatchArrangement(machineDescr_One)

    @staticmethod
    def createOneWithTandem():
        machineDescr_One = {
            "machineID": "bigrep_onev3",
            "isStudio": False,
            "machine_size": [1000, 1000],
            "machine_preferedStartPoint": [200, 200],
            "adhesionType": "brim",
            "adhesionMargin": 10,
            "tandemMode": True,
            "batch_printing_90deg_rotation": True,
            "securityDistance0": 70,
            "securityDistance1": 10
        }

        return BatchArrangement(machineDescr_One)

    @staticmethod
    def create90False():
        machineDescr_One = {
            "machineID": "bigrep_onev3",
            "isStudio": False,
            "machine_size": [1000, 1000],
            "machine_preferedStartPoint": [200, 200],
            "adhesionType": "brim",
            "adhesionMargin": 10,
            "tandemMode": False,
            "batch_printing_90deg_rotation": False,
            "securityDistance0": 70,
            "securityDistance1": 10
        }

        return BatchArrangement(machineDescr_One)

    @staticmethod
    def createStudio():
        machineDescr_Studio = {
            "machineID": "bigrep_studio",
            "isStudio": True,
            "machine_size": [1000, 500],
            "machine_preferedStartPoint": [200, 100],
            "adhesionType": "brim",
            "adhesionMargin": 10,
            "tandemMode": False,
            "batch_printing_90deg_rotation": True,
            "securityDistance0": 80,
            "securityDistance1": 10
        }

        return BatchArrangement(machineDescr_Studio)

    @staticmethod
    def _calcBBox(translations):
        minCoordinate = translations[0]
        maxCoordinate = translations[0]
        for item in translations[1:]:
            minCoordinate = numpy.minimum(minCoordinate, item)
            maxCoordinate = numpy.maximum(maxCoordinate, item)

        return (minCoordinate, maxCoordinate)

    def _checkTranslation(self, translations):
        minCoordinate, maxCoordinate = TestBatchArrangement._calcBBox(translations)

        self.assertGreater(minCoordinate[0], -self.batchArrangement.machineSize.max_x / 2.0)
        self.assertGreater(minCoordinate[2], -self.batchArrangement.machineSize.max_y / 2.0)

        self.assertLess(maxCoordinate[0], self.batchArrangement.machineSize.max_x / 2.0)
        self.assertLess(maxCoordinate[2], self.batchArrangement.machineSize.max_y / 2.0)

    def _checkTranslationTandem(self, translations):
        minCoordinate, maxCoordinate = TestBatchArrangement._calcBBox(translations)

        self.assertGreater(minCoordinate[0], -self.batchArrangement.machineSize.max_x)
        self.assertGreater(minCoordinate[2], -self.batchArrangement.machineSize.max_y / 2.0)

        self.assertLess(maxCoordinate[0], 0.0)
        self.assertLess(maxCoordinate[2], self.batchArrangement.machineSize.max_y / 2.0)

    def setUp(self):
        self.batchArrangement = TestBatchArrangement.createOne()

    def test__switchXYBox(self):
        box = (numpy.array([10, 20, 30]), numpy.array([40, 50, 80]))
        self.assertTrue(self.batchArrangement.shallRotateForPopulate(box))

    def test_length(self):
        bbox_list = [(numpy.array([0, 0, 0]), numpy.array([100, 100, 100]))] * 20
        translations, _ = self.batchArrangement.process(bbox_list)
        self.assertEqual(len(translations), len(bbox_list))
        self._checkTranslation(translations)

    def test_existFull(self):
        bbox_list = [(numpy.array([0, 0, 0]), numpy.array([100, 100, 100]))] * 150
        _, warning = self.batchArrangement._createSlotY(self.batchArrangement._createBBoxListEnhanced(bbox_list))
        self.assertTrue(warning)

    def test_existsLastRow(self):
        bbox_list = [(numpy.array([0, 0, 0]), numpy.array([100, 100, 100]))] * 30
        slotY, _ = self.batchArrangement._createSlotY(self.batchArrangement._createBBoxListEnhanced(bbox_list))
        self.assertGreater(len(slotY[-1].itemsX), 1)

    def test_mixed0(self):
        bbox_list = [(numpy.array([0, 0, 0]), numpy.array([100, 100, 100]))] * 30
        bbox_list.append((numpy.array([0, 0, 0]), numpy.array([200, 200, 200])))
        slotY, _ = self.batchArrangement._createSlotY(self.batchArrangement._createBBoxListEnhanced(bbox_list))
        self.assertGreater(len(slotY[-1].itemsX), 1)

    def test_mixed1(self):
        bbox_list = [(numpy.array([0, 0, 0]), numpy.array([100, 100, 100])),
                     (numpy.array([0, 0, 0]), numpy.array([200, 200, 200]))]
        translations, _ = self.batchArrangement.process(bbox_list)
        for item in translations:
            self.assertIsNotNone(item)

        self._checkTranslation(translations)

    @parameterized.expand([
        ["pro", False],
        ["studio", True],
    ])
    def test_multiplier(self, name, isStudio): # pylint: disable=unused-argument
        self.batchArrangement = TestBatchArrangement.createStudio() if isStudio else TestBatchArrangement.createOne()

        box = (numpy.array([0, 0, 0]), numpy.array([100, 100, 100]))

        multiplier = self.batchArrangement.calcMultiplier(box)
        # Just a rough plausibility test.
        self.assertGreater(multiplier, 10)
        self.assertLess(multiplier, 50)

        # Test if the plate is not too full.
        bbox_list = [box] * multiplier
        translations, warning = self.batchArrangement.process(bbox_list)
        self.assertFalse(warning)
        self._checkTranslation(translations)

        # Test if +1 leads to a too full plate.
        bbox_list = [box] * (multiplier + 1)
        _, warning = self.batchArrangement.process(bbox_list)
        self.assertTrue(warning)

    @parameterized.expand([
        ["pro", False],
        ["studio", True],
    ])
    def test_multiplierSequential(self, name, isStudio): # pylint: disable=unused-argument
        self.batchArrangement = TestBatchArrangement.createStudio() if isStudio else TestBatchArrangement.createOne()

        box = (numpy.array([0, 0, 0]), numpy.array([100, 100, 100]))
        multiplier = self.batchArrangement.calcMultiplierSequential(box)
        # Just a rough plausibility test.
        self.assertGreater(multiplier, 1)
        self.assertLess(multiplier, 9)

        # Test if the row is not too full.
        bbox_list = [box] * multiplier
        slotY, warning = self.batchArrangement._createSlotY(self.batchArrangement._createBBoxListEnhanced(bbox_list))
        self.assertFalse(warning)
        self.assertEqual(len(slotY), multiplier)

        # Test if +1 leads to a too a "overflow".
        bbox_list = [box] * (multiplier + 1)
        slotY, warning = self.batchArrangement._createSlotY(self.batchArrangement._createBBoxListEnhanced(bbox_list))
        self.assertFalse(warning)
        self.assertEqual(len(slotY), multiplier)

    def test_tandemMode(self):
        self.batchArrangement = TestBatchArrangement.createOneWithTandem()

        box = (numpy.array([0, 0, 0]), numpy.array([100, 100, 100]))
        multiplier = self.batchArrangement.calcMultiplier(box)
        # Just a rough plausibility test.
        self.assertGreater(multiplier, 1)
        self.assertLess(multiplier, 20)

        # Test if the row is not too full.
        bbox_list = [box] * multiplier
        translations, warning = self.batchArrangement.process(bbox_list)
        self.assertFalse(warning)

        self._checkTranslationTandem(translations)

    @parameterized.expand([
        ["pro", False],
        ["studio", True],
    ])
    def test_multiplierSequentialMinusOne(self, name, isStudio): # pylint: disable=unused-argument
        self.batchArrangement = TestBatchArrangement.createStudio() if isStudio else TestBatchArrangement.createOne()

        box = (numpy.array([0, 0, 0]), numpy.array([100, 100, 100]))
        multiplier = self.batchArrangement.calcMultiplier(box) - 1
        # Just a rough plausibility test.
        self.assertGreater(multiplier, 10)
        self.assertLess(multiplier, 60)

        # Test if the plate is not too full.
        bbox_list = [box] * multiplier
        translations, warning = self.batchArrangement.process(bbox_list)
        self.assertFalse(warning)
        self._checkTranslation(translations)

    @parameterized.expand([
        ["is90Rot_False", False],
        ["is90Rot_True", True],
    ])
    def test_multiplierSequentialRotate(self, name, is90Rot): # pylint: disable=unused-argument
        self.batchArrangement = TestBatchArrangement.createOne() if is90Rot else TestBatchArrangement.create90False()

        box = (numpy.array([0, 0, 0]), numpy.array([100, 100, 300]))
        shallRotate = self.batchArrangement.shallRotateForPopulate(box)
        self.assertEqual(shallRotate, is90Rot)


# This is a comment. Very important.
if __name__ == '__main__':
    unittest.main()
