# SPDX-License-Identifier: BSD-3-Clause
# Copyright Contributors to the OpenColorIO Project.

import unittest
import logging

logger = logging.getLogger(__name__)

try:
    import numpy as np
except ImportError:
    logger.warning(
        "NumPy could not be imported. "
        "Test case will lack significant coverage!"
    )
    np = None

import PyOpenColorIO as OCIO

from UnitTestUtils import STRING_TYPES


class GpuShaderDescTest(unittest.TestCase):

    def test_shader_creator_interface(self):
        desc = OCIO.GpuShaderDesc.CreateShaderDesc()
        desc.setLanguage(OCIO.GPU_LANGUAGE_GLSL_1_3)
        self.assertEqual(OCIO.GPU_LANGUAGE_GLSL_1_3, desc.getLanguage())
        desc.setFunctionName("foo123")
        self.assertEqual("foo123", desc.getFunctionName())
        desc.finalize()
        self.assertEqual("glsl_1.3 foo123 ocio outColor 0 6001c324468d497f99aa06d3014798d8",
                         desc.getCacheID())

    def test_uniform(self):
        # Test dynamic exposure and gamma
        config = OCIO.Config()
        tr = OCIO.ExposureContrastTransform(dynamicExposure=True, 
                                            dynamicGamma=True)
        proc = config.getProcessor(tr)
        desc = OCIO.GpuShaderDesc.CreateShaderDesc()
        gpu_proc = proc.getDefaultGPUProcessor()
        gpu_proc.extractGpuShaderInfo(desc)

        uniforms = desc.getUniforms()
        self.assertEqual(len(uniforms), 2)
        self.assertEqual(uniforms[0][0], "ocio_exposure_contrast_exposureVal")
        self.assertEqual(uniforms[0][1].type, OCIO.UNIFORM_DOUBLE)
        self.assertEqual(uniforms[0][1].getDouble(), 0.0)
        self.assertEqual(uniforms[1][0], "ocio_exposure_contrast_gammaVal")
        self.assertEqual(uniforms[1][1].type, OCIO.UNIFORM_DOUBLE)
        self.assertEqual(uniforms[1][1].getDouble(), 1.0)

        # Can dynamically modify uniforms
        dyn_exposure = desc.getDynamicProperty(OCIO.DYNAMIC_PROPERTY_EXPOSURE)
        dyn_exposure.setDouble(2.0)
        self.assertEqual(uniforms[0][1].getDouble(), 2.0)
        dyn_gamma = desc.getDynamicProperty(OCIO.DYNAMIC_PROPERTY_GAMMA)
        dyn_gamma.setDouble(0.5)
        self.assertEqual(uniforms[1][1].getDouble(), 0.5)

        # Uniforms are present in shader src
        text = desc.getShaderText()
        self.assertEqual(text.count("uniform float"), 2)

        # Iterates uniform name and data
        for name, uniform_data in uniforms:
            self.assertIsInstance(name, STRING_TYPES)
            self.assertIsInstance(uniform_data, OCIO.GpuShaderDesc.UniformData)

    def test_vector_uniform(self):
        if not np:
            logger.warning("NumPy not found. Skipping test!")
            return

        # Test dynamic GradingRGBCurve
        a_curve = OCIO.GradingBSplineCurve([1.0, 2.0, 3.0, 4.0])
        rgb_curve = OCIO.GradingRGBCurve(a_curve, a_curve, a_curve, a_curve)
        tr = OCIO.GradingRGBCurveTransform(values=rgb_curve,
                                           dynamic=True)
        config = OCIO.Config()
        proc = config.getProcessor(tr)
        desc = OCIO.GpuShaderDesc.CreateShaderDesc()
        gpu_proc = proc.getDefaultGPUProcessor()
        gpu_proc.extractGpuShaderInfo(desc)

        uniforms = desc.getUniforms()
        self.assertEqual(len(uniforms), 5)

        self.assertEqual(uniforms[0][0], "ocio_grading_rgbcurve_knotsOffsets")
        self.assertEqual(uniforms[0][1].type, OCIO.UNIFORM_VECTOR_INT)
        vector_int = uniforms[0][1].getVectorInt()
        self.assertTrue(isinstance(vector_int, np.ndarray))
        self.assertEqual(vector_int.dtype, np.intc)
        self.assertTrue(np.array_equal(
            vector_int, 
            np.array([0, 2, 2, 2, 4, 2, 6, 2], 
            dtype=np.intc))
        )

        self.assertEqual(uniforms[1][0], "ocio_grading_rgbcurve_knots")
        self.assertEqual(uniforms[1][1].type, OCIO.UNIFORM_VECTOR_FLOAT)
        vector_float = uniforms[1][1].getVectorFloat()
        self.assertTrue(isinstance(vector_float, np.ndarray))
        self.assertEqual(vector_float.dtype, np.float32)
        self.assertTrue(np.array_equal(
            vector_float, 
            np.array([1.0, 3.0, 1.0, 3.0, 1.0, 3.0, 1.0, 3.0], 
            dtype=np.float32))
        )

        # Can dynamically modify uniforms
        b_curve = OCIO.GradingBSplineCurve([5.0, 6.0, 7.0, 8.0])
        dyn_rgb_curve = desc.getDynamicProperty(
            OCIO.DYNAMIC_PROPERTY_GRADING_RGBCURVE
        )
        dyn_rgb_curve.setGradingRGBCurve(
            OCIO.GradingRGBCurve(b_curve, b_curve, b_curve, b_curve)
        )

        self.assertTrue(np.array_equal(
            uniforms[1][1].getVectorFloat(), 
            np.array([5.0, 7.0, 5.0, 7.0, 5.0, 7.0, 5.0, 7.0], 
            dtype=np.float32))
        )

    def test_texture(self):
        # Test addTexture() & getTextures().
        if not np:
            logger.warning("NumPy not found. Skipping test!")
            return

        desc = OCIO.GpuShaderDesc.CreateShaderDesc()
        buf = np.array([0,0.1,0.2,0.3,0.4,0.5]).astype(np.float32)
        desc.addTexture('tex', 'sampler', 2, 3,
                        OCIO.GpuShaderDesc.TEXTURE_RED_CHANNEL,
                        OCIO.INTERP_DEFAULT, buf)
        desc.addTexture(textureName='tex2', samplerName='sampler2', width=3, height=2,
                        channel=OCIO.GpuShaderDesc.TEXTURE_RED_CHANNEL,
                        interpolation=OCIO.INTERP_DEFAULT, values=buf)
        textures = desc.getTextures()
        self.assertEqual(len(textures), 2)
        t1 = next(textures)
        self.assertEqual(t1.textureName, 'tex')
        self.assertEqual(t1.samplerName, 'sampler')
        self.assertEqual(t1.width, 2)
        self.assertEqual(t1.height, 3)
        self.assertEqual(t1.channel, OCIO.GpuShaderDesc.TEXTURE_RED_CHANNEL)
        self.assertEqual(t1.interpolation, OCIO.INTERP_DEFAULT)
        v1 = t1.getValues()
        self.assertEqual(len(v1), 6)
        self.assertEqual(v1[0], np.float32(0))
        self.assertEqual(v1[1], np.float32(0.1))
        self.assertEqual(v1[2], np.float32(0.2))
        self.assertEqual(v1[3], np.float32(0.3))
        self.assertEqual(v1[4], np.float32(0.4))
        self.assertEqual(v1[5], np.float32(0.5))

        t2 = next(textures)
        self.assertEqual(t2.textureName, 'tex2')
        self.assertEqual(t2.samplerName, 'sampler2')
        self.assertEqual(t2.width, 3)
        self.assertEqual(t2.height, 2)
        self.assertEqual(t2.channel, OCIO.GpuShaderDesc.TEXTURE_RED_CHANNEL)
        self.assertEqual(t2.interpolation, OCIO.INTERP_DEFAULT)
        v2 = t2.getValues()
        self.assertEqual(len(v2), 6)
        self.assertEqual(v2[0], np.float32(0))
        self.assertEqual(v2[1], np.float32(0.1))
        self.assertEqual(v2[2], np.float32(0.2))
        self.assertEqual(v2[3], np.float32(0.3))
        self.assertEqual(v2[4], np.float32(0.4))
        self.assertEqual(v2[5], np.float32(0.5))

    def test_texture_3d(self):
        # Test add3DTexture() & get3DTextures().
        if not np:
            logger.warning("NumPy not found. Skipping test!")
            return

        desc = OCIO.GpuShaderDesc.CreateShaderDesc()
        buf = np.linspace(0, 1, num=8*3).astype(np.float32)
        bufTest1 = buf[3]
        desc.add3DTexture('tex', 'sampler', 2,
                          OCIO.INTERP_DEFAULT, buf)
        buf = np.linspace(0, 1, num=27*3).astype(np.float32)
        bufTest2 = buf[42]
        desc.add3DTexture('tex2', 'sampler2', 3,
                          OCIO.INTERP_DEFAULT, buf)

        textures = desc.get3DTextures()
        self.assertEqual(len(textures), 2)
        t1 = next(textures)
        self.assertEqual(t1.textureName, 'tex')
        self.assertEqual(t1.samplerName, 'sampler')
        self.assertEqual(t1.edgeLen, 2)
        self.assertEqual(t1.interpolation, OCIO.INTERP_DEFAULT)
        v1 = t1.getValues()
        self.assertEqual(len(v1), 3*8)
        self.assertEqual(v1[3], bufTest1)
        t2 = next(textures)
        self.assertEqual(t2.textureName, 'tex2')
        self.assertEqual(t2.samplerName, 'sampler2')
        self.assertEqual(t2.edgeLen, 3)
        self.assertEqual(t2.interpolation, OCIO.INTERP_DEFAULT)
        v2 = t2.getValues()
        self.assertEqual(len(v2), 3*27)
        self.assertEqual(v2[42], bufTest2)
