using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using System.Collections.Generic;
using UnityEditor.SceneManagement;
using System.IO;

// Editor shortcuts:
//   F3         = view to main camera
//   Alt+F3     = main camera to view
//   Shift+F3   = main camera to selected

public class GFTools
{
    // hot keys: _ normal, % ctrl, # shift, & alt

    private static Camera GetMainCamera()
    {
        // return GameObject.FindGameObjectWithTag("MainCamera").GetComponent<Camera>();
        return GameObject.Find("MainCamera").GetComponent<Camera>();

    }

    [MenuItem("Tools/GF/View To Camera _F3")]
    private static void ViewToCamera()
    {
        Camera mainCamera = GetMainCamera();
        if (mainCamera && SceneView.lastActiveSceneView)
        {
            Undo.RegisterCompleteObjectUndo(SceneView.lastActiveSceneView, "ViewToCamera");

            SceneView.lastActiveSceneView.AlignViewToObject(mainCamera.transform);
        }
    }

    [MenuItem("Tools/GF/Camera To View &F3")]
    private static void CameraToView()
    {
        Camera mainCamera = GetMainCamera();
        if (mainCamera && SceneView.lastActiveSceneView)
        {
            Undo.RegisterCompleteObjectUndo(mainCamera, "CameraToView");

            Transform viewTransform = SceneView.lastActiveSceneView.camera.transform;
            mainCamera.transform.position = viewTransform.position;
            mainCamera.transform.rotation = viewTransform.rotation;
        }
    }


    [MenuItem("Tools/GF/Camera To Selected %F3")]
    private static void CameraToSelected()
    {
        Camera mainCamera = GetMainCamera();
        GameObject obj = Selection.activeGameObject;
        if (mainCamera && obj)
        {
            Undo.RegisterCompleteObjectUndo(mainCamera, "CameraToSelected " + obj.name);

            mainCamera.transform.position = obj.transform.position;
            mainCamera.transform.rotation = obj.transform.rotation;
        }
    }
    /*
        [MenuItem("Tools/GF/FindObject  %F7")]
        private static void FindObject()
        {
            var p = AssetDatabase.GUIDToAssetPath("39ac9fb1915efed498d576ce9a3b88bf");
            Debug.Log(p);
            //string[] ret = AssetDatabase.FindAssets("39ac9fb1915efed498d576ce9a3b88bf");
        }
    */

    // Alt+D duplicate selected objects with the same name, keeping correct transform values (avoid Unity bug)
    [MenuItem("Tools/GF/DuplicateX &D", false, 0)]
    static public void DuplicateX(MenuCommand menuCommand)
    {
        if (Selection.gameObjects.Length == 0)
            return;

        List<GameObject> newObjects = new List<GameObject>();
        foreach (var obj in Selection.gameObjects)
        {
            GameObject newObject = Object.Instantiate<GameObject>(obj);
            newObjects.Add(newObject);

            newObject.transform.localRotation = obj.transform.localRotation;
            newObject.transform.SetParent(obj.transform.parent, false);

            //apply exact values from original
            newObject.transform.localPosition = obj.transform.localPosition;
            newObject.transform.localEulerAngles = obj.transform.localEulerAngles;
            newObject.transform.localScale = obj.transform.localScale;

            Undo.RegisterCreatedObjectUndo(newObject, "Custom Duplicate");

            Selection.activeGameObject = newObject;

            newObject.name = obj.name;
            newObject.transform.SetSiblingIndex(obj.transform.GetSiblingIndex() + 1);
        }

        Selection.objects = newObjects.ToArray();
    }

    // Ctrl+Shift+C Create instances of current object at selected objets' positions
    [MenuItem("Tools/GF/Create with prefab #%C")]
    private static void CreateWithPrefab()
    {
        InstantiateWithPrefab(false);
    }

    // Ctrl+Shift+R Replace instances of selected objets with current object
    [MenuItem("Tools/GF/Replace with prefab #%R")]
    private static void ReplaceWithPrefab()
    {
        InstantiateWithPrefab(true);
    }

    private static void InstantiateWithPrefab(bool deleteDestination)
    {
        GameObject originalObject = null;
        Object[] prefabList = Selection.GetFiltered(typeof(UnityEngine.GameObject), SelectionMode.Assets);

        // try to find a prefab
        foreach (var o in prefabList)
        {
            PrefabType t = PrefabUtility.GetPrefabType(o);
            if (t == PrefabType.Prefab || t == PrefabType.ModelPrefab)
            {
                if (originalObject == null)
                {
                    originalObject = o as GameObject;
                }
                else
                {
                    Debug.Log("select only one prefab in project window");
                    return;
                }
            }
        }

        // if no prefab found, use active object
        GameObject originalGameObject = null;
        if (originalObject == null)
        {
            originalObject = Selection.activeGameObject;
            if (originalObject != null)
            {
                originalGameObject = originalObject;

                PrefabType t = PrefabUtility.GetPrefabType(originalObject);
                if (t == PrefabType.PrefabInstance)
                {
                    GameObject originalPrefab = PrefabUtility.GetCorrespondingObjectFromSource(originalObject) as GameObject;
                    if (originalPrefab != null)
                        originalObject = originalPrefab;
                }
            }
        }

        if (originalObject == null)
        {
            Debug.Log("select one prefab in project window or an object in the scene window");
            return;
        }

        // clear selection
        Selection.activeGameObject = null;
        Selection.objects = new Object[0];

        if (deleteDestination)
            Debug.Log("Replacing with " + originalObject.name + ":");
        else
            Debug.Log("Duplicating " + originalObject.name + " at:");

        List<GameObject> newObjects = new List<GameObject>();

        for (int i = 0; i < prefabList.Length; i++)
        {
            GameObject go = prefabList[i] as GameObject;
            if (!go || go == originalObject || go == originalGameObject)
                continue;

            PrefabType t = PrefabUtility.GetPrefabType(go);
            if (t != PrefabType.Prefab && t != PrefabType.ModelPrefab)
            {
                Debug.Log("  " + (newObjects.Count + 1).ToString() + " " + go.name);

                var newObject = ReplaceObjectWithPrefab(go, originalObject, deleteDestination);
                if (newObject == null)
                {
                    Debug.Log("failed to instantiate");
                    break;
                }

                newObjects.Add(newObject);
            }
        }

        Selection.activeGameObject = newObjects.Count != 0 ? newObjects[0] : null;
        Selection.objects = newObjects.ToArray();

        EditorSceneManager.MarkAllScenesDirty();

        // focus hierarchy window to focus active selected item (new created object)
        EditorApplication.ExecuteMenuItem("Window/General/Hierarchy");
    }

    // Ctrl+E rotate selected objects 90 degrees
    [MenuItem("Tools/GF/Rotate Y 90 %E")]
    private static void RotateY90()
    {
        //GameObject obj = Selection.activeGameObject;
        //if (obj)
        foreach (var obj in Selection.gameObjects)
        {
            Undo.RegisterCompleteObjectUndo(obj, "RotY90");
            Vector3 r = obj.transform.rotation.eulerAngles;
            r.y = Mathf.Round(GFMath.AngleClamp360(r.y + 90));
            obj.transform.rotation = Quaternion.Euler(r);
        }
    }


    [MenuItem("Tools/GF/Do something to selected objects")]
    private static void DoSomethingToSelectedObjects()
    {
        // reorder objects list by sibling index, as seen in the hierarchy
        GameObject[] objects = Selection.gameObjects;
        System.Array.Sort(objects, new UnitySiblingsSort());

        List<GameObject> processedObjects = new List<GameObject>();

        for (int io = 0; io < objects.Length; io++)
        {
            GameObject go = objects[io];

            // bool isAsset = AssetDatabase.Contains(go);
            // bool isPrefabInstance = PrefabUtility.GetPrefabType(go) == PrefabType.PrefabInstance;
            // bool isModelPrefabInstance = PrefabUtility.GetPrefabType(go) == PrefabType.ModelPrefabInstance;

            // nothing
            Debug.Log("  " + go.name);

            /*
            // Apply prefabs
            var instanceRoot = PrefabUtility.FindRootGameObjectWithSameParentPrefab(go);
            var targetPrefab = UnityEditor.PrefabUtility.GetPrefabParent(instanceRoot);
            if (instanceRoot != go)
            {
                Debug.Log(go.name + " not an instance root (skip)");
                continue;
            }
            Vector3 oldPos = go.transform.localPosition;
            PrefabUtility.ReplacePrefab(instanceRoot, targetPrefab, ReplacePrefabOptions.ConnectToPrefab);
            go.transform.localPosition = oldPos;
            */

            /*
            // Reposition
            Vector3 pos = go.transform.localPosition;
            pos.x = io * 2;
            go.transform.localPosition = pos;
            */

            /*
            // Reposition children
            for(int c=0; c<go.transform.GetChildCount(); c++)
            {
                Transform ct = go.transform.GetChild(c);
                ct.localPosition += new Vector3(0.0f, -0.06f, 0.0f);
            }
            */
        }

        if (processedObjects.Count > 0)
        {
            Selection.objects = processedObjects.ToArray();

            Debug.Log("  processed " + processedObjects.Count + " objects.");
        }

        EditorSceneManager.MarkAllScenesDirty();
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    private static List<GameObject> GetAllObjectsInScene(int prefabTypeMask, bool disabledOnly = false)
    {
        List<GameObject> list = new List<GameObject>();

        foreach (GameObject go in Resources.FindObjectsOfTypeAll(typeof(GameObject)) as GameObject[])
        {
            //if (hiddenOnly && go.hideFlags == HideFlags.None)
            if (disabledOnly && go.activeSelf)
                continue;

            PrefabType pt = PrefabUtility.GetPrefabType(go);
            int ptMask = (1 << (int)pt);
            if ((ptMask & prefabTypeMask) == 0)
                continue;

            list.Add(go);
        }

        return list;
    }

    [MenuItem("Tools/GF/Select/All scene prefabs")]
    private static void SelectAllScenePrefabs()
    {
        int mask = (1 << (int)PrefabType.PrefabInstance) | (1 << (int)PrefabType.ModelPrefabInstance);
        var list = GetAllObjectsInScene(mask);
        Selection.objects = list.ToArray();
        Debug.Log("  selected " + list.Count + " prefab objects.");
    }

    [MenuItem("Tools/GF/Select/All scene non-prefabs")]
    private static void SelectAllSceneNonPrefabs()
    {
        var list = GetAllObjectsInScene(0);
        Selection.objects = list.ToArray();
        Debug.Log("  selected " + list.Count + " non-prefab objects.");
    }

    [MenuItem("Tools/GF/Select/All scene missing prefabs")]
    private static void SelectAllSceneMissingPrefabs()
    {
        int mask = (1 << (int)PrefabType.MissingPrefabInstance);
        var list = GetAllObjectsInScene(mask);
        Selection.objects = list.ToArray();
        Debug.Log("  selected " + list.Count + " missing prefabs objects.");
    }

    [MenuItem("Tools/GF/Select/All scene disconnected prefabs")]
    private static void SelectAllSceneDisconnectedPrefabs()
    {
        int mask = (1 << (int)PrefabType.DisconnectedPrefabInstance) | (1 << (int)PrefabType.DisconnectedModelPrefabInstance);
        var list = GetAllObjectsInScene(mask);
        Selection.objects = list.ToArray();
        Debug.Log("  selected " + list.Count + " disconnected prefabs objects.");
    }

    [MenuItem("Tools/GF/Select/All scene disabled objects")]
    private static void SelectAllSceneDisabledObjects()
    {
        int mask = 0xffffff;
        var list = GetAllObjectsInScene(mask, true);
        Selection.objects = list.ToArray();
        foreach (var o in list)
            Debug.Log("- " + o.name);
        Debug.Log("  selected " + list.Count + " hidden objects.");
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    [MenuItem("Tools/GF/Fix/Fix instance names to prefab")]
    private static void FixInstanceNamesToPrefab()
    {
        Debug.Log("Fix instance to prefab names");

        if (Selection.gameObjects.Length == 0)
        {
            Debug.Log("  Select some objects first.");
            return;
        }
        Object[] objects = Selection.GetFiltered(typeof(UnityEngine.GameObject), SelectionMode.Assets);
        foreach (var go in objects)
        {
            PrefabType t = PrefabUtility.GetPrefabType(go);
            if (t == PrefabType.PrefabInstance)
            {
                Object prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
                if (prefab)
                    go.name = prefab.name;
            }
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    [MenuItem("Tools/GF/Select/Select mobile prefabs")]
    private static void SelectMobilePrefabs()
    {
        DoMobilePrefabs(false);
    }

    [MenuItem("Tools/GF/Fix/Fix mobile prefabs")]
    private static void FixMobilePrefabs()
    {
        DoMobilePrefabs(true);
    }

    private static void DoMobilePrefabs(bool fix)
    {
        List<GameObject> list = new List<GameObject>();
        List<GameObject> failedList = new List<GameObject>();

        int mask = (1 << (int)PrefabType.PrefabInstance) | (1 << (int)PrefabType.ModelPrefabInstance);
        var listAll = GetAllObjectsInScene(mask);

        for (int i = 0; i < listAll.Count; i++)
        {
            var go = listAll[i];
            // PrefabType pt = PrefabUtility.GetPrefabType(go);
            Object prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
            if (!prefab)
            {
                Debug.Log("! missing prefab for " + go.name);
                continue;
            }

            string path = AssetDatabase.GetAssetPath(prefab);
            if (!path.Contains("Mobile Assets"))
                continue;
            //if (File.Exists(path))
            // path = Path.GetDirectoryName(path);

            Debug.Log("- " + go.name + (go.transform.childCount > 0 ? " !!! PARENT !!!" : ""));

            if (fix)
            {
                string newPath = path.Replace("Mobile Assets", "Props");
                if (!File.Exists(newPath))
                {
                    Debug.Log("MISSING HD PREFAB");
                    failedList.Add(go);
                }
                else
                {
                    var newGo = ReplaceObjectWithPrefab(go, newPath, true);
                    if (newGo)
                    {
                        list.Add(newGo);
                    }
                    else
                    {
                        Debug.Log("FAILED TO REPLACE");
                        failedList.Add(go);
                    }
                }
            }
            else
            {
                list.Add(go);
            }
            // break;
        }

        if (fix)
        {
            if (failedList.Count > 0)
                Selection.objects = failedList.ToArray();
            else
                Selection.objects = list.ToArray();

            Debug.Log("  fixed " + list.Count + " mobile prefab objects.");
            Debug.Log("  failed " + failedList.Count + "!!!!");
        }
        else
        {
            Selection.objects = list.ToArray();
            Debug.Log("  selected " + list.Count + " mobile prefab objects.");
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private static string GetSelectedAssetsPath()
    {
        string path = "Assets";
        foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets))
        {
            path = AssetDatabase.GetAssetPath(obj);
            if (File.Exists(path))
            {
                path = Path.GetDirectoryName(path);
            }
            break;
        }
        return path;
    }

    private static GameObject CreateNewPrefab(GameObject obj, string localPath)
    {
        Object prefab = PrefabUtility.CreateEmptyPrefab(localPath);
        return PrefabUtility.ReplacePrefab(obj, prefab, ReplacePrefabOptions.ConnectToPrefab);
    }

    [MenuItem("Tools/GF/Create multiple prefabs at once")]
    private static void CreateMultiplePrefabsAtOnce()
    {
        Undo.RegisterCompleteObjectUndo(Selection.gameObjects, "create prefabs");

        string path = GetSelectedAssetsPath();
        Debug.Log("Create multiple prefabs at once in " + path);

        foreach (GameObject go in Selection.gameObjects)
        {
            Debug.Log("  " + go.name);
            CreateNewPrefab(go, path + "/" + go.name + ".prefab");
            AssetDatabase.Refresh();
        }
    }


    private static GameObject ReplaceObjectWithPrefab(GameObject go, string prefabPath, bool deleteObject = true)
    {
        Object prefabObject = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject));
        if(prefabObject == null)
        {
            Debug.Log("  SKIP: prefab not found: " + prefabPath);
            return null;
        }
        
        return ReplaceObjectWithPrefab(go, prefabObject, deleteObject);
    }

    private static GameObject ReplaceObjectWithPrefab(GameObject go, Object prefabObject, bool deleteObject = true)
    {
        GameObject newObject;

        newObject = PrefabUtility.InstantiatePrefab(prefabObject) as GameObject;
        if (newObject == null)
        {
            newObject = GameObject.Instantiate(prefabObject) as GameObject;
            if (newObject == null)
            {
                Debug.Log("  SKIP: failed to instantiate");
                return null;
            }
        }

        Undo.RegisterCreatedObjectUndo(newObject, "created prefab");
        newObject.name = prefabObject.name; //go.name;

        newObject.transform.SetParent(go.transform.parent, true);
        newObject.transform.localPosition = go.transform.localPosition;
        newObject.transform.localRotation = go.transform.localRotation;
        newObject.transform.localScale = go.transform.localScale;
        newObject.transform.SetSiblingIndex(go.transform.GetSiblingIndex() + 1);

        if (deleteObject)
            Undo.DestroyObjectImmediate(go);

        return newObject;
    }
}

/////////////////////////////////////////////////////////////////

public class UnitySiblingsSort : System.Collections.Generic.IComparer<GameObject>
{
    public int Compare(GameObject lhs, GameObject rhs)
    {
        if (lhs == rhs) return 0;
        if (lhs == null) return -1;
        if (rhs == null) return 1;
        return (lhs.transform.GetSiblingIndex() > rhs.transform.GetSiblingIndex()) ? 1 : -1;
    }
}

#endif