using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
#if UNITY_5_3_OR_NEWER
using UnityEditor.SceneManagement;
#endif

namespace UnityTest
{
    [CustomEditor(typeof(AssertionComponent))]
    public class AssertionComponentEditor : Editor
    {
        private readonly DropDownControl<Type> m_ComparerDropDown = new DropDownControl<Type>();

        private readonly PropertyPathSelector m_ThisPathSelector = new PropertyPathSelector("Compare");
        private readonly PropertyPathSelector m_OtherPathSelector = new PropertyPathSelector("Compare to");

        #region GUI Contents
        private readonly GUIContent m_GUICheckAfterTimeGuiContent = new GUIContent("Check after (seconds)", "After how many seconds the assertion should be checked");
        private readonly GUIContent m_GUIRepeatCheckTimeGuiContent = new GUIContent("Repeat check", "Should the check be repeated.");
        private readonly GUIContent m_GUIRepeatEveryTimeGuiContent = new GUIContent("Frequency of repetitions", "How often should the check be done");
        private readonly GUIContent m_GUICheckAfterFramesGuiContent = new GUIContent("Check after (frames)", "After how many frames the assertion should be checked");
        private readonly GUIContent m_GUIRepeatCheckFrameGuiContent = new GUIContent("Repeat check", "Should the check be repeated.");
        #endregion

        private static List<Type> allComparersList = null;

        public AssertionComponentEditor()
        {
            m_ComparerDropDown.convertForButtonLabel = type => type.Name;
            m_ComparerDropDown.convertForGUIContent = type => type.Name;
            m_ComparerDropDown.ignoreConvertForGUIContent = types => false;
            m_ComparerDropDown.tooltip = "Comparer that will be used to compare values and determine the result of assertion.";
        }

        public override void OnInspectorGUI()
        {
            var script = (AssertionComponent)target;
            EditorGUILayout.BeginHorizontal();
            var obj = DrawComparerSelection(script);
#if UNITY_2017_3_OR_NEWER
            script.checkMethods = (CheckMethod)EditorGUILayout.EnumFlagsField(script.checkMethods,
                                                                             EditorStyles.popup,
                                                                             GUILayout.ExpandWidth(false));
#else
            script.checkMethods = (CheckMethod)EditorGUILayout.EnumMaskField(script.checkMethods,
                                                                             EditorStyles.popup,
                                                                             GUILayout.ExpandWidth(false));
#endif
            EditorGUILayout.EndHorizontal();

            if (script.IsCheckMethodSelected(CheckMethod.AfterPeriodOfTime))
            {
                DrawOptionsForAfterPeriodOfTime(script);
            }

            if (script.IsCheckMethodSelected(CheckMethod.Update))
            {
                DrawOptionsForOnUpdate(script);
            }

            if (obj)
            {
                EditorGUILayout.Space();

                m_ThisPathSelector.Draw(script.Action.go, script.Action,
                                        script.Action.thisPropertyPath, script.Action.GetAccepatbleTypesForA(),
                                        go =>
                                        {
                                            script.Action.go = go;
                                            AssertionExplorerWindow.Reload();
                                        },
                                        s =>
                                        {
                                            script.Action.thisPropertyPath = s;
                                            AssertionExplorerWindow.Reload();
                                        });

                EditorGUILayout.Space();

                DrawCustomFields(script);

                EditorGUILayout.Space();

                if (script.Action is ComparerBase)
                {
                    DrawCompareToType(script.Action as ComparerBase);
                }
            }
            if(GUI.changed)
			{
#if UNITY_5_3_OR_NEWER
                EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
#else
				EditorApplication.MarkSceneDirty();
#endif
			}
        }

        private void DrawOptionsForAfterPeriodOfTime(AssertionComponent script)
        {
            EditorGUILayout.Space();
            script.checkAfterTime = EditorGUILayout.FloatField(m_GUICheckAfterTimeGuiContent,
                                                               script.checkAfterTime);
            if (script.checkAfterTime < 0)
                script.checkAfterTime = 0;
            script.repeatCheckTime = EditorGUILayout.Toggle(m_GUIRepeatCheckTimeGuiContent,
                                                            script.repeatCheckTime);
            if (script.repeatCheckTime)
            {
                script.repeatEveryTime = EditorGUILayout.FloatField(m_GUIRepeatEveryTimeGuiContent,
                                                                    script.repeatEveryTime);
                if (script.repeatEveryTime < 0)
                    script.repeatEveryTime = 0;
            }
        }

        private void DrawOptionsForOnUpdate(AssertionComponent script)
        {
            EditorGUILayout.Space();
            script.checkAfterFrames = EditorGUILayout.IntField(m_GUICheckAfterFramesGuiContent,
                                                               script.checkAfterFrames);
            if (script.checkAfterFrames < 1)
                script.checkAfterFrames = 1;
            script.repeatCheckFrame = EditorGUILayout.Toggle(m_GUIRepeatCheckFrameGuiContent,
                                                             script.repeatCheckFrame);
            if (script.repeatCheckFrame)
            {
                script.repeatEveryFrame = EditorGUILayout.IntField(m_GUIRepeatEveryTimeGuiContent,
                                                                   script.repeatEveryFrame);
                if (script.repeatEveryFrame < 1)
                    script.repeatEveryFrame = 1;
            }
        }

        private void DrawCompareToType(ComparerBase comparer)
        {
            comparer.compareToType = (ComparerBase.CompareToType)EditorGUILayout.EnumPopup("Compare to type",
                                                                                           comparer.compareToType,
                                                                                           EditorStyles.popup);

            if (comparer.compareToType == ComparerBase.CompareToType.CompareToConstantValue)
            {
                try
                {
                    DrawConstCompareField(comparer);
                }
                catch (NotImplementedException)
                {
                    Debug.LogWarning("This comparer can't compare to static value");
                    comparer.compareToType = ComparerBase.CompareToType.CompareToObject;
                }
            }
            else if (comparer.compareToType == ComparerBase.CompareToType.CompareToObject)
            {
                DrawObjectCompareField(comparer);
            }
        }

        private void DrawObjectCompareField(ComparerBase comparer)
        {
            m_OtherPathSelector.Draw(comparer.other, comparer,
                                     comparer.otherPropertyPath, comparer.GetAccepatbleTypesForB(),
                                     go =>
                                     {
                                         comparer.other = go;
                                         AssertionExplorerWindow.Reload();
                                     },
                                     s =>
                                     {
                                         comparer.otherPropertyPath = s;
                                         AssertionExplorerWindow.Reload();
                                     }
                                     );
        }

        private void DrawConstCompareField(ComparerBase comparer)
        {
            if (comparer.ConstValue == null)
            {
                comparer.ConstValue = comparer.GetDefaultConstValue();
            }

            var so = new SerializedObject(comparer);
            var sp = so.FindProperty("constantValueGeneric");
            if (sp != null)
            {
                EditorGUILayout.PropertyField(sp, new GUIContent("Constant"), true);
                so.ApplyModifiedProperties();
            }
        }

        private bool DrawComparerSelection(AssertionComponent script)
        {
            if(allComparersList == null)
            {
                allComparersList = new List<Type>();
                var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
                foreach (var assembly in allAssemblies)
                {
                    var types = assembly.GetTypes();
                    allComparersList.AddRange(types.Where(type => type.IsSubclassOf(typeof(ActionBase)) && !type.IsAbstract));
                }
            }
            var allComparers = allComparersList.ToArray();

            if (script.Action == null)
                script.Action = (ActionBase)CreateInstance(allComparers.First());

            m_ComparerDropDown.Draw(script.Action.GetType(), allComparers,
                                    type =>
                                    {
                                        if (script.Action == null || script.Action.GetType().Name != type.Name)
                                        {
                                            script.Action = (ActionBase)CreateInstance(type);
                                            AssertionExplorerWindow.Reload();
                                        }
                                    });

            return script.Action != null;
        }

        private void DrawCustomFields(AssertionComponent script)
        {
            foreach (var prop in script.Action.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
            {
                var so = new SerializedObject(script.Action);
                var sp = so.FindProperty(prop.Name);
                if (sp != null)
                {
                    EditorGUILayout.PropertyField(sp, true);
                    so.ApplyModifiedProperties();
                }
            }
        }
    }
}