"""Define the unit tests for the :mod:`colour.io.ctl` module."""

from __future__ import annotations

import os
import shutil
import tempfile
import textwrap

import numpy as np

from colour.constants import TOLERANCE_ABSOLUTE_TESTS
from colour.io import (
    ctl_render,
    process_image_ctl,
    read_image,
    template_ctl_transform_float,
    template_ctl_transform_float3,
)
from colour.utilities import full, is_ctlrender_installed

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
    "ROOT_RESOURCES",
    "TestCtlRender",
    "TestProcessImageCtl",
    "TestTemplateCtlTransformFloat",
    "TestTemplateCtlTransformFloat3",
]

ROOT_RESOURCES: str = os.path.join(os.path.dirname(__file__), "resources")

# TODO: Reinstate coverage when "ctlrender" is tivially available
# cross-platform.


class TestCtlRender:
    """Define :func:`colour.io.ctl.ctl_render` definition unit tests methods."""

    def setup_method(self) -> None:
        """Initialise the common tests attributes."""

        self._temporary_directory = tempfile.mkdtemp()

    def teardown_method(self) -> None:
        """After tests actions."""

        shutil.rmtree(self._temporary_directory)

    def test_ctl_render(self) -> None:  # pragma: no cover
        """Test :func:`colour.io.ctl.ctl_render` definition."""

        if not is_ctlrender_installed():
            return

        ctl_adjust_gain_float = template_ctl_transform_float(
            "rIn * gain[0]",
            "gIn * gain[1]",
            "bIn * gain[2]",
            description="Adjust Gain",
            parameters=["input float gain[3] = {1.0, 1.0, 1.0}"],
        )

        ctl_adjust_exposure_float = template_ctl_transform_float(
            "rIn * pow(2, exposure)",
            "gIn * pow(2, exposure)",
            "bIn * pow(2, exposure)",
            description="Adjust Exposure",
            parameters=["input float exposure = 0.0"],
        )

        path_input = os.path.join(ROOT_RESOURCES, "CMS_Test_Pattern.exr")
        path_output = os.path.join(
            self._temporary_directory, "CMS_Test_Pattern_Float.exr"
        )

        ctl_render(
            path_input,
            path_output,
            {
                ctl_adjust_gain_float: ["-param3 gain 0.5 1.0 2.0"],
                ctl_adjust_exposure_float: ["-param1 exposure 1.0"],
            },
            "-verbose",
            "-force",
        )

        np.testing.assert_allclose(
            read_image(path_output)[..., 0:3],
            read_image(path_input) * [1, 2, 4],
            atol=TOLERANCE_ABSOLUTE_TESTS,
        )

        ctl_render(
            path_input,
            path_output,
            {
                os.path.join(ROOT_RESOURCES, "Adjust_Exposure_Float3.ctl"): [
                    "-param1 exposure 1.0"
                ],
            },
            "-verbose",
            "-force",
            env=dict(os.environ, CTL_MODULE_PATH=ROOT_RESOURCES),
        )

        np.testing.assert_allclose(
            read_image(path_output)[..., 0:3],
            read_image(path_input) * 2,
            atol=TOLERANCE_ABSOLUTE_TESTS,
        )


class TestProcessImageCtl:
    """
    Define :func:`colour.io.ctl.process_image_ctl` definition unit tests
    methods.
    """

    def test_process_image_ctl(self) -> None:  # pragma: no cover
        """Test :func:`colour.io.ctl.process_image_ctl` definition."""

        if not is_ctlrender_installed():
            return

        ctl_adjust_gain_float = template_ctl_transform_float(
            "rIn * gain[0]",
            "gIn * gain[1]",
            "bIn * gain[2]",
            description="Adjust Gain",
            parameters=["input float gain[3] = {1.0, 1.0, 1.0}"],
        )

        np.testing.assert_allclose(
            process_image_ctl(
                0.18,
                {
                    ctl_adjust_gain_float: ["-param3 gain 0.5 1.0 2.0"],
                },
                "-verbose",
                "-force",
            ),
            0.18 / 2,
            atol=0.0001,
        )

        np.testing.assert_allclose(
            process_image_ctl(
                np.array([0.18, 0.18, 0.18]),
                {
                    ctl_adjust_gain_float: ["-param3 gain 0.5 1.0 2.0"],
                },
            ),
            np.array([0.18 / 2, 0.18, 0.18 * 2]),
            atol=0.0001,
        )

        np.testing.assert_allclose(
            process_image_ctl(
                np.array([[0.18, 0.18, 0.18]]),
                {
                    ctl_adjust_gain_float: ["-param3 gain 0.5 1.0 2.0"],
                },
            ),
            np.array([[0.18 / 2, 0.18, 0.18 * 2]]),
            atol=0.0001,
        )

        np.testing.assert_allclose(
            process_image_ctl(
                np.array([[[0.18, 0.18, 0.18]]]),
                {
                    ctl_adjust_gain_float: ["-param3 gain 0.5 1.0 2.0"],
                },
            ),
            np.array([[[0.18 / 2, 0.18, 0.18 * 2]]]),
            atol=0.0001,
        )

        np.testing.assert_allclose(
            process_image_ctl(
                full([4, 2, 3], 0.18),
                {
                    ctl_adjust_gain_float: ["-param3 gain 0.5 1.0 2.0"],
                },
            ),
            full([4, 2, 3], 0.18) * [0.5, 1.0, 2.0],
            atol=0.0001,
        )


class TestTemplateCtlTransformFloat:
    """
    Define :func:`colour.io.ctl.template_ctl_transform_float` definition unit
    tests methods.
    """

    def test_template_ctl_transform_float(self) -> None:
        """Test :func:`colour.io.ctl.template_ctl_transform_float` definition."""

        ctl_foo_bar_float = template_ctl_transform_float(
            "rIn + foo[0]",
            "gIn + foo[1]",
            "bIn + foo[2]",
            description="Foo & Bar",
            imports=['import "Foo.ctl";', 'import "Bar.ctl";'],
            parameters=[
                "input float foo[3] = {1.0, 1.0, 1.0}",
                "input float bar = 1.0",
            ],
            header="// Custom Header",
        )

        target = textwrap.dedent(
            """
            // Foo & Bar

            import "Foo.ctl";
            import "Bar.ctl";

            // Custom Header
            void main
            (
                output varying float rOut,
                output varying float gOut,
                output varying float bOut,
                output varying float aOut,
                input varying float rIn,
                input varying float gIn,
                input varying float bIn,
                input varying float aIn = 1.0,
                input float foo[3] = {1.0, 1.0, 1.0},
                input float bar = 1.0
            )
            {
                rOut = rIn + foo[0];
                gOut = gIn + foo[1];
                bOut = bIn + foo[2];
                aOut = aIn;
            }"""[1:]
        )
        assert ctl_foo_bar_float == target

        ctl_no_description_float = template_ctl_transform_float("rIn * 2.0")

        target_no_description = (
            '// "float" Processing Function\n\n'
            "void main\n"
            "(\n"
            "    output varying float rOut,\n"
            "    output varying float gOut,\n"
            "    output varying float bOut,\n"
            "    output varying float aOut,\n"
            "    input varying float rIn,\n"
            "    input varying float gIn,\n"
            "    input varying float bIn,\n"
            "    input varying float aIn = 1.0)\n"
            "{\n"
            "    rOut = rIn * 2.0;\n"
            "    gOut = rIn * 2.0;\n"
            "    bOut = rIn * 2.0;\n"
            "    aOut = aIn;\n"
            "}"
        )
        assert ctl_no_description_float == target_no_description


class TestTemplateCtlTransformFloat3:
    """
    Define :func:`colour.io.ctl.template_ctl_transform_float3` definition unit
    tests methods.
    """

    def test_template_ctl_transform_float3(self) -> None:
        """Test :func:`colour.io.ctl.template_ctl_transform_float3` definition."""

        ctl_foo_bar_float3 = template_ctl_transform_float3(
            "baz(rgbIn, foo, bar)",
            description="Foo, Bar & Baz",
            imports=[
                '// import "Foo.ctl";',
                '// import "Bar.ctl";',
                '// import "Baz.ctl";',
            ],
            parameters=[
                "input float foo[3] = {1.0, 1.0, 1.0}",
                "input float bar = 1.0",
            ],
            header=textwrap.dedent(
                """
                float[3] baz(float rgbIn[3], float foo[3], float qux)
                {
                    float rgbOut[3];

                    rgbOut[0] = rgbIn[0] * foo[0]* qux;
                    rgbOut[1] = rgbIn[1] * foo[1]* qux;
                    rgbOut[2] = rgbIn[2] * foo[2]* qux;

                    return rgbOut;
                }
"""[1:]
            ),
        )
        # fmt: off
        target = textwrap.dedent(
            """
            // Foo, Bar & Baz

            // import "Foo.ctl";
            // import "Bar.ctl";
            // import "Baz.ctl";

            float[3] baz(float rgbIn[3], float foo[3], float qux)
            {
                float rgbOut[3];

                rgbOut[0] = rgbIn[0] * foo[0]* qux;
                rgbOut[1] = rgbIn[1] * foo[1]* qux;
                rgbOut[2] = rgbIn[2] * foo[2]* qux;

                return rgbOut;
            }

            void main
            (
                output varying float rOut,
                output varying float gOut,
                output varying float bOut,
                output varying float aOut,
                input varying float rIn,
                input varying float gIn,
                input varying float bIn,
                input varying float aIn = 1.0,
                input float foo[3] = {1.0, 1.0, 1.0},
                input float bar = 1.0
            )
            {
                float rgbIn[3] = {rIn, gIn, bIn};

                float rgbOut[3] = baz(rgbIn, foo, bar);

                rOut = rgbOut[0];
                gOut = rgbOut[1];
                bOut = rgbOut[2];
                aOut = aIn;
            }"""[
                1:
            ]
        )
        assert ctl_foo_bar_float3 == target

        ctl_no_description_float3 = template_ctl_transform_float3("rgbIn * 2.0")

        target_no_description = (
            '// "float3" Processing Function\n\n'
            "void main\n"
            "(\n"
            "    output varying float rOut,\n"
            "    output varying float gOut,\n"
            "    output varying float bOut,\n"
            "    output varying float aOut,\n"
            "    input varying float rIn,\n"
            "    input varying float gIn,\n"
            "    input varying float bIn,\n"
            "    input varying float aIn = 1.0)\n"
            "{\n"
            "    float rgbIn[3] = {rIn, gIn, bIn};\n\n"
            "    float rgbOut[3] = rgbIn * 2.0;\n\n"
            "    rOut = rgbOut[0];\n"
            "    gOut = rgbOut[1];\n"
            "    bOut = rgbOut[2];\n"
            "    aOut = aIn;\n"
            "}"
        )
        assert ctl_no_description_float3 == target_no_description
