# 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 math import sqrt
from mathutils import Quaternion
from .gltf2_blender_node import BlenderNode
from .gltf2_blender_skin import BlenderSkin
from .gltf2_blender_animation import BlenderAnimation
from .gltf2_blender_animation_utils import simulate_stash


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

    @staticmethod
    def create(gltf, scene_idx):
        """Scene creation."""
        gltf.blender_active_collection = None
        if scene_idx is not None:
            pyscene = gltf.data.scenes[scene_idx]
            list_nodes = pyscene.nodes

            # Create a new scene only if not already exists in .blend file
            # TODO : put in current scene instead ?
            if pyscene.name not in [scene.name for scene in bpy.data.scenes]:
                # TODO: There is a bug in 2.8 alpha that break CLEAR_KEEP_TRANSFORM
                # if we are creating a new scene
                scene = bpy.context.scene
                if bpy.context.collection.name in bpy.data.collections: # avoid master collection
                    gltf.blender_active_collection = bpy.context.collection.name
                if scene.render.engine not in ['CYCLES', 'BLENDER_EEVEE']:
                    scene.render.engine = "BLENDER_EEVEE"

                gltf.blender_scene = scene.name
            else:
                gltf.blender_scene = pyscene.name

            # Switch to newly created main scene
            bpy.context.window.scene = bpy.data.scenes[gltf.blender_scene]
            if bpy.context.collection.name in bpy.data.collections: # avoid master collection
                gltf.blender_active_collection = bpy.context.collection.name

        else:
            # No scene in glTF file, create all objects in current scene
            scene = bpy.context.scene
            if scene.render.engine not in ['CYCLES', 'BLENDER_EEVEE']:
                scene.render.engine = "BLENDER_EEVEE"
            if bpy.context.collection.name in bpy.data.collections: # avoid master collection
                gltf.blender_active_collection = bpy.context.collection.name
            gltf.blender_scene = scene.name
            list_nodes = BlenderScene.get_root_nodes(gltf)

        if bpy.app.debug_value != 100:
            # Create Yup2Zup empty
            obj_rotation = bpy.data.objects.new("Yup2Zup", None)
            obj_rotation.rotation_mode = 'QUATERNION'
            obj_rotation.rotation_quaternion = Quaternion((sqrt(2) / 2, sqrt(2) / 2, 0.0, 0.0))

            if gltf.blender_active_collection is not None:
                bpy.data.collections[gltf.blender_active_collection].objects.link(obj_rotation)
            else:
                bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj_rotation)

        if list_nodes is not None:
            for node_idx in list_nodes:
                BlenderNode.create(gltf, node_idx, None)  # None => No parent

        # Now that all mesh / bones are created, create vertex groups on mesh
        if gltf.data.skins:
            for skin_id, skin in enumerate(gltf.data.skins):
                if hasattr(skin, "node_ids"):
                    BlenderSkin.create_vertex_groups(gltf, skin_id)

            for skin_id, skin in enumerate(gltf.data.skins):
                if hasattr(skin, "node_ids"):
                    BlenderSkin.create_armature_modifiers(gltf, skin_id)

        if gltf.data.animations:
            for anim_idx, anim in enumerate(gltf.data.animations):
                # Blender armature name -> action all its bones should use
                gltf.arma_cache = {}
                # Things we need to stash when we're done.
                gltf.needs_stash = []

                if list_nodes is not None:
                    for node_idx in list_nodes:
                        BlenderAnimation.anim(gltf, anim_idx, node_idx)

                for (obj, anim_name, action) in gltf.needs_stash:
                    simulate_stash(obj, anim_name, action)

            # Restore first animation
            anim_name = gltf.data.animations[0].track_name
            for node_idx in list_nodes:
                BlenderAnimation.restore_animation(gltf, node_idx, anim_name)

        if bpy.app.debug_value != 100:
            # Parent root node to rotation object
            if list_nodes is not None:
                exclude_nodes = []
                for node_idx in list_nodes:
                    if gltf.data.nodes[node_idx].is_joint:
                        # Do not change parent if root node is already parented (can be the case for skinned mesh)
                        if not bpy.data.objects[gltf.data.nodes[node_idx].blender_armature_name].parent:
                            bpy.data.objects[gltf.data.nodes[node_idx].blender_armature_name].parent = obj_rotation
                        else:
                            exclude_nodes.append(node_idx)
                    else:
                        # Do not change parent if root node is already parented (can be the case for skinned mesh)
                        if not bpy.data.objects[gltf.data.nodes[node_idx].blender_object].parent:
                            bpy.data.objects[gltf.data.nodes[node_idx].blender_object].parent = obj_rotation
                        else:
                            exclude_nodes.append(node_idx)

                if gltf.animation_object is False:





                    # Avoid rotation bug if collection is hidden or disabled
                    if gltf.blender_active_collection is not None:
                        gltf.collection_hide_viewport = bpy.data.collections[gltf.blender_active_collection].hide_viewport
                        bpy.data.collections[gltf.blender_active_collection].hide_viewport = False
                        # TODO for visibility ... but seems not exposed on bpy for now

                    for node_idx in list_nodes:

                        if node_idx in exclude_nodes:
                            continue # for root node that are parented by the process
                            # for example skinned meshes

                        for obj_ in bpy.context.scene.objects:
                            obj_.select_set(False)
                        if gltf.data.nodes[node_idx].is_joint:
                            bpy.data.objects[gltf.data.nodes[node_idx].blender_armature_name].select_set(True)
                            bpy.context.view_layer.objects.active = bpy.data.objects[gltf.data.nodes[node_idx].blender_armature_name]

                        else:
                            bpy.data.objects[gltf.data.nodes[node_idx].blender_object].select_set(True)
                            bpy.context.view_layer.objects.active = bpy.data.objects[gltf.data.nodes[node_idx].blender_object]

                        bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')

                    # remove object
                    #bpy.context.scene.collection.objects.unlink(obj_rotation)
                    bpy.data.objects.remove(obj_rotation)

                    # Restore collection hidden / disabled values
                    if gltf.blender_active_collection is not None:
                        bpy.data.collections[gltf.blender_active_collection].hide_viewport = gltf.collection_hide_viewport
                        # TODO restore visibility when expose in bpy

        # Make first root object the new active one
        if list_nodes is not None:
            if gltf.data.nodes[list_nodes[0]].blender_object:
                bl_name = gltf.data.nodes[list_nodes[0]].blender_object
            else:
                bl_name = gltf.data.nodes[list_nodes[0]].blender_armature_name
            bpy.context.view_layer.objects.active = bpy.data.objects[bl_name]

    @staticmethod
    def get_root_nodes(gltf):
        if gltf.data.nodes is None:
            return None

        parents = {}
        for idx, node  in enumerate(gltf.data.nodes):
            pynode = gltf.data.nodes[idx]
            if pynode.children:
                for child_idx in pynode.children:
                    parents[child_idx] = idx

        roots = []
        for idx, node in enumerate(gltf.data.nodes):
            if idx not in parents.keys():
                roots.append(idx)

        return roots

