using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace UnityTest
{
    public interface IListRenderer
    {
        void Render(IEnumerable<AssertionComponent> allAssertions, List<string> foldMarkers);
    }

    public abstract class AssertionListRenderer<T> : IListRenderer
    {
        private static class Styles
        {
            public static readonly GUIStyle redLabel;
            static Styles()
            {
                redLabel = new GUIStyle(EditorStyles.label);
                redLabel.normal.textColor = Color.red;
            }
        }

        public void Render(IEnumerable<AssertionComponent> allAssertions, List<string> foldMarkers)
        {
            foreach (var grouping in GroupResult(allAssertions))
            {
                var key = GetStringKey(grouping.Key);
                bool isFolded = foldMarkers.Contains(key);
                if (key != "")
                {
                    EditorGUILayout.BeginHorizontal();

                    EditorGUI.BeginChangeCheck();
                    isFolded = PrintFoldout(isFolded,
                                            grouping.Key);
                    if (EditorGUI.EndChangeCheck())
                    {
                        if (isFolded)
                            foldMarkers.Add(key);
                        else
                            foldMarkers.Remove(key);
                    }
                    EditorGUILayout.EndHorizontal();
                    if (isFolded)
                        continue;
                }
                foreach (var assertionComponent in grouping)
                {
                    EditorGUILayout.BeginVertical();
                    EditorGUILayout.BeginHorizontal();

                    if (key != "")
                        GUILayout.Space(15);

                    var assertionKey = assertionComponent.GetHashCode().ToString();
                    bool isDetailsFolded = foldMarkers.Contains(assertionKey);

                    EditorGUI.BeginChangeCheck();
                    if (GUILayout.Button("",
                                         EditorStyles.foldout,
                                         GUILayout.Width(15)))
                    {
                        isDetailsFolded = !isDetailsFolded;
                    }
                    if (EditorGUI.EndChangeCheck())
                    {
                        if (isDetailsFolded)
                            foldMarkers.Add(assertionKey);
                        else
                            foldMarkers.Remove(assertionKey);
                    }
                    PrintFoldedAssertionLine(assertionComponent);
                    EditorGUILayout.EndHorizontal();

                    if (isDetailsFolded)
                    {
                        EditorGUILayout.BeginHorizontal();
                        if (key != "")
                            GUILayout.Space(15);
                        PrintAssertionLineDetails(assertionComponent);
                        EditorGUILayout.EndHorizontal();
                    }
                    GUILayout.Box("", new[] {GUILayout.ExpandWidth(true), GUILayout.Height(1)});

                    EditorGUILayout.EndVertical();
                }
            }
        }

        protected abstract IEnumerable<IGrouping<T, AssertionComponent>> GroupResult(IEnumerable<AssertionComponent> assertionComponents);

        protected virtual string GetStringKey(T key)
        {
            return key.GetHashCode().ToString();
        }

        protected virtual bool PrintFoldout(bool isFolded, T key)
        {
            var content = new GUIContent(GetFoldoutDisplayName(key));
            var size = EditorStyles.foldout.CalcSize(content);

            var rect = GUILayoutUtility.GetRect(content,
                                                EditorStyles.foldout,
                                                GUILayout.MaxWidth(size.x));
            var res = EditorGUI.Foldout(rect,
                                        !isFolded,
                                        content,
                                        true);

            return !res;
        }

        protected virtual string GetFoldoutDisplayName(T key)
        {
            return key.ToString();
        }

        protected virtual void PrintFoldedAssertionLine(AssertionComponent assertionComponent)
        {
            EditorGUILayout.BeginHorizontal();

            EditorGUILayout.BeginVertical(GUILayout.MaxWidth(300));
            EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(300));
            PrintPath(assertionComponent.Action.go,
                      assertionComponent.Action.thisPropertyPath);
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.EndVertical();

            EditorGUILayout.BeginVertical(GUILayout.MaxWidth(250));
            var labelStr = assertionComponent.Action.GetType().Name;
            var labelStr2 = assertionComponent.Action.GetConfigurationDescription();
            if (labelStr2 != "")
                labelStr += "( " + labelStr2 + ")";
            EditorGUILayout.LabelField(labelStr);
            EditorGUILayout.EndVertical();

            if (assertionComponent.Action is ComparerBase)
            {
                var comparer = assertionComponent.Action as ComparerBase;

                var otherStrVal = "(no value selected)";
                EditorGUILayout.BeginVertical();
                EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(300));
                switch (comparer.compareToType)
                {
                    case ComparerBase.CompareToType.CompareToObject:
                        if (comparer.other != null)
                        {
                            PrintPath(comparer.other,
                                      comparer.otherPropertyPath);
                        }
                        else
                        {
                            EditorGUILayout.LabelField(otherStrVal,
                                                       Styles.redLabel);
                        }
                        break;
                    case ComparerBase.CompareToType.CompareToConstantValue:
                        otherStrVal = comparer.ConstValue.ToString();
                        EditorGUILayout.LabelField(otherStrVal);
                        break;
                    case ComparerBase.CompareToType.CompareToNull:
                        otherStrVal = "null";
                        EditorGUILayout.LabelField(otherStrVal);
                        break;
                }
                EditorGUILayout.EndHorizontal();
                EditorGUILayout.EndVertical();
            }
            else
            {
                EditorGUILayout.LabelField("");
            }
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.Space();
        }

        protected virtual void PrintAssertionLineDetails(AssertionComponent assertionComponent)
        {
            EditorGUILayout.BeginHorizontal();


            EditorGUILayout.BeginVertical(GUILayout.MaxWidth(320));
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Attached to",
                                       GUILayout.Width(70));
            var sss = EditorStyles.objectField.CalcSize(new GUIContent(assertionComponent.gameObject.name));
            EditorGUILayout.ObjectField(assertionComponent.gameObject,
                                        typeof(GameObject),
                                        true,
                                        GUILayout.Width(sss.x));
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.EndVertical();


            EditorGUILayout.BeginVertical(GUILayout.MaxWidth(250));
            EditorGUILayout.EnumMaskField(assertionComponent.checkMethods,
                                          EditorStyles.popup,
                                          GUILayout.MaxWidth(150));
            EditorGUILayout.EndVertical();


            EditorGUILayout.BeginVertical();
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Disabled",
                                       GUILayout.Width(55));
            assertionComponent.enabled = !EditorGUILayout.Toggle(!assertionComponent.enabled,
                                                                 GUILayout.Width(15));
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.EndVertical();

            EditorGUILayout.EndHorizontal();
        }

        private void PrintPath(GameObject go, string propertyPath)
        {
            string contentString = "";
            GUIStyle styleThisPath = EditorStyles.label;
            if (go != null)
            {
                var sss = EditorStyles.objectField.CalcSize(new GUIContent(go.name));
                EditorGUILayout.ObjectField(
                    go,
                    typeof(GameObject),
                    true,
                    GUILayout.Width(sss.x));

                if (!string.IsNullOrEmpty(propertyPath))
                    contentString = "." + propertyPath;
            }
            else
            {
                contentString = "(no value selected)";
                styleThisPath = Styles.redLabel;
            }

            var content = new GUIContent(contentString,
                                         contentString);
            var rect = GUILayoutUtility.GetRect(content,
                                                EditorStyles.label,
                                                GUILayout.MaxWidth(200));

            EditorGUI.LabelField(rect,
                                 content,
                                 styleThisPath);
        }
    }
}