import unittest

import numpy as np
from numpy.testing import assert_array_equal

from clodius.tiles.mrmatrix import tileset_info, tiles


class MockHdf5(dict):
    # By wrapping a dict in our own class, we can add arbitrary attributes.
    pass


class TilesetInfoTest(unittest.TestCase):
    def setUp(self):
        tileset_stub = {"resolutions": {"1": {"values": np.array([[1, 2], [3, 4]])}}}

        self.tileset = MockHdf5(tileset_stub)
        self.tileset.attrs = {}

        self.tileset_min = MockHdf5(tileset_stub)
        self.tileset_min.attrs = {"min-pos": (1, 1)}

        self.tileset_max = MockHdf5(tileset_stub)
        self.tileset_max.attrs = {"max-pos": (9, 9)}

        self.info = {
            "bins_per_dimension": 256,
            "max_pos": (2, 2),
            "min_pos": [0, 0],
            "mirror_tiles": "false",
            "resolutions": [1],
        }

    def test_default(self):
        self.assertEqual(tileset_info(self.tileset), self.info)
        self.assertEqual(tileset_info(self.tileset, bounds="???"), self.info)

    def test_with_min(self):
        self.info["min_pos"] = (1, 1)
        self.assertEqual(tileset_info(self.tileset_min), self.info)
        self.assertEqual(tileset_info(self.tileset_min, bounds="???"), self.info)

    def test_with_max(self):
        self.info["max_pos"] = (9, 9)
        self.assertEqual(tileset_info(self.tileset_max), self.info)
        self.assertEqual(tileset_info(self.tileset_max, bounds="???"), self.info)


class TilesTest(unittest.TestCase):
    def test_zoom_out_of_bounds(self):
        def should_fail():
            tileset_stub = MockHdf5(
                {"resolutions": {"1": {"values": np.array([[1, 2], [3, 4]])}}}
            )
            tileset_stub.attrs = {}
            tiles(tileset_stub, 2, 0, 0)

        self.assertRaisesRegex(ValueError, r"Zoom level out of bounds", should_fail)

    def test_padding(self):
        tileset = MockHdf5(
            {
                "resolutions": {
                    "1": {
                        "values": np.array([[1.0, 2], [3, 4]])
                        # It's important that there is a float value:
                        # If there isn't, np.nan will be converted
                        # to a large negative integer.
                    }
                }
            }
        )
        tileset.attrs = {}
        zoomed = tiles(tileset, 0, 0, 0)
        self.assertEqual(zoomed.shape, (256, 256))
        assert_array_equal(zoomed[0:2, 0:2], [[1, 2], [3, 4]])
        assert_array_equal(zoomed[2:256, 0], [np.nan for x in range(254)])

    def test_bins(self):
        tileset = MockHdf5(
            {
                "resolutions": {
                    "1": {
                        "values": np.array(
                            [[float(x) for x in range(500)] for y in range(500)]
                        )
                    }
                }
            }
        )
        tileset.attrs = {}

        zoomed_0 = tiles(tileset, 0, 0, 0)
        self.assertEqual(zoomed_0.shape, (256, 256))
        self.assertEqual(zoomed_0[0, 0], 0)

        zoomed_1 = tiles(tileset, 0, 1, 1)
        self.assertEqual(zoomed_1.shape, (256, 256))
        self.assertEqual(zoomed_1[0, 0], 256)
        self.assertEqual(zoomed_1[1, 0], 256)  # Constant dimension
        self.assertEqual(zoomed_1[0, 1], 257)  # Changing dimension
        self.assertEqual(zoomed_1[0, 256 - 13], 499)
        assert_array_equal(zoomed_1[0:1, (256 - 12) : (256 - 12 + 1)], [[np.nan]])
        # Plain assertEqual gave: nan != nan

    def test_zoom(self):
        tileset = MockHdf5(
            {
                "resolutions": {
                    "1": {"values": np.array([[1, 2], [3, 4]])},
                    "5": {"values": np.array([[3, 4], [5, 6]])},
                    "11": {"values": np.array([[5, 6], [7, 8]])},
                }
            }
        )
        tileset.attrs = {}

        zoomed_0 = tiles(tileset, 0, 0, 0)
        assert_array_equal(zoomed_0[0:2, 0:2], [[5, 6], [7, 8]])

        zoomed_1 = tiles(tileset, 1, 0, 0)
        assert_array_equal(zoomed_1[0:2, 0:2], [[3, 4], [5, 6]])

        zoomed_2 = tiles(tileset, 2, 0, 0)
        assert_array_equal(zoomed_2[0:2, 0:2], [[1, 2], [3, 4]])
