"""
pyplanetarium package integration tests
"""

import unittest

from pyplanetarium import SpotShape, SpotId, Transform, Canvas, ImageFormat, Window


class CanvasCase(unittest.TestCase):
    """
    pyplanetarium.Canvas class unit tests
    """

    def test_init(self) -> None:
        """
        Canvas object instantiation test
        """

        width = 1024
        height = 768

        canvas = Canvas.new(width, height)
        self.assertIsInstance(canvas, Canvas)
        self.assertEqual(canvas.dimensions(), (width, height))

        self.assertEqual(str(canvas), "Canvas(1024, 768)")
        self.assertEqual(repr(canvas), "Canvas(1024, 768)")

        canvas.set_background(1000)
        canvas.clear()

    def test_init_err(self) -> None:
        """
        Canvas instantiation errors test
        """

        with self.assertRaises(TypeError):
            Canvas.new()  # type: ignore

        with self.assertRaises(TypeError):
            Canvas.new(100)  # type: ignore

        with self.assertRaises(TypeError):
            Canvas.new((100, 200))  # type: ignore

    def test_enum_repr(self) -> None:
        """
        Enum variants string representation test
        """

        self.assertEqual(str(ImageFormat.PngGamma8Bpp), "PngGamma8Bpp")
        self.assertEqual(repr(ImageFormat.PngGamma8Bpp), "PngGamma8Bpp")
        self.assertEqual(repr(ImageFormat.PngLinear16Bpp), "PngLinear16Bpp")
        self.assertEqual(repr(ImageFormat.RawGamma8Bpp), "RawGamma8Bpp")
        self.assertEqual(repr(ImageFormat.RawLinear10BppLE), "RawLinear10BppLE")
        self.assertEqual(repr(ImageFormat.RawLinear12BppLE), "RawLinear12BppLE")

    def test_enum_hash(self) -> None:
        """
        Enum variants as dict keys test
        """

        self.assertEqual(hash(ImageFormat.PngGamma8Bpp), 3)

        formats = {}
        formats[ImageFormat.PngGamma8Bpp] = "PNG8"
        formats[ImageFormat.PngLinear16Bpp] = "PNG16"

        self.assertEqual(formats[ImageFormat.PngGamma8Bpp], "PNG8")
        self.assertEqual(formats[ImageFormat.PngLinear16Bpp], "PNG16")

    def test_draw_spots(self) -> None:
        """
        Light spots image drawing test
        """

        shape1 = SpotShape().scale(3.5)
        shape2 = SpotShape(5.5).stretch(1.5, 1.0).rotate(45)

        width = 1024
        height = 768

        canvas = Canvas.new(width, height)
        self.assertEqual(canvas.dimensions(), (width, height))

        spot1 = canvas.add_spot((100.5, 200.7), shape1, 0.8)
        self.assertIsInstance(spot1, SpotId)
        self.assertEqual(str(spot1), "SpotId(0)")

        spot2 = canvas.add_spot((400.5, 600.7), shape2, 0.6)
        self.assertIsInstance(spot2, SpotId)
        self.assertEqual(repr(spot2), "SpotId(1)")

        self.assertNotEqual(spot1, spot2)

        canvas.set_background(int(0.1 * Canvas.PIXEL_MAX))

        canvas.draw()

    def test_move_spots(self) -> None:
        """
        Light spots moving test
        """

        shape1 = SpotShape().scale(3.5)
        shape2 = SpotShape(5.5).stretch(1.0, 1.5).rotate(30)

        width = 1024
        height = 768

        canvas = Canvas.new(width, height)
        spot1 = canvas.add_spot((100.5, 200.7), shape1, 0.8)
        spot2 = canvas.add_spot((400.5, 600.7), shape2, 0.6)

        pos1 = canvas.spot_position(spot1)
        pos2 = canvas.spot_position(spot2)
        assert pos1 is not None
        assert pos2 is not None
        self.assertAlmostEqual(pos1[0], 100.5, 4)
        self.assertAlmostEqual(pos2[1], 600.7, 4)

        int1 = canvas.spot_intensity(spot1)
        int2 = canvas.spot_intensity(spot2)
        assert int1 is not None
        assert int2 is not None
        self.assertAlmostEqual(int1, 0.8, 4)
        self.assertAlmostEqual(int2, 0.6, 4)

        canvas.set_spot_offset(spot2, (5.5, -7.0))
        canvas.set_spot_illumination(spot2, 0.5)

        canvas.set_background(int(0.2 * Canvas.PIXEL_MAX))
        canvas.set_brightness(1.3)

        pos2 = canvas.spot_position(spot2)
        assert pos2 is not None
        self.assertAlmostEqual(pos2[0], 400.5 + 5.5, 4)
        self.assertAlmostEqual(pos2[1], 600.7 - 7.0, 4)

        int1 = canvas.spot_intensity(spot1)
        int2 = canvas.spot_intensity(spot2)
        assert int1 is not None
        assert int2 is not None
        self.assertAlmostEqual(int1, 0.8 * 1.3, 4)
        self.assertAlmostEqual(int2, 0.6 * 0.5 * 1.3, 4)

        canvas.draw()

    def test_view_transform(self) -> None:
        """
        Setting the canvas view transform test
        """

        shape1 = SpotShape([[1, -0.5], [0.5, 1.5]])
        shape2 = SpotShape(5.5).stretch(1.0, 1.5).rotate(30)

        width = 1024
        height = 768

        canvas = Canvas.new(width, height)
        spot1 = canvas.add_spot((100.5, 200.25), shape1, 0.8)
        spot2 = canvas.add_spot((400.5, 600.75), shape2, 0.6)

        pos1 = canvas.spot_position(spot1)
        pos2 = canvas.spot_position(spot2)
        assert pos1 is not None
        assert pos2 is not None
        self.assertAlmostEqual(pos1[0], 100.5, 4)
        self.assertAlmostEqual(pos1[1], 200.25, 4)
        self.assertAlmostEqual(pos2[0], 400.5, 4)
        self.assertAlmostEqual(pos2[1], 600.75, 4)

        canvas.set_view_transform(Transform())

        canvas.set_spot_offset(spot2, (5.5, -7.0))
        pos2 = canvas.spot_position(spot2)
        assert pos2 is not None
        self.assertAlmostEqual(pos2[0], 400.5 + 5.5, 4)
        self.assertAlmostEqual(pos2[1], 600.75 - 7.0, 4)

        canvas.set_view_transform(Transform((-10, 25)))

        pos1 = canvas.spot_position(spot1)
        pos2 = canvas.spot_position(spot2)
        assert pos1 is not None
        assert pos2 is not None
        self.assertAlmostEqual(pos1[0], 100.5 - 10, 4)
        self.assertAlmostEqual(pos1[1], 200.25 + 25, 4)
        self.assertAlmostEqual(pos2[0], 400.5 + 5.5 - 10, 4)
        self.assertAlmostEqual(pos2[1], 600.75 - 7.0 + 25, 4)

        xfrm = Transform((-100, 200)).rotate(45).compose(Transform([[-1, 0], [0, 1]]))

        canvas.set_view_transform(xfrm)
        pos1 = canvas.spot_position(spot1)
        pos2 = canvas.spot_position(spot2)
        assert pos1 is not None
        assert pos2 is not None
        self.assertAlmostEqual(pos1[0], 282.6659, 4)
        self.assertAlmostEqual(pos1[1], 283.373, 4)
        self.assertAlmostEqual(pos2[0], 344.8913, 4)
        self.assertAlmostEqual(pos2[1], 777.6407, 4)

        canvas.draw()

    def test_spot_hash(self) -> None:
        """
        Opaque spot identifiers as dict keys test
        """

        canvas = Canvas.new(10, 10)
        spot1 = canvas.add_spot((1.0, 1.0), SpotShape(), 0.8)
        spot2 = canvas.add_spot((2.0, 2.0), SpotShape(2.0), 0.6)

        self.assertEqual(hash(spot1), 0)
        self.assertEqual(hash(spot2), 1)

        spots = {}
        spots[spot1] = "Spot1"
        spots[spot2] = "Spot2"

        self.assertEqual(spots[spot1], "Spot1")
        self.assertEqual(spots[spot2], "Spot2")

    def test_export_images(self) -> None:
        """
        Canvas image export test
        """

        shape1 = SpotShape().scale(3.5)
        shape2 = SpotShape().scale(5.5)

        width = 256
        height = 256

        canvas = Canvas.new(width, height)

        spot1 = canvas.add_spot((180.5, 150.7), shape1, 0.8)
        spot2 = canvas.add_spot((100.5, 110.7), shape2, 0.6)

        self.assertNotEqual(spot1, spot2)

        canvas.set_background(5000)

        canvas.draw()

        raw8_bytes = canvas.export_image(ImageFormat.RawGamma8Bpp)
        self.assertIsInstance(raw8_bytes, bytes)
        self.assertEqual(len(raw8_bytes), 65536)
        self.assertEqual(raw8_bytes[0], 78)
        self.assertEqual(raw8_bytes[65535], 78)

        raw10_bytes = canvas.export_image(ImageFormat.RawLinear10BppLE)
        self.assertIsInstance(raw10_bytes, bytes)
        self.assertEqual(len(raw10_bytes), 2 * 65536)
        self.assertEqual(raw10_bytes[0], 78)
        self.assertEqual(raw10_bytes[1], 0)

        raw12_bytes = canvas.export_image(ImageFormat.RawLinear12BppLE)
        self.assertIsInstance(raw12_bytes, bytes)
        self.assertEqual(len(raw12_bytes), 2 * 65536)
        self.assertEqual(raw12_bytes[0], 56)
        self.assertEqual(raw12_bytes[1], 1)

        png8_bytes = canvas.export_image(ImageFormat.PngGamma8Bpp)
        self.assertIsInstance(png8_bytes, bytes)
        self.assertEqual(len(png8_bytes), 949)

        png16_bytes = canvas.export_image(ImageFormat.PngLinear16Bpp)
        self.assertIsInstance(png16_bytes, bytes)
        self.assertEqual(len(png16_bytes), 1816)

        # with open("image8.raw", "wb") as f:
        #     f.write(raw8_bytes)

        # with open("image10.raw", "wb") as f:
        #     f.write(raw10_bytes)

        # with open("image12.raw", "wb") as f:
        #     f.write(raw12_bytes)

        # with open("image8.png", "wb") as f:
        #     f.write(png8_bytes)

        # with open("image16.png", "wb") as f:
        #     f.write(png16_bytes)

    def test_export_window_images(self) -> None:
        """
        Windowed canvas image export test
        """

        shape1 = SpotShape().scale(3.5)
        shape2 = SpotShape().scale(5.5)

        width = 256
        height = 256

        canvas = Canvas.new(width, height)

        spot1 = canvas.add_spot((180.5, 150.7), shape1, 0.8)
        spot2 = canvas.add_spot((100.5, 110.7), shape2, 0.6)

        self.assertNotEqual(spot1, spot2)

        canvas.set_background(5000)

        canvas.draw()

        wnd1 = Window.new(32, 16).at(170, 140)
        wnd2 = Window.new(32, 16).at(90, 100)

        raw8_bytes = canvas.export_window_image(wnd1, ImageFormat.RawGamma8Bpp)
        self.assertIsInstance(raw8_bytes, bytes)
        self.assertEqual(len(raw8_bytes), 32 * 16)

        raw10_bytes = canvas.export_window_image(wnd1, ImageFormat.RawLinear10BppLE)
        self.assertIsInstance(raw10_bytes, bytes)
        self.assertEqual(len(raw10_bytes), 2 * 32 * 16)

        raw12_bytes = canvas.export_window_image(wnd2, ImageFormat.RawLinear12BppLE)
        self.assertIsInstance(raw12_bytes, bytes)
        self.assertEqual(len(raw12_bytes), 2 * 32 * 16)

        png8_bytes = canvas.export_window_image(wnd1, ImageFormat.PngGamma8Bpp)
        self.assertIsInstance(png8_bytes, bytes)
        self.assertEqual(len(png8_bytes), 250)

        png16_bytes = canvas.export_window_image(wnd2, ImageFormat.PngLinear16Bpp)
        self.assertIsInstance(png16_bytes, bytes)
        self.assertEqual(len(png16_bytes), 664)

    def test_export_subsampled_images(self) -> None:
        """
        Subsampled canvas image export test
        """

        shape1 = SpotShape().scale(3.5)
        shape2 = SpotShape().scale(5.5)

        width = 256
        height = 256

        canvas = Canvas.new(width, height)

        spot1 = canvas.add_spot((180.5, 150.7), shape1, 0.8)
        spot2 = canvas.add_spot((100.5, 110.7), shape2, 0.6)

        self.assertNotEqual(spot1, spot2)

        canvas.set_background(5000)

        canvas.draw()

        raw8_bytes = canvas.export_subsampled_image((2, 2), ImageFormat.RawGamma8Bpp)
        self.assertIsInstance(raw8_bytes, bytes)
        self.assertEqual(len(raw8_bytes), 128 * 128)

        raw10_bytes = canvas.export_subsampled_image(
            (2, 2), ImageFormat.RawLinear10BppLE
        )
        self.assertIsInstance(raw10_bytes, bytes)
        self.assertEqual(len(raw10_bytes), 2 * 128 * 128)

        raw12_bytes = canvas.export_subsampled_image(
            (4, 4), ImageFormat.RawLinear12BppLE
        )
        self.assertIsInstance(raw12_bytes, bytes)
        self.assertEqual(len(raw12_bytes), 2 * 64 * 64)

        png8_bytes = canvas.export_subsampled_image((2, 2), ImageFormat.PngGamma8Bpp)
        self.assertIsInstance(png8_bytes, bytes)
        self.assertEqual(len(png8_bytes), 366)

        png16_bytes = canvas.export_subsampled_image((4, 4), ImageFormat.PngLinear16Bpp)
        self.assertIsInstance(png16_bytes, bytes)
        self.assertEqual(len(png16_bytes), 284)


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