// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license. 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Simplygon;
using System.IO;
using System.Linq;
using Unity.Formats.USD;
using USD.NET;
using SimplygonUnityEditorPlugin.Logger;

namespace Simplygon.Unity.EditorPlugin
{
    public static class SimplygonExporter
    {
        public static spScene Export(ISimplygon simplygon, string tempDirectory, List<GameObject> selectedGameObjects)
        {
            if (!string.IsNullOrEmpty(tempDirectory))
            {
                string usdFile = System.IO.Path.Combine(tempDirectory, "export.usd");

                InitUsd.Initialize();
                var scene = Scene.Create(usdFile);
                scene.Time = 0;
                scene.StartTime = 0;
                scene.EndTime = 0;

                var root = new UnityEngine.GameObject();
                string rootName = "";
                List<GameObject> selectedGameObjectClones = new List<GameObject>();

                if (selectedGameObjects.Count > 1)
                {
                    rootName = root.name = string.Join("_", selectedGameObjects.Select(i => i.name));
                    List<string> duplicateNames = selectedGameObjects.Select(go => go.name).GroupBy(s => s).SelectMany(g => g.Skip(1)).ToList();

                    foreach (var gameObject in selectedGameObjects)
                    {
                        var clone = UnityEngine.Object.Instantiate(gameObject);

                        clone.transform.SetParent(root.transform);

                        clone.transform.localPosition = gameObject.transform.position;
                        clone.transform.localScale = gameObject.transform.lossyScale;
                        clone.transform.localRotation = gameObject.transform.rotation;

                        if (duplicateNames.Contains(gameObject.name))
                        {
                            clone.name = GetInstanceName(gameObject);
                        }
                        else
                        {
                            clone.name = gameObject.name;
                        }

                        selectedGameObjectClones.Add(clone);

                    }

                    SceneExporter.Export(root,
                                         scene,
                                         BasisTransformation.SlowAndSafe,
                                         exportUnvarying: true,
                                         zeroRootTransform: false,
                                         exportMaterials: true,
                                         exportMonoBehaviours: false);
                }
                else
                {
                    var gameObject = selectedGameObjects.First();
                    var clone = UnityEngine.Object.Instantiate(gameObject);

                    clone.transform.SetParent(null);

                    bool skinnedAsset = gameObject.GetComponentInChildren<SkinnedMeshRenderer>() != null;

                    if (clone.transform.childCount > 0 && !skinnedAsset)
                    {
                        List<string> childrenNames = new List<string>();

                        for (int c = 0; c < clone.transform.childCount; ++c)
                        {
                            var child = clone.transform.GetChild(c).gameObject;
                            childrenNames.Add(child.name);
                        }

                        List<string> duplicateNames = childrenNames.GroupBy(s => s).SelectMany(g => g.Skip(1)).ToList();

                        for (int c = 0; c < clone.transform.childCount; ++c)
                        {
                            var child = clone.transform.GetChild(c).gameObject;
                            var originalChild = gameObject.transform.GetChild(c).gameObject;

                            child.transform.localPosition = child.transform.position;
                            child.transform.localScale = child.transform.lossyScale;
                            child.transform.localRotation = child.transform.rotation;

                            if (duplicateNames.Contains(child.name))
                            {
                                child.name = GetInstanceName(originalChild);
                            }
                        }

                        clone.transform.localPosition = Vector3.zero;
                        clone.transform.localScale = Vector3.one;
                        clone.transform.localRotation = Quaternion.identity;

                    }
                    else
                    {
                        clone.transform.localPosition = gameObject.transform.position;
                        clone.transform.localScale = gameObject.transform.lossyScale;
                        clone.transform.localRotation = gameObject.transform.rotation;
                    }

                    clone.name = gameObject.name;

                    SceneExporter.Export(clone,
                                         scene,
                                         BasisTransformation.SlowAndSafe,
                                         exportUnvarying: true,
                                         zeroRootTransform: skinnedAsset,
                                         exportMaterials: true,
                                         exportMonoBehaviours: false);

                    selectedGameObjectClones.Add(clone);
                }

                scene.Save();
                scene.Close();

                foreach (var clone in selectedGameObjectClones)
                {
                    UnityEngine.Object.DestroyImmediate(clone);
                }
                UnityEngine.Object.DestroyImmediate(root);

                using (spSceneImporter sgSceneImporter = simplygon.CreateSceneImporter())
                {
                    sgSceneImporter.SetImportFilePath(usdFile);
                    sgSceneImporter.RunImport();

                    SimplygonExporter.ExportSelectionSetsInSelection(simplygon, sgSceneImporter.GetScene(), selectedGameObjects, rootName);

                    return sgSceneImporter.GetScene();
                }
            }
            return null;
        }

        private static void ExportSelectionSetsInSelection(ISimplygon simplygon, spScene sgScene, List<GameObject> selectedGameObjects, string rootName)
        {
            if (sgScene == null || sgScene.IsNull())
            {
                return;
            }

            var selectionSetGroups = Resources.FindObjectsOfTypeAll<SimplygonSelectionSet>().Where(i => Selection.Contains(i.gameObject) || Selection.gameObjects.Any(p => i.transform.IsChildOf(p.transform)) || i.gameObject.GetComponent<Camera>() != null).Select(i => new { SelectionSetName = i.SelectionSetName, IncludeChildren = i.IncludeChildrenInSelectionSet, GameObject = i.transform.gameObject }).GroupBy(i => i.SelectionSetName);

            spSelectionSetTable sgSelectionSetsTable = sgScene.GetSelectionSetTable();
            foreach (var selectionSetGroup in selectionSetGroups)
            {
                spSelectionSet sgSelectionSet = simplygon.CreateSelectionSet();
                sgSelectionSet.SetName(selectionSetGroup.First().SelectionSetName);

                foreach (var selectionSetItem in selectionSetGroup)
                {
                    if (selectionSetItem.GameObject.GetComponent<Camera>() != null)
                    {
                        var pos = selectionSetItem.GameObject.transform.position;
                        var look = selectionSetItem.GameObject.transform.forward;
                        var fov = selectionSetItem.GameObject.GetComponent<Camera>().fieldOfView;

                        spSceneCamera sgSceneCamera = simplygon.CreateSceneCamera();
                        sgSceneCamera.SetName(selectionSetItem.GameObject.GetComponent<Camera>().name);

                        sgSceneCamera.GetCameraPositions().SetTupleCount(1);
                        sgSceneCamera.GetCameraPositions().SetItem(0, pos.x);
                        sgSceneCamera.GetCameraPositions().SetItem(1, pos.y);
                        sgSceneCamera.GetCameraPositions().SetItem(2, -pos.z);

                        sgSceneCamera.GetTargetPositions().SetTupleCount(1);
                        sgSceneCamera.GetTargetPositions().SetItem(0, pos.x + look.x);
                        sgSceneCamera.GetTargetPositions().SetItem(1, pos.y + look.y);
                        sgSceneCamera.GetTargetPositions().SetItem(2, -pos.z + -look.z);

                        sgSceneCamera.SetUseNormalizedCoordinates(false);
                        sgSceneCamera.SetFieldOfView(fov * Mathf.Deg2Rad);
                        sgScene.GetRootNode().AddChild(sgSceneCamera);
                        sgSelectionSet.AddItem(sgSceneCamera.GetNodeGUID());
                    }
                    else
                    {
                        foreach (var selectedGameObject in selectedGameObjects)
                        {
                            if (selectionSetItem.GameObject.transform.IsChildOf(selectedGameObject.transform))
                            {
                                string path = GetParentScenePath(selectionSetItem.GameObject);
                                string rootPath = GetParentScenePath(selectedGameObject);

                                string selectedGameObjectName = selectedGameObject.name;
                                if (selectedGameObjects.Count > 1)
                                {
                                    List<string> duplicateNames = selectedGameObjects.Select(go => go.name).GroupBy(s => s).SelectMany(g => g.Skip(1)).ToList();
                                    if (duplicateNames.Contains(selectedGameObject.name))
                                    {
                                        selectedGameObjectName = GetInstanceName(selectedGameObject);
                                    }
                                }

                                if (!string.IsNullOrEmpty(rootName))
                                {
                                    path = $"Root/{rootName}/" + selectedGameObjectName + path.Replace(rootPath, "");
                                }
                                else
                                {
                                    path = "Root/" + selectedGameObjectName + path.Replace(rootPath, "");
                                }

                                spSceneNode node = sgScene.GetNodeFromPath(FixNodeNamesInPath(path));

                                if (!node.IsNull())
                                {
                                    if (selectionSetItem.IncludeChildren)
                                    {
                                        sgSelectionSet.AddHierarchy(node);
                                    }
                                    else
                                    {
                                        sgSelectionSet.AddItem(node.GetNodeGUID());
                                    }
                                }
                                else
                                {
                                    SimplygonLogger.Instance.Log(Category.Warning, $"Failed to set selection set with path {path}.");
                                }
                            }
                        }
                    }
                }

                sgSelectionSetsTable.AddSelectionSet(sgSelectionSet);
            }
        }

        private static string GetInstanceName(GameObject go)
        {
            return go.name + "_" + go.GetInstanceID().ToString().Replace("-", "_");
        }

        private static string GetParentScenePath(GameObject go)
        {
            if (go.transform.parent != null)
            {
                int nameMatches = 0;

                for (int c = 0; c < go.transform.parent.childCount; ++c)
                {
                    var child = go.transform.parent.GetChild(c).gameObject;
                    if (child.name == go.name)
                    {
                        nameMatches++;
                    }
                }

                string gameObjectName = go.name;
                if (nameMatches > 1)
                {
                    gameObjectName = GetInstanceName(go);
                }

                return GetParentScenePath(go.transform.parent.gameObject) + "/" + gameObjectName;
            }
            return go.name;
        }

        private static string FixNodeNamesInPath(string path)
        {
            path = path.Replace(" ", "_");
            path = path.Replace("(", "_");
            path = path.Replace(")", "_");

            return path;
        }
    }
}