# 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 BlenderPbr():
    """Blender Pbr."""
    def __new__(cls, *args, **kwargs):
        raise RuntimeError("%s should not be instantiated" % cls)

    def create(gltf, pypbr, mat_name, vertex_color):
        """Pbr creation."""
        engine = bpy.context.scene.render.engine
        if engine in ['CYCLES', 'BLENDER_EEVEE']:
            BlenderPbr.create_nodetree(gltf, pypbr, mat_name, vertex_color)

    def create_nodetree(gltf, pypbr, mat_name, vertex_color, nodetype='principled'):
        """Nodetree creation."""
        material = bpy.data.materials[mat_name]
        material.use_nodes = True
        node_tree = material.node_tree

        # If there is no diffuse texture, but only a color, wihtout
        # vertex_color, we set this color in viewport color
        if pypbr.color_type == gltf.SIMPLE and not vertex_color:
            # Manage some change in beta version on 20190129
            if len(material.diffuse_color) == 3:
                material.diffuse_color = pypbr.base_color_factor[:3]
            else:
                material.diffuse_color = pypbr.base_color_factor

        # 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 = 1250, 0

        # create Main node
        if nodetype == "principled":
            main_node = node_tree.nodes.new('ShaderNodeBsdfPrincipled')
            main_node.location = 0, 0
        elif nodetype == "unlit":
            main_node = node_tree.nodes.new('ShaderNodeEmission')
            main_node.location = 750, -300

        if pypbr.color_type == gltf.SIMPLE:

            if not vertex_color:

                # change input values
                main_node.inputs[0].default_value = pypbr.base_color_factor
                if nodetype == "principled":
                    # TODO : currently set metallic & specular in same way
                    main_node.inputs[5].default_value = pypbr.metallic_factor
                    main_node.inputs[7].default_value = pypbr.roughness_factor

            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

                if nodetype == "principled":
                    # TODO : currently set metallic & specular in same way
                    main_node.inputs[5].default_value = pypbr.metallic_factor
                    main_node.inputs[7].default_value = pypbr.roughness_factor

                # links
                rgb_node = node_tree.nodes.new('ShaderNodeMixRGB')
                rgb_node.blend_type = 'MULTIPLY'
                rgb_node.inputs['Fac'].default_value = 1.0
                rgb_node.inputs['Color1'].default_value = pypbr.base_color_factor
                node_tree.links.new(rgb_node.inputs['Color2'], attribute_node.outputs[0])
                node_tree.links.new(main_node.inputs[0], rgb_node.outputs[0])

        elif pypbr.color_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'

                vc_mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
                vc_mult_node.blend_type = 'MULTIPLY'
                vc_mult_node.inputs['Fac'].default_value = 1.0

            BlenderTextureInfo.create(gltf, pypbr.base_color_texture)

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

            mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
            mult_node.blend_type = 'MULTIPLY'
            mult_node.inputs['Fac'].default_value = 1.0
            mult_node.inputs['Color2'].default_value = [
                                                        pypbr.base_color_factor[0],
                                                        pypbr.base_color_factor[1],
                                                        pypbr.base_color_factor[2],
                                                        pypbr.base_color_factor[3],
                                                        ]

            mapping = node_tree.nodes.new('ShaderNodeMapping')
            mapping.location = -1500, 500
            mapping.vector_type = 'POINT'
            if text_node.image is not None: # Sometimes images can't be retrieved (bad gltf file ...)
                tex_transform = text_node.image['tex_transform'][str(pypbr.base_color_texture.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 pypbr.base_color_texture.tex_coord is not None:
                uvmap["gltf2_texcoord"] = pypbr.base_color_texture.tex_coord  # Set custom flag to retrieve TexCoord
            else:
                uvmap["gltf2_texcoord"] = 0  # TODO set in pre_compute instead of here
            # UV Map will be set after object/UVMap creation

            # Create links
            if vertex_color:
                node_tree.links.new(vc_mult_node.inputs[2], attribute_node.outputs[0])
                node_tree.links.new(vc_mult_node.inputs[1], mult_node.outputs[0])
                node_tree.links.new(main_node.inputs[0], vc_mult_node.outputs[0])

            else:
                node_tree.links.new(main_node.inputs[0], mult_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])
            node_tree.links.new(mult_node.inputs[1], text_node.outputs[0])

        elif pypbr.color_type == gltf.TEXTURE:

            BlenderTextureInfo.create(gltf, pypbr.base_color_texture)

            # 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

                vc_mult_node = node_tree.nodes.new('ShaderNodeMixRGB')
                vc_mult_node.blend_type = 'MULTIPLY'
                vc_mult_node.inputs['Fac'].default_value = 1.0

            # create UV Map / Mapping / Texture nodes / separate & math and combine
            text_node = node_tree.nodes.new('ShaderNodeTexImage')
            if gltf.data.images[
                gltf.data.textures[pypbr.base_color_texture.index].source
            ].blender_image_name is not None:
                text_node.image = bpy.data.images[gltf.data.images[
                    gltf.data.textures[pypbr.base_color_texture.index].source
                ].blender_image_name]
            text_node.label = 'BASE COLOR'
            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'
            if text_node.image is not None: # Sometimes images can't be retrieved (bad gltf file ...)
                tex_transform = text_node.image['tex_transform'][str(pypbr.base_color_texture.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 pypbr.base_color_texture.tex_coord is not None:
                uvmap["gltf2_texcoord"] = pypbr.base_color_texture.tex_coord  # Set custom flag to retrieve TexCoord
            else:
                uvmap["gltf2_texcoord"] = 0  # TODO set in pre_compute instead of here
            # UV Map will be set after object/UVMap creation

            # Create links
            if vertex_color:
                node_tree.links.new(vc_mult_node.inputs[2], attribute_node.outputs[0])
                node_tree.links.new(vc_mult_node.inputs[1], text_node.outputs[0])
                node_tree.links.new(main_node.inputs[0], vc_mult_node.outputs[0])

            else:
                node_tree.links.new(main_node.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 nodetype == 'principled':
            # Says metallic, but it means metallic & Roughness values
            if pypbr.metallic_type == gltf.SIMPLE:
                main_node.inputs[4].default_value = pypbr.metallic_factor
                main_node.inputs[7].default_value = pypbr.roughness_factor

            elif pypbr.metallic_type == gltf.TEXTURE:
                BlenderTextureInfo.create(gltf, pypbr.metallic_roughness_texture)
                metallic_text = node_tree.nodes.new('ShaderNodeTexImage')
                metallic_text.image = bpy.data.images[gltf.data.images[
                    gltf.data.textures[pypbr.metallic_roughness_texture.index].source
                ].blender_image_name]
                if metallic_text.image:
                    metallic_text.image.colorspace_settings.is_data = True
                metallic_text.label = 'METALLIC ROUGHNESS'
                metallic_text.location = -500, 0

                metallic_separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
                metallic_separate.location = -250, 0

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


                metallic_uvmap = node_tree.nodes.new('ShaderNodeUVMap')
                metallic_uvmap.location = -1500, 0
                if pypbr.metallic_roughness_texture.tex_coord is not None:
                    # Set custom flag to retrieve TexCoord
                    metallic_uvmap["gltf2_texcoord"] = pypbr.metallic_roughness_texture.tex_coord
                else:
                    metallic_uvmap["gltf2_texcoord"] = 0  # TODO set in pre_compute instead of here

                # links
                node_tree.links.new(metallic_separate.inputs[0], metallic_text.outputs[0])
                node_tree.links.new(main_node.inputs[4], metallic_separate.outputs[2])  # metallic
                node_tree.links.new(main_node.inputs[7], metallic_separate.outputs[1])  # Roughness

                node_tree.links.new(metallic_mapping.inputs[0], metallic_uvmap.outputs[0])
                node_tree.links.new(metallic_text.inputs[0], metallic_mapping.outputs[0])

            elif pypbr.metallic_type == gltf.TEXTURE_FACTOR:

                BlenderTextureInfo.create(gltf, pypbr.metallic_roughness_texture)
                metallic_text = node_tree.nodes.new('ShaderNodeTexImage')
                metallic_text.image = bpy.data.images[gltf.data.images[
                    gltf.data.textures[pypbr.metallic_roughness_texture.index].source
                ].blender_image_name]
                if metallic_text.image:
                    metallic_text.image.colorspace_settings.is_data = True
                metallic_text.label = 'METALLIC ROUGHNESS'
                metallic_text.location = -1000, 0

                metallic_separate = node_tree.nodes.new('ShaderNodeSeparateRGB')
                metallic_separate.location = -500, 0

                metallic_math = node_tree.nodes.new('ShaderNodeMath')
                metallic_math.operation = 'MULTIPLY'
                metallic_math.inputs[1].default_value = pypbr.metallic_factor
                metallic_math.location = -250, 100

                roughness_math = node_tree.nodes.new('ShaderNodeMath')
                roughness_math.operation = 'MULTIPLY'
                roughness_math.inputs[1].default_value = pypbr.roughness_factor
                roughness_math.location = -250, -100

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

                metallic_uvmap = node_tree.nodes.new('ShaderNodeUVMap')
                metallic_uvmap.location = -1500, 0
                if pypbr.metallic_roughness_texture.tex_coord is not None:
                    # Set custom flag to retrieve TexCoord
                    metallic_uvmap["gltf2_texcoord"] = pypbr.metallic_roughness_texture.tex_coord
                else:
                    metallic_uvmap["gltf2_texcoord"] = 0  # TODO set in pre_compute instead of here

                # links
                node_tree.links.new(metallic_separate.inputs[0], metallic_text.outputs[0])

                # metallic
                node_tree.links.new(metallic_math.inputs[0], metallic_separate.outputs[2])
                node_tree.links.new(main_node.inputs[4], metallic_math.outputs[0])

                # roughness
                node_tree.links.new(roughness_math.inputs[0], metallic_separate.outputs[1])
                node_tree.links.new(main_node.inputs[7], roughness_math.outputs[0])

                node_tree.links.new(metallic_mapping.inputs[0], metallic_uvmap.outputs[0])
                node_tree.links.new(metallic_text.inputs[0], metallic_mapping.outputs[0])

        # link node to output
        if nodetype == 'principled':
            node_tree.links.new(output_node.inputs[0], main_node.outputs[0])
        elif nodetype == 'unlit':
            mix = node_tree.nodes.new('ShaderNodeMixShader')
            mix.location = 1000, 0
            path = node_tree.nodes.new('ShaderNodeLightPath')
            path.location = 500, 300
            if pypbr.color_type != gltf.SIMPLE:
                math = node_tree.nodes.new('ShaderNodeMath')
                math.location = 750, 200
                math.operation = 'MULTIPLY'

                # Set material alpha mode to blend
                # This is needed for Eevee
                material.blend_method = 'HASHED' # TODO check best result in eevee

            transparent = node_tree.nodes.new('ShaderNodeBsdfTransparent')
            transparent.location = 750, 0

            node_tree.links.new(output_node.inputs[0], mix.outputs[0])
            node_tree.links.new(mix.inputs[2], main_node.outputs[0])
            node_tree.links.new(mix.inputs[1], transparent.outputs[0])
            if pypbr.color_type != gltf.SIMPLE:
                node_tree.links.new(math.inputs[0], path.outputs[0])
                node_tree.links.new(math.inputs[1], text_node.outputs[1])
                node_tree.links.new(mix.inputs[0], math.outputs[0])
            else:
                node_tree.links.new(mix.inputs[0], path.outputs[0])

