﻿#if UNITY_EDITOR
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;

namespace ModestTree
{
    public static class LodHelper
    {
        const string Lod0Suffix = "_LOD0";

        [MenuItem("GameObject/Create All LODGroup", false, 10)]
        public static void CreateAllLodGroups()
        {
            var allObjects = Object.FindObjectsOfType<GameObject>();
            foreach (var cur in allObjects)
            {
                if (PrefabUtility.IsPartOfAnyPrefab(cur))
                {
                    PrefabUtility.UnpackPrefabInstance(PrefabUtility.GetOutermostPrefabInstanceRoot(cur), PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
                }

                if (cur.name.EndsWith(Lod0Suffix))
                {
                    CreateLodGroup(cur);
                }
            }
        }

        [MenuItem("GameObject/Copy LODGroup distances to Other LODGroups", false, 10)]
        public static void CopyLodDistance()
        {
            if (!CopyLodDistanceCheck())
            {
                return;
            }

            var selected = (GameObject)Selection.activeObject;
            var name = selected.name;

            var originalGroup = selected.GetComponent<LODGroup>();
            var originalLods = originalGroup.GetLODs();

            // if originalLods contains multiple elements with the same screenRelativeTransitionHeight
            // Setting the LODs will cause an error
            var heights = new float[originalLods.Length];
            for (var i = 0; i < originalLods.Length; i++)
            {
                heights[i] = originalLods[i].screenRelativeTransitionHeight;
            }

            if (heights.Length != heights.Distinct().Count())
            {
                Debug.LogErrorFormat("Cannot apply LOD settings, selected LODGroup contains multiple levels with the same value");
                return;
            }

            Debug.LogFormat("Copying LODGroup distances from '{0}'", name);

            var lodGroups = GameObject.FindObjectsOfType<LODGroup>();
            foreach (var curGroup in lodGroups)
            {
                if (curGroup == originalGroup)
                {
                    continue;
                }

                var lods = curGroup.GetLODs();
                for (var i = 0; i < lods.Length && i < originalLods.Length; i++)
                {
                    lods[i].screenRelativeTransitionHeight = originalLods[i].screenRelativeTransitionHeight;
                }

                // If destination has more levels, we must remove the extra levels to avoid errors
                if (originalLods.Length < lods.Length)
                {
                    lods = lods.Take(originalLods.Length).ToArray();
                }

                curGroup.SetLODs(lods);
                curGroup.RecalculateBounds();

                EditorUtility.SetDirty(curGroup);
            }

            Debug.LogFormat("Successfully copied LODGroup distances from '{0}' to '{1}' other groups", name, lodGroups.Length);
        }

        static bool CopyLodDistanceCheck()
        {
            var selected = Selection.activeObject;
            if (selected == null)
            {
                Debug.LogError("Nothing selected, cannot copy LODGroup distances");
                return false;
            }

            var go = (GameObject)selected;

            if (go == null || go.GetComponent<LODGroup>() == null)
            {
                Debug.LogError("Selected object does not have an LODGroup");
                return false;
            }

            return true;
        }

        static void CreateLodGroup(GameObject go)
        {
            var originalName = go.name;
            var name = originalName.Remove(originalName.Length - Lod0Suffix.Length);
            var path = GetTransformPath(go).Substring(1);

            Debug.LogFormat("Creating LODGroup for '{0}'", name);

            var rootObject = new GameObject(name);
            var lodGroup = rootObject.AddComponent<LODGroup>();
            var lodObjects = new List<GameObject>();

            rootObject.transform.localScale = go.transform.localScale;
            rootObject.transform.SetPositionAndRotation(go.transform.position, go.transform.rotation);

            if (go.transform.parent != null)
            {
                rootObject.transform.SetParent(go.transform.parent);
            }

            go.transform.SetParent(rootObject.transform);
            lodObjects.Add(go);

            var lodIndex = 1;
            while (true) // Gather sibling LOD objects
            {
                var searchString = string.Format("{0}/{1}_LOD{2}", Path.GetDirectoryName(path), name, lodIndex);
                searchString = searchString.Replace('\\', '/');

                var nextLodObject = GameObject.Find(searchString);
                if (nextLodObject == null)
                {
                    break;
                }

                nextLodObject.transform.SetParent(rootObject.transform);
                lodObjects.Add(nextLodObject);
                lodIndex++;
            }

            var lods = new LOD[lodObjects.Count];
            for (var i = 0; i < lodObjects.Count; i++)
            {
                var renderers = new Renderer[1];
                renderers[0] = lodObjects[i].GetComponent<Renderer>();
                lods[i] = new LOD(1.0F / (i + 2), renderers);
            }

            lods[lods.Length - 1].screenRelativeTransitionHeight = 1f / 10f;

            lodGroup.SetLODs(lods);
            lodGroup.RecalculateBounds();

            EditorUtility.SetDirty(rootObject);

            Debug.LogFormat("Successfully created LODGroup for '{0}'", name);
        }

        static string GetTransformPath(GameObject go)
        {
            var builder = new StringBuilder();
            builder.AppendFormat("/{0}", go.transform.name);
            var current = go.transform;
            while (current.parent != null)
            {
                current = current.parent;
                builder.Insert(0, string.Format("/{0}", current.name));
            }

            return builder.ToString();
        }
    }
}
#endif