using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Experimental.SceneManagement;
using UnityEditor.SceneManagement;
using UnityEditorInternal;
using UnityEngine;

public static class EditorUtils {

    public static void OpenAllScene(Func<float, EditorBuildSettingsScene, GameObject[], bool> editFunc){
        var currentScenePath = EditorSceneManager.GetActiveScene().path;
        var scenes = EditorBuildSettings.scenes.Where(s => s.enabled).ToArray();
        for(int i = 0; i < scenes.Length; ++i){
            var scene = scenes[i];
            EditorSceneManager.OpenScene(scene.path);
            var gameObjectArray = Resources.FindObjectsOfTypeAll<GameObject>().Where(go => string.IsNullOrEmpty(AssetDatabase.GetAssetPath(go)) && go.hideFlags == HideFlags.None).ToArray();
            var isChanged = editFunc((float)i / scenes.Length, scene, gameObjectArray);
            if(isChanged){
                EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene());
            }
        }
        EditorSceneManager.OpenScene(currentScenePath);
    }

    public static GameObject[] GetAllAssets(){
        var pathArray = AssetDatabase.GetAllAssetPaths().Where(path => path.StartsWith("Assets/")).ToArray();
        return pathArray.Select(a => AssetDatabase.LoadAssetAtPath(a, typeof(GameObject)) as GameObject).Where(a => a != null).ToArray();
    }

    public static void FindAllPrefabPath(string folderPath, Action<float, string> action){
        var pathArray = Directory.GetFiles(folderPath, "*.prefab");
        for(int i = 0; i < pathArray.Length; ++i){
            action((float)i / pathArray.Length, pathArray[i].Replace(Application.dataPath, "Assets"));
        }
    }
    public static void FindAllChildrensPrefabPath(string folderPath, Action<float, string> action){
        FindAllPrefabPath(folderPath, action);
        foreach(var childPath in Directory.GetDirectories(folderPath)){
            FindAllChildrensPrefabPath(childPath, action);
        }
    }

    public static void EditPrefab(string folderPath, Func<GameObject, bool> editFunc){
        var prefab = PrefabUtility.LoadPrefabContents(folderPath);
        var isChanged = editFunc(prefab);
        if(isChanged){
            PrefabUtility.SaveAsPrefabAsset(prefab, folderPath);
        }
        PrefabUtility.UnloadPrefabContents(prefab);
    }

    public static void FindAllChildrensPrefab(Transform transform, Action<Transform> action){
        for(int i = transform.childCount - 1; i >= 0; --i){
            Transform child = transform.GetChild(i);
            if(PrefabUtility.IsAnyPrefabInstanceRoot(child.gameObject)){
                action(child);
            }else{
                FindAllChildrensPrefab(child, action);
            }
        }
    }

    public static void SwapPrefab(Transform target, GameObject swapPrefab){
        var to = (UnityEditor.PrefabUtility.InstantiatePrefab(swapPrefab) as GameObject).transform;
        to.parent = target.parent;
        to.localPosition = target.localPosition;
        to.localRotation = target.localRotation;
        to.localScale = target.localScale;
        GameObject.DestroyImmediate(target.gameObject);
    }

    public static string MigratePrefabToPrefabVariant(GameObject basePrefab, GameObject migrationPrefab){
        var basePath = AssetDatabase.GetAssetPath(basePrefab);
        var path = AssetDatabase.GetAssetPath(migrationPrefab).Replace(".prefab", " Variant.prefab");
        var instance = PrefabUtility.InstantiatePrefab(basePrefab) as GameObject;
        PrefabUtility.SaveAsPrefabAsset(instance, path);
        GameObject.DestroyImmediate(instance);

        var prefab = PrefabUtility.LoadPrefabContents(path);

        var result = Migrate(prefab.transform, migrationPrefab.transform);
        if(!result){
            FixField(prefab.transform, migrationPrefab.transform);

            PrefabUtility.SaveAsPrefabAsset(prefab, path);
        }
        PrefabUtility.UnloadPrefabContents(prefab);
        return path;
    }

    private static bool Migrate(Transform targetTransform, Transform migrationTransform){
        var targetChildCount = targetTransform.childCount;
        for(int i = 0; i < migrationTransform.childCount; ++i){
            var migrationChild = migrationTransform.GetChild(i);
            Transform targetChild;
            if(i < targetChildCount){
                targetChild = targetTransform.GetChild(i);
                if(migrationChild.name != targetChild.name){
                    var containsNameCount = ChildContaintsNameCount(targetTransform, migrationChild.name);
                    if(containsNameCount == 0){
                        targetChild = GameObject.Instantiate(migrationChild.gameObject, targetTransform).transform;
                        targetChild.name = migrationChild.name;
                        targetChild.SetSiblingIndex(migrationChild.GetSiblingIndex());
                        ++targetChildCount;
                    }else if(containsNameCount == 1){
                        for(int j = 0; j < targetChildCount; ++j){
                            if(targetTransform.GetChild(j).name == migrationChild.name){
                                targetChild = targetTransform.GetChild(j);
                                targetChild.SetSiblingIndex(migrationChild.GetSiblingIndex());
                                break;
                            }
                        }
                    }else{
                        Debug.LogError("同一階層に同じ名前のオブジェクトがある為中止します。");
                        return true;
                    }
                }
            }else{
                targetChild = GameObject.Instantiate(migrationChild.gameObject, targetTransform).transform;
                targetChild.name = migrationChild.name;
                ++targetChildCount;
            }
            CopyComponents(targetChild, migrationChild);
            var result = Migrate(targetChild, migrationChild);
            if(result){
                return true;
            }
            targetChild.gameObject.SetActive(migrationChild.gameObject.activeSelf);
        }
        for(int i = 0; i < targetChildCount; ++i){
            var toChild = targetTransform.GetChild(i);
            var containsNameCount = ChildContaintsNameCount(migrationTransform, toChild.name);
            if(containsNameCount == 0){
                toChild.gameObject.SetActive(false);
                Debug.LogWarning("Change disable " + toChild.GetPath());
            }
        }
        return false;
    }

    private static void FixField(Transform targetTransform, Transform migrationTransform){
        var targetChildCount = targetTransform.childCount;
        for(int i = 0; i < migrationTransform.childCount; ++i){
            var migrationChild = migrationTransform.GetChild(i);
            var targetChild = targetTransform.GetChild(i);
            if(migrationChild.name != targetChild.name){
                for(int j = 0; j < targetChildCount; ++j){
                    if(targetTransform.GetChild(j).name == migrationChild.name){
                        targetChild = targetTransform.GetChild(j);
                        break;
                    }
                }
            }
            FixField(targetChild, migrationChild);
            ProcessReflection(migrationChild, targetChild, targetTransform);
            migrationChild.FindComponent<ParticleSystem>(fromPS => {
                if(fromPS.subEmitters.enabled){
                    var fromSub = fromPS.subEmitters;
                    FindChildrensByPath(targetTransform, migrationChild.GetPath(), t => {
                        t.FindComponent<ParticleSystem>(toPS => {
                            var toSub = toPS.subEmitters;
                            for(int index = 0; index < fromSub.subEmittersCount; ++index){
                                var fromTarget = fromSub.GetSubEmitterSystem(index);
                                FindChildrensByPath(targetTransform, fromTarget.transform.GetPath(), toTarget => {
                                    toSub.SetSubEmitterSystem(index, toTarget.GetComponent<ParticleSystem>());
                                });
                            }
                        });
                    });
                }
            });
        }
    }
    private static int ChildContaintsNameCount(Transform transform, string name){
        var counter = 0;
        foreach(Transform child in transform){
            if(child.name == name){
                ++counter;
            }
        }
        return counter;
    }

    private static void CopyComponents(Transform to, Transform from){
        var components = from.gameObject.GetComponents<Component>();
        var targetComponents = to.gameObject.GetComponents<Component>();
        var currentComponentCount = new Dictionary<System.Type, int>();
        foreach(var component in components){
            var componentType = component.GetType();
            var componentCount = targetComponents.Count(c => c.GetType() == componentType);
            ComponentUtility.CopyComponent(component);
            if(componentCount == 0){
                ComponentUtility.PasteComponentAsNew(to.gameObject);
                }else if(componentCount == 1){
                    var targetComponent = targetComponents.First(c => c.GetType() == componentType);
                    ComponentUtility.PasteComponentValues(targetComponent);
                }else{
                    if(!currentComponentCount.ContainsKey(componentType)){
                        currentComponentCount.Add(componentType, 0);
                    }
                    var count = currentComponentCount[componentType];
                    var targetComponentsWithType = targetComponents.Where(c => c.GetType() == componentType);
                    if(count < targetComponentsWithType.Count()){
                        var targetComponent = targetComponents.Where(c => c.GetType() == componentType).ElementAt(count);
                        currentComponentCount[componentType] += 1;
                        ComponentUtility.PasteComponentValues(targetComponent);
                    }else{
                    ComponentUtility.PasteComponentAsNew(to.gameObject);
                }
            }
        }
        foreach(var component in targetComponents){
            var componentType = component.GetType();
            var componentCount = components.Count(c => c.GetType() == componentType);
            if(componentCount == 0){
                Debug.LogWarning("Deestroy component " + to.GetPath() + " " + componentType);
                GameObject.DestroyImmediate(component);
            }
        }
    }

    private static void ProcessReflection(Transform from, Transform to, Transform parent){
        var fromComponents = from.GetComponents<Component>();
        for(int i = 0; i < fromComponents.Length; ++i){
            var fromType = fromComponents[i].GetType();
            var toComponent = to.GetComponent(fromType);
            if(toComponent == null){
                continue;
            }
            MemberInfo[] members = fromType.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            foreach(var m in members){
                if(m.MemberType != MemberTypes.Field){
                    continue;
                }
                System.Type fieldType = ((FieldInfo)m).FieldType;
                if(fieldType == typeof(Transform) || fieldType == typeof(GameObject)){
                    string targetPath;
                    FieldInfo field = fromType.GetField(m.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                    if(fieldType == typeof(Transform)){
                        Transform t = (Transform)field.GetValue(fromComponents[i]);
                        if(t == null){
                            continue;
                        }
                        targetPath = t.GetPath();
                    }else if(fieldType == typeof(GameObject)){
                        GameObject obj = (GameObject)field.GetValue(fromComponents[i]);
                        if(obj == null){
                                continue;
                        }
                        targetPath = obj.transform.GetPath();
                    }else{
                        continue;
                    }
                    FindChildrensByPath(parent, targetPath, targetTransform => {
                        FieldInfo setField = fromType.GetField(m.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                        if(setField == null){
                            Debug.Log("setField is null " + m.Name);
                        }else{
                            if(fieldType == typeof(Transform)){
                                setField.SetValue(toComponent, targetTransform);
                            }else if(fieldType == typeof(GameObject)){
                                setField.SetValue(toComponent, targetTransform.gameObject);
                            }
                        }
                    });
                }else if(fieldType == typeof(Transform[])){
                    FieldInfo field = fromType.GetField(m.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                    Transform[] t = (Transform[])field.GetValue(fromComponents[i]);
                    if(t == null){
                        continue;
                    }
                    var targetPathArray = new string[t.Length];
                    for(int j = 0; j < t.Length; ++j){
                        if(t[j] == null){
                            targetPathArray[j] = null;
                        }else{
                            targetPathArray[j] = t[j].GetPath();
                        }
                    }
                    var newTransformArray = new Transform[t.Length];
                    for(int j = 0; j < t.Length; ++j){
                        if(targetPathArray[j] == null){
                            continue;
                        }
                        FindChildrensByPath(parent, targetPathArray[j], targetTransform => {
                            newTransformArray[j] = targetTransform;
                        });
                    }
                    FieldInfo setField = fromType.GetField(m.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                    if(setField == null){
                        Debug.Log("setField is null " + m.Name);
                    }else{
                        setField.SetValue(toComponent, newTransformArray);
                    }
                }else if(fieldType == typeof(GameObject[])){
                    FieldInfo field = fromType.GetField(m.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                    GameObject[] t = (GameObject[])field.GetValue(fromComponents[i]);
                    if(t == null){
                        continue;
                    }
                    var targetPathArray = new string[t.Length];
                    for(int j = 0; j < t.Length; ++j){
                        if(t[j] == null){
                            targetPathArray[j] = null;
                        }else{
                            targetPathArray[j] = t[j].transform.GetPath();
                        }
                    }
                    var newArray = new GameObject[t.Length];
                    for(int j = 0; j < t.Length; ++j){
                        if(targetPathArray[j] == null){
                            continue;
                        }
                        FindChildrensByPath(parent, targetPathArray[j], targetTransform => {
                            newArray[j] = targetTransform.gameObject;
                        });
                    }
                    FieldInfo setField = fromType.GetField(m.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                    if(setField == null){
                        Debug.Log("setField is null " + m.Name);
                    }else{
                        setField.SetValue(toComponent, newArray);
                    }
                }
            }
        }
    }

    private static bool FindChildrensByPath(Transform t, string path, Action<Transform> callback){
        for(int i = 0; i < t.childCount; ++i){
            Transform child = t.GetChild(i);
            if(child.GetPath().Replace(" Variant", "") == path){
                callback(child);
                return true;
            }
        }
        for(int i = 0; i < t.childCount; ++i){
            Transform child = t.GetChild(i);
            if(FindChildrensByPath(child, path, callback)){
                return true;
            }
        }
        return false;
    }
}
