using System;
using UnityEngine;

namespace Jovian.InspectorTools {
    public class InterfaceComponentAttribute : PropertyAttribute {
        public readonly Type interfaceType;
        public readonly bool allowMismatch;

        public InterfaceComponentAttribute(Type interfaceType, bool allowMismatch = false) {
            this.interfaceType = interfaceType;
            this.allowMismatch = allowMismatch;
        }
    }
}

#if UNITY_EDITOR
namespace Jovian.InspectorTools.Internal {
    using UnityEditor;

    [CustomPropertyDrawer(typeof(InterfaceComponentAttribute))]
    public class InterfaceComponentAttributeDrawer : PropertyDrawer {

        private const float MESSAGE_HEIGHT = 40f;
        private const float FIELD_HEIGHT = 18f;

        private enum ValueImplementation {
            Null,
            Match,
            Mismatch
        }

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
            ValueImplementation valueImplementation = GetValueInterfaceImplementation(property, ((InterfaceComponentAttribute)attribute).interfaceType);
            return base.GetPropertyHeight(property, label) + (valueImplementation != ValueImplementation.Match ? MESSAGE_HEIGHT : 0f);
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
            InterfaceComponentAttribute interfaceAttribute = (InterfaceComponentAttribute)attribute;
            Type interfaceType = interfaceAttribute.interfaceType;
            bool allowMismatch = interfaceAttribute.allowMismatch;
            UnityEngine.Object value = property.objectReferenceValue;
            if(value is GameObject gameObject) {
                value = gameObject.GetComponent(interfaceType) as Component;
                if(value != null) {
                    property.objectReferenceValue = value;
                    property.serializedObject.ApplyModifiedPropertiesWithoutUndo();
                }
            }
            else if(value is Transform transform) {
                value = transform.GetComponent(interfaceType) as Component;
                if(value != null) {
                    property.objectReferenceValue = value;
                    property.serializedObject.ApplyModifiedPropertiesWithoutUndo();
                }
            }

            ValueImplementation valueImplementation = GetValueInterfaceImplementation(property, interfaceType);

            string message = null;
            if(valueImplementation == ValueImplementation.Null) {
                message = $"Component must implement '{interfaceType.Name}'";
            }
            else if(valueImplementation == ValueImplementation.Mismatch) {
                message = $"Component does not implement '{interfaceType.Name}'";

                if(allowMismatch == false) {
                    property.objectReferenceValue = null;
                    property.serializedObject.ApplyModifiedPropertiesWithoutUndo();
                }
            }

            if(string.IsNullOrEmpty(message) == false) {
                Rect boxPosition = new Rect(position.x, position.y, position.width, MESSAGE_HEIGHT - 2f);
                EditorGUI.HelpBox(boxPosition, message, valueImplementation == ValueImplementation.Mismatch ? MessageType.Warning : MessageType.Info);
            }

            Rect fieldPosition = new Rect(position.x, position.yMax - FIELD_HEIGHT, position.width, FIELD_HEIGHT);
            EditorGUI.PropertyField(fieldPosition, property, label);
        }

        private ValueImplementation GetValueInterfaceImplementation(SerializedProperty property, Type interfaceType) {
            UnityEngine.Object value = property.objectReferenceValue;
            if(value == null) {
                return ValueImplementation.Null;
            }
            else if(interfaceType.IsAssignableFrom(property.objectReferenceValue.GetType())) {
                return ValueImplementation.Match;
            }
            return ValueImplementation.Mismatch;
        }
    }
}
#endif