﻿#if UNITY_EDITOR

using System;
using UnityEditor;
using UnityEngine;
using System.Reflection;
using System.Linq;
using Arugula;

[CustomPropertyDrawer(typeof(EnumFlagAttribute))]
public class EnumFlagDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EnumFlagAttribute flagSettings = (EnumFlagAttribute)attribute;

        Enum targetEnum = null;

        if (property.propertyPath != property.name)
        {
            Type type = property.serializedObject.targetObject.GetType();
            FieldInfo parentField = type.GetField(property.propertyPath.Split('.')[0]);
            var obj = (object)parentField.GetValue(property.serializedObject.targetObject);

            targetEnum = (Enum)fieldInfo.GetValue(obj);
        }
        else
        {
            targetEnum = (Enum)fieldInfo.GetValue(property.serializedObject.targetObject);
        }


        string propName = flagSettings.name;
        if (string.IsNullOrEmpty(propName))
            propName = ObjectNames.NicifyVariableName(property.name);

        EditorGUI.BeginProperty(position, label, property);

        Enum enumNew = EditorGUI.EnumFlagsField(position, propName, targetEnum);
        property.intValue = (int)Convert.ChangeType(enumNew, targetEnum.GetType());
        EditorGUI.EndProperty();
    }
}

[CustomPropertyDrawer(typeof(MinMaxAttribute))]
public class MinMaxDrawer : PropertyDrawer
{
    const float numericWidth = 60;
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        if (property.propertyType != SerializedPropertyType.Vector2)
            EditorGUI.LabelField(position, label, "Error: must be Vector2");

        //float space = 2 * EditorGUIUtility.pixelsPerPoint;
        float space = 2;
        Vector2 val = property.vector2Value;
        float min = val.x;
        float max = val.y;

        EditorGUI.BeginProperty(position, label, property);
        MinMaxAttribute settings = (MinMaxAttribute)attribute;

        EditorGUI.PrefixLabel(position, label);

        Rect sliderPosition = position;
        sliderPosition.x += EditorGUIUtility.labelWidth + numericWidth + space;
        sliderPosition.width -= EditorGUIUtility.labelWidth + (numericWidth + space) * 2;

        EditorGUI.BeginChangeCheck();
        EditorGUI.MinMaxSlider(sliderPosition, ref min, ref max, settings.min, settings.max);
        if (EditorGUI.EndChangeCheck())
        {
            val.x = min.Snap(settings.snapping);
            val.y = max.Snap(settings.snapping);
            property.vector2Value = val;
            property.serializedObject.ApplyModifiedProperties();
        }

        Rect minPosition = position;
        minPosition.x += EditorGUIUtility.labelWidth;
        minPosition.width = numericWidth;

        EditorGUI.BeginChangeCheck();
        min = EditorGUI.FloatField(minPosition, min).Snap(settings.snapping);
        if (EditorGUI.EndChangeCheck())
        {
            val.x = Mathf.Max(min, settings.min);
            property.vector2Value = val;
            property.serializedObject.ApplyModifiedProperties();
        }

        Rect maxPosition = position;
        maxPosition.x += maxPosition.width - numericWidth;
        maxPosition.width = numericWidth;

        EditorGUI.BeginChangeCheck();
        max = EditorGUI.FloatField(maxPosition, max).Snap(settings.snapping);
        if (EditorGUI.EndChangeCheck())
        {
            val.y = Mathf.Min(max, settings.max);
            property.vector2Value = val;
            property.serializedObject.ApplyModifiedProperties();
        }
        EditorGUI.EndProperty();
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return EditorGUI.GetPropertyHeight(property);
        //return EditorGUIUtility.singleLineHeight;
    }
}

[CustomPropertyDrawer(typeof(DebugMethodAttribute))]
public class RunMethodDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var attr = (DebugMethodAttribute)attribute;

        if (attr.method != null)
        {
            if (!attr.showField)
            {
                if (GUI.Button(position, attr.method))
                {
                    RunMethodWithParam(property);
                }
            }
            else
            {
                Rect r = position;
                r.width = EditorGUIUtility.labelWidth / 2;
                if (GUI.Button(r, attr.method))
                {
                    RunMethodWithParam(property);
                }

                r = position;
                r.width -= EditorGUIUtility.labelWidth / 2;
                r.x += EditorGUIUtility.labelWidth / 2;

                EditorGUI.PropertyField(r, property);
            }
        }
        else
        {
            if (attr.showPicker)
            {
                Rect r = position;
                r.width = EditorGUIUtility.labelWidth;
                if (GUI.Button(r, "Invoke"))
                {
                    RunMethod(property);
                }

                position.width -= EditorGUIUtility.labelWidth;
                position.x += EditorGUIUtility.labelWidth;
                if (EditorGUI.DropdownButton(position, new GUIContent(property.stringValue == null ? "none" : property.stringValue), FocusType.Passive))
                {
                    ShowDropdown(property);
                }
            }
            else
            {
                if (GUI.Button(position, property.stringValue))
                {
                    RunMethod(property);
                }
            }
        }

    }

    void RunMethodWithParam(SerializedProperty p)
    {
        var attr = (DebugMethodAttribute)attribute;
        var methods = fieldInfo.DeclaringType.GetMethods(attr.bindingFlags);
        var method = methods.FirstOrDefault(x => x.Name == attr.method && x.GetParameters().Length > 0);

        if (method != null)
        {
            object obj = null;

            switch (p.propertyType)
            {
                case SerializedPropertyType.Generic:
                    break;
                case SerializedPropertyType.Integer:
                    obj = p.intValue;
                    break;
                case SerializedPropertyType.Boolean:
                    obj = p.boolValue;
                    break;
                case SerializedPropertyType.Float:
                    obj = p.floatValue;
                    break;
                case SerializedPropertyType.String:
                    obj = p.stringValue;
                    break;
                case SerializedPropertyType.Color:
                    obj = p.colorValue;
                    break;
                case SerializedPropertyType.ObjectReference:
                    obj = p.objectReferenceValue;
                    break;
                case SerializedPropertyType.LayerMask:
                    obj = p.intValue;
                    break;
                case SerializedPropertyType.Enum:
                    obj = p.enumValueIndex;
                    break;
                case SerializedPropertyType.Vector2:
                    obj = p.vector2Value;
                    break;
                case SerializedPropertyType.Vector3:
                    obj = p.vector3Value;
                    break;
                case SerializedPropertyType.Vector4:
                    obj = p.vector4Value;
                    break;
                case SerializedPropertyType.Rect:
                    obj = p.rectValue;
                    break;
                case SerializedPropertyType.ArraySize:
                    break;
                case SerializedPropertyType.Character:
                    break;
                case SerializedPropertyType.AnimationCurve:
                    obj = p.animationCurveValue;
                    break;
                case SerializedPropertyType.Bounds:
                    obj = p.boundsValue;
                    break;
                case SerializedPropertyType.Gradient:
                    obj = p.objectReferenceValue;
                    break;
                case SerializedPropertyType.Quaternion:
                    obj = p.quaternionValue;
                    break;
                case SerializedPropertyType.ExposedReference:
                    break;
                case SerializedPropertyType.FixedBufferSize:
                    break;
                case SerializedPropertyType.Vector2Int:
                    obj = p.vector2IntValue;
                    break;
                case SerializedPropertyType.Vector3Int:
                    obj = p.vector3IntValue;
                    break;
                case SerializedPropertyType.RectInt:
                    obj = p.rectIntValue;
                    break;
                case SerializedPropertyType.BoundsInt:
                    obj = p.boundsIntValue;
                    break;
                case SerializedPropertyType.ManagedReference:
                    break;
            }

            method.Invoke(p.serializedObject.targetObject, new object[] { obj });
        }
    }

    void RunMethod(SerializedProperty property)
    {
        var attr = (DebugMethodAttribute)attribute;

        if (property.stringValue == null || property.stringValue == "")
            return;

        var methods = fieldInfo.DeclaringType.GetMethods(attr.bindingFlags);

        var method = methods.FirstOrDefault(x => x.Name == property.stringValue);

        if (method != null)
        {
            method.Invoke(property.serializedObject.targetObject, null);
        }
    }

    void ShowDropdown(SerializedProperty property)
    {
        GenericMenu menu = new GenericMenu();

        var attr = (DebugMethodAttribute)attribute;

        var methods = fieldInfo.DeclaringType.GetMethods(attr.bindingFlags);
        foreach (var m in methods)
        {
            menu.AddItem(new GUIContent(m.Name), property.stringValue == m.Name, () => { property.stringValue = m.Name; property.serializedObject.ApplyModifiedProperties(); });
        }

        menu.ShowAsContext();
    }
}

[CustomPropertyDrawer(typeof(TimeStampAttribute))]
public class TimeStampDrawer : PropertyDrawer
{
    static DateTime Epoch = new DateTime(1970, 1, 1);
    static DateTime GetDT(SerializedProperty property)
    {
        return Epoch + TimeSpan.FromSeconds(property.doubleValue);
    }

    static double GetDouble(DateTime dt)
    {
        return (dt - Epoch).TotalSeconds;
    }
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        DateTime dt = GetDT(property);

        position = EditorGUI.PrefixLabel(position, new GUIContent(ObjectNames.NicifyVariableName(property.name)));
        var r = position;

        r.width = 38;
        if (EditorGUI.DropdownButton(r, new GUIContent(dt.Day.ToString()), FocusType.Keyboard))
            DayContext(property);
        r.x += r.width;

        r.width = 38;
        if (EditorGUI.DropdownButton(r, new GUIContent(dt.Month.ToString()), FocusType.Keyboard))
            MonthContext(property);
        r.x += r.width;

        r.width = 50;
        if (EditorGUI.DropdownButton(r, new GUIContent(dt.Year.ToString()), FocusType.Keyboard))
            YearContext(property);
        r.x += r.width + 5;

        r.width = 38;
        if (EditorGUI.DropdownButton(r, new GUIContent(dt.Hour.ToString().PadLeft(2, '0')), FocusType.Keyboard))
            HourContext(property);
        r.x += r.width;

        if (EditorGUI.DropdownButton(r, new GUIContent(dt.Minute.ToString().PadLeft(2, '0')), FocusType.Keyboard))
            MinuteContext(property);
        r.x += r.width;

        if (EditorGUI.DropdownButton(r, new GUIContent(dt.Second.ToString().PadLeft(2, '0')), FocusType.Keyboard))
            SecondContext(property);
        r.x += r.width;

        r.width = 110;
        r.x -= 65;
        EditorGUI.BeginChangeCheck();
        int ms = EditorGUI.IntField(r, dt.Millisecond);

        if (EditorGUI.EndChangeCheck())
        {
            ms = Mathf.Clamp(ms, 0, 999);
            if (ms != dt.Millisecond)
            {
                dt = dt.AddMilliseconds(ms - dt.Millisecond);
                property.doubleValue = GetDouble(dt);
                property.serializedObject.ApplyModifiedProperties();
            }
        }

        r.x += 115;
        r.width = 40;
        if(GUI.Button(r, "Now"))
        {
            dt = DateTime.Now;
            property.doubleValue = GetDouble(dt);
            property.serializedObject.ApplyModifiedProperties();
        }

        //        base.OnGUI(position, property, label);
    }

    void YearContext(SerializedProperty property)
    {
        GenericMenu menu = new GenericMenu();
        DateTime dt = GetDT(property);

        for (int i = DateTime.Now.Year + 10; i >= DateTime.Now.Year - 10; i--)
        {
            menu.AddItem(new GUIContent(i.ToString()), i == dt.Year, SelectYear, new object[] { property, i });
        }

        menu.ShowAsContext();
    }

    void SelectYear(object arg)
    {
        var args = (object[])arg;

        var p = (SerializedProperty)args[0];
        var i = (int)args[1];

        var dt = GetDT(p);

        dt = dt.AddYears(i - dt.Year);

        p.doubleValue = GetDouble(dt);
        p.serializedObject.ApplyModifiedProperties();
    }

    void MonthContext(SerializedProperty property)
    {
        GenericMenu menu = new GenericMenu();
        DateTime dt = GetDT(property);

        for (int i = 1; i <= 12; i++)
        {
            menu.AddItem(new GUIContent(i.ToString()), i == dt.Month, SelectMonth, new object[] { property, i });
        }

        menu.ShowAsContext();
    }

    void SelectMonth(object arg)
    {
        var args = (object[])arg;

        var p = (SerializedProperty)args[0];
        var i = (int)args[1];

        var dt = GetDT(p);

        dt = dt.AddMonths(i - dt.Month);

        p.doubleValue = GetDouble(dt);
        p.serializedObject.ApplyModifiedProperties();
    }

    void DayContext(SerializedProperty property)
    {
        GenericMenu menu = new GenericMenu();
        DateTime dt = GetDT(property);

        for (int i = 1; i <= DateTime.DaysInMonth(dt.Year, dt.Month); i++)
        {
            menu.AddItem(new GUIContent(i.ToString()), i == dt.Day, SelectDay, new object[] { property, i });
        }

        menu.ShowAsContext();
    }

    void SelectDay(object arg)
    {
        var args = (object[])arg;

        var p = (SerializedProperty)args[0];
        var i = (int)args[1];

        var dt = GetDT(p);

        dt = dt.AddDays(i - dt.Day);

        p.doubleValue = GetDouble(dt);
        p.serializedObject.ApplyModifiedProperties();
    }

    void HourContext(SerializedProperty property)
    {
        GenericMenu menu = new GenericMenu();
        DateTime dt = GetDT(property);

        for (int i = 0; i <= 23; i++)
        {
            menu.AddItem(new GUIContent(i.ToString()), i == dt.Hour, SelectHour, new object[] { property, i });
        }

        menu.ShowAsContext();
    }

    void SelectHour(object arg)
    {
        var args = (object[])arg;

        var p = (SerializedProperty)args[0];
        var i = (int)args[1];

        var dt = GetDT(p);

        dt = dt.AddHours(i - dt.Hour);

        p.doubleValue = GetDouble(dt);
        p.serializedObject.ApplyModifiedProperties();
    }

    void MinuteContext(SerializedProperty property)
    {
        GenericMenu menu = new GenericMenu();
        DateTime dt = GetDT(property);

        for (int i = 0; i <= 59; i++)
        {
            menu.AddItem(new GUIContent(i.ToString()), i == dt.Hour, SelectMinute, new object[] { property, i });
        }

        menu.ShowAsContext();
    }

    void SelectMinute(object arg)
    {
        var args = (object[])arg;

        var p = (SerializedProperty)args[0];
        var i = (int)args[1];

        var dt = GetDT(p);

        dt = dt.AddMinutes(i - dt.Minute);

        p.doubleValue = GetDouble(dt);
        p.serializedObject.ApplyModifiedProperties();
    }

    void SecondContext(SerializedProperty property)
    {
        GenericMenu menu = new GenericMenu();
        DateTime dt = GetDT(property);

        for (int i = 0; i <= 59; i++)
        {
            menu.AddItem(new GUIContent(i.ToString()), i == dt.Second, SelectSecond, new object[] { property, i });
        }

        menu.ShowAsContext();
    }

    void SelectSecond(object arg)
    {
        var args = (object[])arg;

        var p = (SerializedProperty)args[0];
        var i = (int)args[1];

        var dt = GetDT(p);

        dt = dt.AddSeconds(i - dt.Second);

        p.doubleValue = GetDouble(dt);
        p.serializedObject.ApplyModifiedProperties();
    }
}
#endif