using UnityEngine;
using UnityEditor;

namespace TFramework
{
    public class PreviewInspector : Editor
    {
        private PreviewRenderUtility m_PreviewUtility;
        private GameObject m_PreviewInstance;
        private Bounds m_PreviewBounds;
        private Vector2 m_PreviewDir = new Vector2(120f, -20f);

        public Texture2D PreviewTexture { get; private set; }

        public override bool HasPreviewGUI() => true;

        private void AddSingleGO(GameObject go)
        {
#if UNITY_2017_1_OR_NEWER
            m_PreviewUtility.AddSingleGO(go);
#endif
        }

        public override void OnPreviewGUI(Rect r, GUIStyle background)
        {
            InitPreview();
            m_PreviewDir = Drag2D(m_PreviewDir, r);
            if (Event.current.type != EventType.Repaint) return;

            m_PreviewUtility.BeginPreview(r, background);
            Camera camera = m_PreviewUtility.camera;

            float num = Mathf.Max(m_PreviewBounds.extents.magnitude, 0.0001f);
            float num2 = num * 2.8f;
            Quaternion quaternion = Quaternion.Euler(-m_PreviewDir.y, -m_PreviewDir.x, 0f);
            Vector3 position = m_PreviewBounds.center - quaternion * (Vector3.forward * num2);
            camera.transform.position = position;
            camera.transform.rotation = quaternion;
            camera.nearClipPlane = num2 - num * 1.1f;
            camera.farClipPlane = num2 + num * 1.1f;

            m_PreviewUtility.lights[0].intensity = .7f;
            m_PreviewUtility.lights[0].transform.rotation = quaternion * Quaternion.Euler(40f, 40f, 0);
            m_PreviewUtility.lights[1].intensity = .7f;
            m_PreviewUtility.lights[1].transform.rotation = quaternion * Quaternion.Euler(340, 218, 177);

            m_PreviewUtility.ambientColor = new Color(.1f, .1f, .1f, 0);

            SetEnabledRecursive(m_PreviewInstance, true);
            camera.Render();
            SetTexture(camera, new Vector2Int(512, 512));
            SetEnabledRecursive(m_PreviewInstance, false);
            m_PreviewUtility.EndAndDrawPreview(r);
        }

        private void InitPreview()
        {
            if (m_PreviewUtility == null)
            {
                m_PreviewUtility = new PreviewRenderUtility(true);
                m_PreviewUtility.cameraFieldOfView = 60f;

                CreatePreviewInstances();
            }
            m_PreviewUtility.camera.cullingMask = 1 << kPreviewCullingLayer;
        }

        private void DestroyPreview()
        {
            m_PreviewUtility?.Cleanup();
            m_PreviewUtility = null;
        }

        private void DestroyPreviewInstances()
        {
            if (m_PreviewInstance != null)
            {
                DestroyImmediate(m_PreviewInstance);
            }
            m_PreviewInstance = null;
        }

        void OnDestroy()
        {
            DestroyPreviewInstances();
            DestroyPreview();
        }

        private void OnDisable()
        {
            DestroyPreviewInstances();
            DestroyPreview();
        }

        private void CreatePreviewInstances()
        {
            DestroyPreviewInstances();

            // Ҫ���Ƶ���Ϸ����
            m_PreviewInstance = Selection.activeGameObject;
            if (m_PreviewInstance == null)
            {
                return;
            }

            // ʵ��������
            m_PreviewInstance = Instantiate(m_PreviewInstance, Vector3.zero, Quaternion.identity) as GameObject;
            m_PreviewInstance.transform.localScale = 100 * Vector3.one;
            // �ݹ��������ر�־�Ͳ�
            InitInstantiatedPreviewRecursive(m_PreviewInstance);
            // �رն�����Ⱦ
            SetEnabledRecursive(m_PreviewInstance, false);

            m_PreviewBounds = new Bounds(m_PreviewInstance.transform.position, Vector3.zero);
            GetRenderableBoundsRecurse(ref m_PreviewBounds, m_PreviewInstance);
            AddSingleGO(m_PreviewInstance);
        }

        public static void GetRenderableBoundsRecurse(ref Bounds bounds, GameObject go)
        {
            MeshRenderer meshRenderer = go.GetComponent(typeof(MeshRenderer)) as MeshRenderer;
            MeshFilter meshFilter = go.GetComponent(typeof(MeshFilter)) as MeshFilter;
            if (meshRenderer && meshFilter && meshFilter.sharedMesh)
            {
                if (bounds.extents == Vector3.zero)
                {
                    bounds = meshRenderer.bounds;
                }
                else
                {
                    // ��չ��Χ�У����ð�Χ���ܹ�������һ����Χ��
                    bounds.Encapsulate(meshRenderer.bounds);
                }
            }
            SkinnedMeshRenderer skinnedMeshRenderer = go.GetComponent(typeof(SkinnedMeshRenderer)) as SkinnedMeshRenderer;
            if (skinnedMeshRenderer && skinnedMeshRenderer.sharedMesh)
            {
                if (bounds.extents == Vector3.zero)
                {
                    bounds = skinnedMeshRenderer.bounds;
                }
                else
                {
                    bounds.Encapsulate(skinnedMeshRenderer.bounds);
                }
            }
            foreach (Transform transform in go.transform)
            {
                GetRenderableBoundsRecurse(ref bounds, transform.gameObject);
            }
        }

        public static Vector2 Drag2D(Vector2 scrollPosition, Rect position)
        {
            int controlID = GUIUtility.GetControlID("Slider".GetHashCode(), FocusType.Passive);
            Event current = Event.current;
            switch (current.GetTypeForControl(controlID))
            {
                case EventType.MouseDown:
                    if (position.Contains(current.mousePosition) && position.width > 50f)
                    {
                        GUIUtility.hotControl = controlID;
                        current.Use();
                        // ���������϶�����Ļ��󣬴���һ�߳���
                        EditorGUIUtility.SetWantsMouseJumping(1);
                    }
                    break;
                case EventType.MouseUp:
                    if (GUIUtility.hotControl == controlID)
                    {
                        GUIUtility.hotControl = 0;
                    }
                    EditorGUIUtility.SetWantsMouseJumping(0);
                    break;
                case EventType.MouseDrag:
                    if (GUIUtility.hotControl == controlID)
                    {
                        // ��ס Shift ���󣬿��Լӿ���ת
                        scrollPosition -= current.delta * (float)((!current.shift) ? 1 : 3) / Mathf.Min(position.width, position.height) * 140f;
                        scrollPosition.y = Mathf.Clamp(scrollPosition.y, -90f, 90f);
                        current.Use();
                        GUI.changed = true;
                    }
                    break;
            }
            return scrollPosition;
        }


        // Ԥ��������Ļ��Ʋ� Camera.PreviewCullingLayer
        // Ϊ�˷�ֹ������ģ�����ͨ�������ȡ������ֱ��дֵ
        private const int kPreviewCullingLayer = 31;

        private static void InitInstantiatedPreviewRecursive(GameObject go)
        {
            go.hideFlags = HideFlags.HideAndDontSave;
            go.layer = kPreviewCullingLayer;
            foreach (Transform transform in go.transform)
            {
                InitInstantiatedPreviewRecursive(transform.gameObject);
            }
        }

        public static void SetEnabledRecursive(GameObject go, bool enabled)
        {
            Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>();
            for (int i = 0; i < componentsInChildren.Length; i++)
            {
                Renderer renderer = componentsInChildren[i];
                renderer.enabled = enabled;
            }
        }

        public void SetTexture(Camera camera, Vector2Int size)
        {
            if (camera == null) return;
            PreviewTexture = new Texture2D(size.x, size.y, TextureFormat.RGB24, false);
            var scrRenderTexture = new RenderTexture(PreviewTexture.width, PreviewTexture.height, 24);

            var camRenderTexture = camera.targetTexture;

            camera.targetTexture = scrRenderTexture;
            camera.Render();
            camera.targetTexture = camRenderTexture;

            RenderTexture.active = scrRenderTexture;
            PreviewTexture.ReadPixels(new Rect(0, 0, PreviewTexture.width, PreviewTexture.height), 0, 0);
            PreviewTexture.Apply();
        }
    }
}

