# Copyright 2018-2019 The glTF-Blender-IO authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import bpy
from .gltf2_blender_texture import BlenderTextureInfo
from ..com.gltf2_blender_conversion import texture_transform_gltf_to_blender


class BlenderKHR_materials_pbrSpecularGlossiness():
    """Blender KHR_materials_pbrSpecularGlossiness extension."""
    def __new__(cls, *args, **kwargs):
        raise RuntimeError("%s should not be instantiated" % cls)

    @staticmethod
    def create(gltf, pbrSG, mat_name, vertex_color):
        """KHR_materials_pbrSpecularGlossiness creation."""
        engine = bpy.context.scene.render.engine
        if engine in ['CYCLES', 'BLENDER_EEVEE']:
            BlenderKHR_materials_pbrSpecularGlossiness.create_nodetree(gltf, pbrSG, mat_name, vertex_color)

    @staticmethod
    def create_nodetree(gltf, pbrSG, mat_name, vertex_color):
        """Node tree creation."""
        material = bpy.data.materials[mat_name]
        material.use_nodes = True
        node_tree = material.node_tree

        # delete all nodes except output
        for node in list(node_tree.nodes):
            if not node.type == 'OUTPUT_MATERIAL':
                node_tree.nodes.remove(node)

        output_node = node_tree.nodes[0]
        output_node.location = 1000, 0

        # create PBR node
        diffuse = node_tree.nodes.new('ShaderNodeBsdfDiffuse')
        diffuse.location = 0, 0
        glossy = node_tree.nodes.new('ShaderNodeBsdfGlossy')
        glossy.location = 0, 100
        mix = node_tree.nodes.new('ShaderNodeMixShader')
        mix.location = 500, 0

        glossy.inputs[1].default_value = 1 - pbrSG['glossinessFactor']

        if pbrSG['diffuse_type'] == gltf.SIMPLE:
            if not vertex_color:
                # change input values
                diffuse.inputs[0].default_value = pbrSG['diffuseFactor']

            else:
                # Create attribute node to get COLOR_0 data
                attribute_node = node_tree.nodes.new('ShaderNodeAttribute')
                attribute_node.attribute_name = 'COLOR_0'
                attribute_node.location = -500, 0

                # links
                node_tree.links.new(diffuse.inputs[0], attribute_node.outputs[0])

        elif pbrSG['diffuse_type'] == gltf.TEXTURE_FACTOR:

            # TODO alpha ?
            if vertex_color:
                # TODO tree locations
                # Create attribute / separate / math nodes
                attribute_node = node_tree.nodes.new('ShaderNodeAttribute')
                attribute_node.attribute_name = 'COLOR_0'

                separate_vertex_color = node_tree.nodes.new('ShaderNodeSeparateRGB')
                math_vc_R = node_tree.nodes.new('ShaderNodeMath')
                math_vc_R.operation = 'MULTIPLY'

                math_vc_G = node_tree.nodes.new('ShaderNodeMath')
                math_vc_G.operation = 'MULTIPLY'

                math_vc_B = node_tree.nodes.new('ShaderNodeMath')
                math_vc_B.operation = 'MULTIPLY'

            BlenderTextureInfo.create(gltf, pbrSG['diffuseTexture'], dict_=True)

            # create UV Map / Mapping / Texture nodes / separate & math and combine
            text_node = node_tree.nodes.new('ShaderNodeTexImage')
            if gltf.data.images[
                gltf.data.textures[pbrSG['diffuseTexture']['index']].source].blender_image_name is not None:
                text_node.image = \
                    bpy.data.images[
                        gltf.data.images[
                            gltf.data.textures[pbrSG['diffuseTexture']['index']].source].blender_image_name
                    ]
            text_node.location = -1000, 500

            combine = node_tree.nodes.new('ShaderNodeCombineRGB')
            combine.location = -250, 500

            math_R = node_tree.nodes.new('ShaderNodeMath')
            math_R.location = -500, 750
            math_R.operation = 'MULTIPLY'
            math_R.inputs[1].default_value = pbrSG['diffuseFactor'][0]

            math_G = node_tree.nodes.new('ShaderNodeMath')
            math_G.location = -500, 500
            math_G.operation = 'MULTIPLY'
            math_G.inputs[1].default_value = pbrSG['diffuseFactor'][1]

            math_B = node_tree.nodes.new('ShaderNodeMath')
            math_B.location = -500, 250
            math_B.operation = 'MULTIPLY'
            math_B.inputs[1].default_value = pbrSG['diffuseFactor'][2]

            separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
            separate.location = -750, 500

            mapping = node_tree.nodes.new('ShaderNodeMapping')
            mapping.location = -1500, 500
            mapping.vector_type = 'POINT'
            tex_transform = text_node.image['tex_transform'][str(pbrSG['diffuseTexture']['index'])]
            mapping.inputs['Location'].default_value[0] = texture_transform_gltf_to_blender(tex_transform)['offset'][0]
            mapping.inputs['Location'].default_value[1] = texture_transform_gltf_to_blender(tex_transform)['offset'][1]
            mapping.inputs['Rotation'].default_value[2] = texture_transform_gltf_to_blender(tex_transform)['rotation']
            mapping.inputs['Scale'].default_value[0] = texture_transform_gltf_to_blender(tex_transform)['scale'][0]
            mapping.inputs['Scale'].default_value[1] = texture_transform_gltf_to_blender(tex_transform)['scale'][1]

            uvmap = node_tree.nodes.new('ShaderNodeUVMap')
            uvmap.location = -2000, 500
            if 'texCoord' in pbrSG['diffuseTexture'].keys():
                uvmap["gltf2_texcoord"] = pbrSG['diffuseTexture']['texCoord']  # Set custom flag to retrieve TexCoord
            else:
                uvmap["gltf2_texcoord"] = 0  # TODO: set in precompute instead of here?
            # UV Map will be set after object/UVMap creation

            # Create links
            if vertex_color:
                node_tree.links.new(separate_vertex_color.inputs[0], attribute_node.outputs[0])
                node_tree.links.new(math_vc_R.inputs[1], separate_vertex_color.outputs[0])
                node_tree.links.new(math_vc_G.inputs[1], separate_vertex_color.outputs[1])
                node_tree.links.new(math_vc_B.inputs[1], separate_vertex_color.outputs[2])
                node_tree.links.new(math_vc_R.inputs[0], math_R.outputs[0])
                node_tree.links.new(math_vc_G.inputs[0], math_G.outputs[0])
                node_tree.links.new(math_vc_B.inputs[0], math_B.outputs[0])
                node_tree.links.new(combine.inputs[0], math_vc_R.outputs[0])
                node_tree.links.new(combine.inputs[1], math_vc_G.outputs[0])
                node_tree.links.new(combine.inputs[2], math_vc_B.outputs[0])

            else:
                node_tree.links.new(combine.inputs[0], math_R.outputs[0])
                node_tree.links.new(combine.inputs[1], math_G.outputs[0])
                node_tree.links.new(combine.inputs[2], math_B.outputs[0])

            # Common for both mode (non vertex color / vertex color)
            node_tree.links.new(math_R.inputs[0], separate.outputs[0])
            node_tree.links.new(math_G.inputs[0], separate.outputs[1])
            node_tree.links.new(math_B.inputs[0], separate.outputs[2])

            node_tree.links.new(mapping.inputs[0], uvmap.outputs[0])
            node_tree.links.new(text_node.inputs[0], mapping.outputs[0])
            node_tree.links.new(separate.inputs[0], text_node.outputs[0])

            node_tree.links.new(diffuse.inputs[0], combine.outputs[0])

        elif pbrSG['diffuse_type'] == gltf.TEXTURE:

            BlenderTextureInfo.create(gltf, pbrSG['diffuseTexture'], dict_=True)

            # TODO alpha ?
            if vertex_color:
                # Create attribute / separate / math nodes
                attribute_node = node_tree.nodes.new('ShaderNodeAttribute')
                attribute_node.attribute_name = 'COLOR_0'
                attribute_node.location = -2000, 250

                separate_vertex_color = node_tree.nodes.new('ShaderNodeSeparateRGB')
                separate_vertex_color.location = -1500, 250

                math_vc_R = node_tree.nodes.new('ShaderNodeMath')
                math_vc_R.operation = 'MULTIPLY'
                math_vc_R.location = -1000, 750

                math_vc_G = node_tree.nodes.new('ShaderNodeMath')
                math_vc_G.operation = 'MULTIPLY'
                math_vc_G.location = -1000, 500

                math_vc_B = node_tree.nodes.new('ShaderNodeMath')
                math_vc_B.operation = 'MULTIPLY'
                math_vc_B.location = -1000, 250

                combine = node_tree.nodes.new('ShaderNodeCombineRGB')
                combine.location = -500, 500

                separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
                separate.location = -1500, 500

            # create UV Map / Mapping / Texture nodes / separate & math and combine
            text_node = node_tree.nodes.new('ShaderNodeTexImage')
            if gltf.data.images[
                gltf.data.textures[pbrSG['diffuseTexture']['index']].source].blender_image_name is not None:
                text_node.image = bpy.data.images[
                    gltf.data.images[gltf.data.textures[pbrSG['diffuseTexture']['index']].source].blender_image_name
                ]
            if vertex_color:
                text_node.location = -2000, 500
            else:
                text_node.location = -500, 500

            mapping = node_tree.nodes.new('ShaderNodeMapping')
            if vertex_color:
                mapping.location = -2500, 500
            else:
                mapping.location = -1500, 500
            mapping.vector_type = 'POINT'
            tex_transform = text_node.image['tex_transform'][str(pbrSG['diffuseTexture']['index'])]
            mapping.inputs['Location'].default_value[0] = texture_transform_gltf_to_blender(tex_transform)['offset'][0]
            mapping.inputs['Location'].default_value[1] = texture_transform_gltf_to_blender(tex_transform)['offset'][1]
            mapping.inputs['Rotation'].default_value[2] = texture_transform_gltf_to_blender(tex_transform)['rotation']
            mapping.inputs['Scale'].default_value[0] = texture_transform_gltf_to_blender(tex_transform)['scale'][0]
            mapping.inputs['Scale'].default_value[1] = texture_transform_gltf_to_blender(tex_transform)['scale'][1]


            uvmap = node_tree.nodes.new('ShaderNodeUVMap')
            if vertex_color:
                uvmap.location = -3000, 500
            else:
                uvmap.location = -2000, 500
            if 'texCoord' in pbrSG['diffuseTexture'].keys():
                uvmap["gltf2_texcoord"] = pbrSG['diffuseTexture']['texCoord']  # Set custom flag to retrieve TexCoord
            else:
                uvmap["gltf2_texcoord"] = 0  # TODO: set in precompute instead of here?
            # UV Map will be set after object/UVMap creation

            # Create links
            if vertex_color:
                node_tree.links.new(separate_vertex_color.inputs[0], attribute_node.outputs[0])

                node_tree.links.new(math_vc_R.inputs[1], separate_vertex_color.outputs[0])
                node_tree.links.new(math_vc_G.inputs[1], separate_vertex_color.outputs[1])
                node_tree.links.new(math_vc_B.inputs[1], separate_vertex_color.outputs[2])

                node_tree.links.new(combine.inputs[0], math_vc_R.outputs[0])
                node_tree.links.new(combine.inputs[1], math_vc_G.outputs[0])
                node_tree.links.new(combine.inputs[2], math_vc_B.outputs[0])

                node_tree.links.new(separate.inputs[0], text_node.outputs[0])

                node_tree.links.new(diffuse.inputs[0], combine.outputs[0])

                node_tree.links.new(math_vc_R.inputs[0], separate.outputs[0])
                node_tree.links.new(math_vc_G.inputs[0], separate.outputs[1])
                node_tree.links.new(math_vc_B.inputs[0], separate.outputs[2])

            else:
                node_tree.links.new(diffuse.inputs[0], text_node.outputs[0])

            # Common for both mode (non vertex color / vertex color)

            node_tree.links.new(mapping.inputs[0], uvmap.outputs[0])
            node_tree.links.new(text_node.inputs[0], mapping.outputs[0])

        if pbrSG['specgloss_type'] == gltf.SIMPLE:

            combine = node_tree.nodes.new('ShaderNodeCombineRGB')
            combine.inputs[0].default_value = pbrSG['specularFactor'][0]
            combine.inputs[1].default_value = pbrSG['specularFactor'][1]
            combine.inputs[2].default_value = pbrSG['specularFactor'][2]

            # links
            node_tree.links.new(glossy.inputs[0], combine.outputs[0])

        elif pbrSG['specgloss_type'] == gltf.TEXTURE:
            BlenderTextureInfo.create(gltf, pbrSG['specularGlossinessTexture'], dict_=True)
            spec_text = node_tree.nodes.new('ShaderNodeTexImage')
            if gltf.data.images[
                gltf.data.textures[pbrSG['specularGlossinessTexture']['index']].source
            ].blender_image_name is not None:
                spec_text.image = bpy.data.images[
                    gltf.data.images[
                        gltf.data.textures[pbrSG['specularGlossinessTexture']['index']].source
                    ].blender_image_name
                ]
            if spec_text.image:
                spec_text.image.colorspace_settings.is_data = True
            spec_text.location = -500, 0

            spec_mapping = node_tree.nodes.new('ShaderNodeMapping')
            spec_mapping.location = -1000, 0
            spec_mapping.vector_type = 'POINT'
            tex_transform = spec_text.image['tex_transform'][str(pbrSG['specularGlossinessTexture']['index'])]
            spec_mapping.inputs['Location'].default_value[0] = texture_transform_gltf_to_blender(tex_transform)['offset'][0]
            spec_mapping.inputs['Location'].default_value[1] = texture_transform_gltf_to_blender(tex_transform)['offset'][1]
            spec_mapping.inputs['Rotation'].default_value[2] = texture_transform_gltf_to_blender(tex_transform)['rotation']
            spec_mapping.inputs['Scale'].default_value[0] = texture_transform_gltf_to_blender(tex_transform)['scale'][0]
            spec_mapping.inputs['Scale'].default_value[1] = texture_transform_gltf_to_blender(tex_transform)['scale'][1]


            spec_uvmap = node_tree.nodes.new('ShaderNodeUVMap')
            spec_uvmap.location = -1500, 0
            if 'texCoord' in pbrSG['specularGlossinessTexture'].keys():
                # Set custom flag to retrieve TexCoord
                spec_uvmap["gltf2_texcoord"] = pbrSG['specularGlossinessTexture']['texCoord']
            else:
                spec_uvmap["gltf2_texcoord"] = 0  # TODO: set in precompute instead of here?

            # links
            node_tree.links.new(glossy.inputs[0], spec_text.outputs[0])
            node_tree.links.new(mix.inputs[0], spec_text.outputs[1])

            node_tree.links.new(spec_mapping.inputs[0], spec_uvmap.outputs[0])
            node_tree.links.new(spec_text.inputs[0], spec_mapping.outputs[0])

        elif pbrSG['specgloss_type'] == gltf.TEXTURE_FACTOR:
            BlenderTextureInfo.create(gltf, pbrSG['specularGlossinessTexture'], dict_=True)

            spec_text = node_tree.nodes.new('ShaderNodeTexImage')
            if gltf.data.images[
                gltf.data.textures[pbrSG['specularGlossinessTexture']['index']].source
            ].blender_image_name is not None:
                spec_text.image = bpy.data.images[gltf.data.images[
                    gltf.data.textures[pbrSG['specularGlossinessTexture']['index']].source
                ].blender_image_name]
            if spec_text.image:
                spec_text.image.colorspace_settings.is_data = True
            spec_text.location = -1000, 0

            spec_math = node_tree.nodes.new('ShaderNodeMath')
            spec_math.operation = 'MULTIPLY'
            spec_math.inputs[0].default_value = pbrSG['glossinessFactor']
            spec_math.location = -250, 100

            spec_mapping = node_tree.nodes.new('ShaderNodeMapping')
            spec_mapping.location = -1000, 0
            spec_mapping.vector_type = 'POINT'
            tex_transform = spec_text.image['tex_transform'][str(pbrSG['specularGlossinessTexture']['index'])]
            spec_mapping.inputs['Location'].default_value[0] = texture_transform_gltf_to_blender(tex_transform)['offset'][0]
            spec_mapping.inputs['Location'].default_value[1] = texture_transform_gltf_to_blender(tex_transform)['offset'][1]
            spec_mapping.inputs['Rotation'].default_value[2] = texture_transform_gltf_to_blender(tex_transform)['rotation']
            spec_mapping.inputs['Scale'].default_value[0] = texture_transform_gltf_to_blender(tex_transform)['scale'][0]
            spec_mapping.inputs['Scale'].default_value[1] = texture_transform_gltf_to_blender(tex_transform)['scale'][1]



            spec_uvmap = node_tree.nodes.new('ShaderNodeUVMap')
            spec_uvmap.location = -1500, 0
            if 'texCoord' in pbrSG['specularGlossinessTexture'].keys():
                # Set custom flag to retrieve TexCoord
                spec_uvmap["gltf2_texcoord"] = pbrSG['specularGlossinessTexture']['texCoord']
            else:
                spec_uvmap["gltf2_texcoord"] = 0  # TODO: set in precompute instead of here?

            # links

            node_tree.links.new(spec_math.inputs[1], spec_text.outputs[0])
            node_tree.links.new(mix.inputs[0], spec_text.outputs[1])
            node_tree.links.new(glossy.inputs[1], spec_math.outputs[0])
            node_tree.links.new(glossy.inputs[0], spec_text.outputs[0])

            node_tree.links.new(spec_mapping.inputs[0], spec_uvmap.outputs[0])
            node_tree.links.new(spec_text.inputs[0], spec_mapping.outputs[0])

        # link node to output
        node_tree.links.new(mix.inputs[2], diffuse.outputs[0])
        node_tree.links.new(mix.inputs[1], glossy.outputs[0])
        node_tree.links.new(output_node.inputs[0], mix.outputs[0])

