using UnityEngine;
using UnityEngine.PostProcessing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace UnityEditor.PostProcessing
{
    //[CanEditMultipleObjects]
    [CustomEditor(typeof(PostProcessingProfile))]
    public class PostProcessingInspector : Editor
    {
        static GUIContent s_PreviewTitle = new GUIContent("Monitors");

        PostProcessingProfile m_ConcreteTarget
        {
            get { return target as PostProcessingProfile; }
        }

        int m_CurrentMonitorID
        {
            get { return m_ConcreteTarget.monitors.currentMonitorID; }
            set { m_ConcreteTarget.monitors.currentMonitorID = value; }
        }

        List<PostProcessingMonitor> m_Monitors;
        GUIContent[] m_MonitorNames;
        Dictionary<PostProcessingModelEditor, PostProcessingModel> m_CustomEditors = new Dictionary<PostProcessingModelEditor, PostProcessingModel>();

        public bool IsInteractivePreviewOpened { get; private set; }

        void OnEnable()
        {
            if (target == null)
                return;

            // Aggregate custom post-fx editors
            var assembly = Assembly.GetAssembly(typeof(PostProcessingInspector));

            var editorTypes = assembly.GetTypes()
                .Where(x => x.IsDefined(typeof(PostProcessingModelEditorAttribute), false));

            var customEditors = new Dictionary<Type, PostProcessingModelEditor>();
            foreach (var editor in editorTypes)
            {
                var attr = (PostProcessingModelEditorAttribute)editor.GetCustomAttributes(typeof(PostProcessingModelEditorAttribute), false)[0];
                var effectType = attr.type;
                var alwaysEnabled = attr.alwaysEnabled;

                var editorInst = (PostProcessingModelEditor)Activator.CreateInstance(editor);
                editorInst.alwaysEnabled = alwaysEnabled;
                editorInst.profile = target as PostProcessingProfile;
                editorInst.inspector = this;
                customEditors.Add(effectType, editorInst);
            }

            // ... and corresponding models
            var baseType = target.GetType();
            var property = serializedObject.GetIterator();

            while (property.Next(true))
            {
                if (!property.hasChildren)
                    continue;

                var type = baseType;
                var srcObject = ReflectionUtils.GetFieldValueFromPath(serializedObject.targetObject, ref type, property.propertyPath);

                if (srcObject == null)
                    continue;

                PostProcessingModelEditor editor;
                if (customEditors.TryGetValue(type, out editor))
                {
                    var effect = (PostProcessingModel)srcObject;

                    if (editor.alwaysEnabled)
                        effect.enabled = editor.alwaysEnabled;

                    m_CustomEditors.Add(editor, effect);
                    editor.target = effect;
                    editor.serializedProperty = property.Copy();
                    editor.OnPreEnable();
                }
            }

            // Prepare monitors
            m_Monitors = new List<PostProcessingMonitor>();

            var monitors = new List<PostProcessingMonitor>
            {
                new HistogramMonitor(),
                new WaveformMonitor(),
                new ParadeMonitor(),
                new VectorscopeMonitor()
            };

            var monitorNames = new List<GUIContent>();

            foreach (var monitor in monitors)
            {
                if (monitor.IsSupported())
                {
                    monitor.Init(m_ConcreteTarget.monitors, this);
                    m_Monitors.Add(monitor);
                    monitorNames.Add(monitor.GetMonitorTitle());
                }
            }

            m_MonitorNames = monitorNames.ToArray();

            if (m_Monitors.Count > 0)
                m_ConcreteTarget.monitors.onFrameEndEditorOnly = OnFrameEnd;
        }

        void OnDisable()
        {
            if (m_CustomEditors != null)
            {
                foreach (var editor in m_CustomEditors.Keys)
                    editor.OnDisable();

                m_CustomEditors.Clear();
            }

            if (m_Monitors != null)
            {
                foreach (var monitor in m_Monitors)
                    monitor.Dispose();

                m_Monitors.Clear();
            }

            if (m_ConcreteTarget != null)
                m_ConcreteTarget.monitors.onFrameEndEditorOnly = null;
        }

        void OnFrameEnd(RenderTexture source)
        {
            if (!IsInteractivePreviewOpened)
                return;

            if (m_CurrentMonitorID < m_Monitors.Count)
                m_Monitors[m_CurrentMonitorID].OnFrameData(source);

            IsInteractivePreviewOpened = false;
        }

        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            // Handles undo/redo events first (before they get used by the editors' widgets)
            var e = Event.current;
            if (e.type == EventType.ValidateCommand && e.commandName == "UndoRedoPerformed")
            {
                foreach (var editor in m_CustomEditors)
                    editor.Value.OnValidate();
            }

            if (!m_ConcreteTarget.debugViews.IsModeActive(BuiltinDebugViewsModel.Mode.None))
                EditorGUILayout.HelpBox("A debug view is currently enabled. Changes done to an effect might not be visible.", MessageType.Info);

            foreach (var editor in m_CustomEditors)
            {
                EditorGUI.BeginChangeCheck();

                editor.Key.OnGUI();

                if (EditorGUI.EndChangeCheck())
                    editor.Value.OnValidate();
            }

            serializedObject.ApplyModifiedProperties();
        }

        public override GUIContent GetPreviewTitle()
        {
            return s_PreviewTitle;
        }

        public override bool HasPreviewGUI()
        {
            return GraphicsUtils.supportsDX11 && m_Monitors.Count > 0;
        }

        public override void OnPreviewSettings()
        {
            using (new EditorGUILayout.HorizontalScope())
            {
                if (m_CurrentMonitorID < m_Monitors.Count)
                    m_Monitors[m_CurrentMonitorID].OnMonitorSettings();

                GUILayout.Space(5);
                m_CurrentMonitorID = EditorGUILayout.Popup(m_CurrentMonitorID, m_MonitorNames, FxStyles.preDropdown, GUILayout.MaxWidth(100f));
            }
        }

        public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
        {
            IsInteractivePreviewOpened = true;

            if (m_CurrentMonitorID < m_Monitors.Count)
                m_Monitors[m_CurrentMonitorID].OnMonitorGUI(r);
        }
    }
}
