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

namespace Fungus
{

	[CustomEditor(typeof(InvokeMethod))]
	public class InvokeMethodEditor : CommandEditor
	{
	    InvokeMethod targetMethod;

	    public override void DrawCommandGUI()
	    {
	        base.DrawCommandGUI();

	        targetMethod = target as InvokeMethod;

	        if (targetMethod == null || targetMethod.targetObject == null)
	            return;

	        SerializedObject objSerializedTarget = new SerializedObject(targetMethod);

	        string component = ShowComponents(objSerializedTarget, targetMethod.targetObject);

	        // show component methods if selected
	        if (!string.IsNullOrEmpty(component))
	        {
	            var method = ShowMethods(objSerializedTarget, targetMethod.targetObject, component);

	            // show method parameters if selected
	            if (method != null)
	            {
	                objSerializedTarget.ApplyModifiedProperties();
	                ShowParameters(objSerializedTarget, targetMethod.targetObject, method);
	                ShowReturnValue(objSerializedTarget, method);
	            }
	        }
	    }

	    private string ShowComponents(SerializedObject objTarget, GameObject gameObject)
	    {
	        var targetComponentAssemblyName = objTarget.FindProperty("targetComponentAssemblyName");
	        var targetComponentFullname = objTarget.FindProperty("targetComponentFullname");
	        var targetComponentText = objTarget.FindProperty("targetComponentText");
	        var objComponents = gameObject.GetComponents<Component>();
	        var objTypesAssemblynames = (from objComp in objComponents select objComp.GetType().AssemblyQualifiedName).ToList();
	        var objTypesName = (from objComp in objComponents select objComp.GetType().Name).ToList();

	        int index = objTypesAssemblynames.IndexOf(targetComponentAssemblyName.stringValue);

	        index = EditorGUILayout.Popup("Target Component", index, objTypesName.ToArray());

	        if (index != -1)
	        {
	            targetComponentAssemblyName.stringValue = objTypesAssemblynames[index];
	            targetComponentFullname.stringValue = objComponents.GetType().FullName;
	            targetComponentText.stringValue = objTypesName[index];
	        }
	        else
	        {
	            targetComponentAssemblyName.stringValue = null;
	        }

	        objTarget.ApplyModifiedProperties();

	        return targetComponentAssemblyName.stringValue;
	    }

	    private MethodInfo ShowMethods(SerializedObject objTarget, GameObject gameObject, string component)
	    {
	        MethodInfo result = null;

	        var targetMethodProp = objTarget.FindProperty("targetMethod");
	        var targetMethodTextProp = objTarget.FindProperty("targetMethodText");
	        var methodParametersProp = objTarget.FindProperty("methodParameters");
			var showInheritedProp = objTarget.FindProperty("showInherited");
	        var saveReturnValueProp = objTarget.FindProperty("saveReturnValue");
	        var returnValueKeyProp = objTarget.FindProperty("returnValueVariableKey");

	        var objComponent = gameObject.GetComponent(ReflectionHelper.GetType(component));
	        var bindingFlags = BindingFlags.Default | BindingFlags.Public | BindingFlags.Instance;

	        if (!showInheritedProp.boolValue)
	        {
	            bindingFlags |= BindingFlags.DeclaredOnly;
	        }

	        if (objComponent != null)
	        {
	            var objMethods = objComponent.GetType().GetMethods(bindingFlags);
	            var methods = (from objMethod in objMethods where !objMethod.IsSpecialName select objMethod).ToList(); // filter out the getter/setter methods
	            var methodText = (from objMethod in methods select objMethod.Name + FormatParameters(objMethod.GetParameters()) + ": " + objMethod.ReturnType.Name).ToList();
	            int index = methodText.IndexOf(targetMethodTextProp.stringValue);

	            index = EditorGUILayout.Popup("Target Method", index, methodText.ToArray());

				EditorGUILayout.PropertyField(showInheritedProp);

	            if (index != -1)
	            {
	                if (targetMethodTextProp.stringValue != methodText[index])
	                {
	                    // reset
	                    methodParametersProp.ClearArray();
	                    methodParametersProp.arraySize = methods[index].GetParameters().Length;

	                    saveReturnValueProp.boolValue = false;
	                    returnValueKeyProp.stringValue = null;
	                }

	                targetMethodTextProp.stringValue = methodText[index];
	                targetMethodProp.stringValue = methods[index].Name;

	                result = methods[index];
	            }
	            else
	            {
	                targetMethodTextProp.stringValue = null;
	                targetMethodProp.stringValue = null;
	            }

	            objTarget.ApplyModifiedProperties();
	        }

	        return result;
	    }

	    private void ShowParameters(SerializedObject objTarget, GameObject gameObject, MethodInfo method)
	    {
	        var methodParametersProp = objTarget.FindProperty("methodParameters");
	        var objParams = method.GetParameters();

	        if (objParams.Length > 0)
	        {
	            GUILayout.Space(20);
	            EditorGUILayout.LabelField("Parameters", EditorStyles.boldLabel);
	            EditorGUILayout.BeginVertical(EditorStyles.helpBox);

	            for (int i = 0; i < objParams.Length; i++)
	            {
	                var objParam = objParams[i];

	                GUILayout.BeginHorizontal();
	                string labelFormat = string.Format("{0}: {1}", objParam.ParameterType.Name, objParam.Name);

	                var objItemProp = methodParametersProp.GetArrayElementAtIndex(i);
	                var serObjValueProp = objItemProp.FindPropertyRelative("objValue");
	                var serVariableKeyProp = objItemProp.FindPropertyRelative("variableKey");
	                var serValueTypeAssemblynameProp = serObjValueProp.FindPropertyRelative("typeAssemblyname");
	                var serValueTypeFullnameProp = serObjValueProp.FindPropertyRelative("typeFullname");

	                serValueTypeAssemblynameProp.stringValue = objParam.ParameterType.AssemblyQualifiedName;
	                serValueTypeFullnameProp.stringValue = objParam.ParameterType.FullName;

	                bool isDrawn = true;

	                if (string.IsNullOrEmpty(serVariableKeyProp.stringValue))
	                {
	                    isDrawn = DrawTypedPropertyInput(labelFormat, serObjValueProp, objParam.ParameterType);
	                }

	                if (isDrawn)
	                {
	                    var vars = GetFungusVariablesByType(targetMethod.GetFlowchart().variables, objParam.ParameterType);
	                    var values = new string[] { "<Value>" };
	                    var displayValue = values.Concat(vars).ToList();

	                    int index = displayValue.IndexOf(serVariableKeyProp.stringValue);

	                    if (index == -1)
	                    {
	                        index = 0;
	                    }

	                    if (string.IsNullOrEmpty(serVariableKeyProp.stringValue))
	                    {
	                        index = EditorGUILayout.Popup(index, displayValue.ToArray(), GUILayout.MaxWidth(80));
	                    }
	                    else
	                    {
	                        index = EditorGUILayout.Popup(labelFormat, index, displayValue.ToArray());
	                    }

	                    if (index > 0)
	                    {
	                        serVariableKeyProp.stringValue = displayValue[index];
	                    }
	                    else
	                    {
	                        serVariableKeyProp.stringValue = null;
	                    }
	                }
	                else
	                {
	                    var style = EditorStyles.label;
	                    var prevColor = style.normal.textColor;
	                    style.normal.textColor = Color.red;
	                    EditorGUILayout.LabelField(new GUIContent(objParam.ParameterType.Name + " cannot be drawn, donĀ“t use this method in the flowchart."), style);
	                    style.normal.textColor = prevColor;
	                }

	                GUILayout.EndHorizontal();
	            }
	            EditorGUILayout.EndVertical();

	            objTarget.ApplyModifiedProperties();
	        }
	    }

	    private void ShowReturnValue(SerializedObject objTarget, MethodInfo method)
	    {
	        var saveReturnValueProp = objTarget.FindProperty("saveReturnValue");
	        var returnValueKeyProp = objTarget.FindProperty("returnValueVariableKey");
	        var returnValueTypeProp = objTarget.FindProperty("returnValueType");
	        var callModeProp = objTarget.FindProperty("callMode");

	        returnValueTypeProp.stringValue = method.ReturnType.FullName;

	        if (method.ReturnType == typeof(IEnumerator))
	        {
	            GUILayout.Space(20);          
	            EditorGUILayout.PropertyField(callModeProp);           
	        }
	        else if (method.ReturnType != typeof(void))
	        {
	            GUILayout.Space(20);
	            EditorGUILayout.LabelField("Return Value", EditorStyles.boldLabel);
	            EditorGUILayout.BeginVertical(EditorStyles.helpBox);
	            saveReturnValueProp.boolValue = EditorGUILayout.Toggle("Save return value", saveReturnValueProp.boolValue);

	            if (saveReturnValueProp.boolValue)
	            {
	                var vars = GetFungusVariablesByType(targetMethod.GetFlowchart().variables, method.ReturnType).ToList();
	                int index = vars.IndexOf(returnValueKeyProp.stringValue);
	                index = EditorGUILayout.Popup(method.ReturnType.Name, index, vars.ToArray());

	                if (index != -1)
	                {
	                    returnValueKeyProp.stringValue = vars[index];                    
	                }
	            }

	            EditorGUILayout.EndVertical();           
	        }
	        else
	        {
	            saveReturnValueProp.boolValue = false;
	        }

	        objTarget.ApplyModifiedProperties();
	    }

	    private bool DrawTypedPropertyInput(string label, SerializedProperty objProperty, Type type)
	    {
	        SerializedProperty objectValue = null;

	        if (type == typeof(int))
	        {
	            objectValue = objProperty.FindPropertyRelative("intValue");
	            objectValue.intValue = EditorGUILayout.IntField(new GUIContent(label), objectValue.intValue);

	            return true;
	        }
	        else if (type == typeof(bool))
	        {
	            objectValue = objProperty.FindPropertyRelative("boolValue");
	            objectValue.boolValue = EditorGUILayout.Toggle(new GUIContent(label), objectValue.boolValue);

	            return true;
	        }
	        else if (type == typeof(float))
	        {
	            objectValue = objProperty.FindPropertyRelative("floatValue");
	            objectValue.floatValue = EditorGUILayout.FloatField(new GUIContent(label), objectValue.floatValue);

	            return true;
	        }
	        else if (type == typeof(string))
	        {
	            objectValue = objProperty.FindPropertyRelative("stringValue");
	            objectValue.stringValue = EditorGUILayout.TextField(new GUIContent(label), objectValue.stringValue);

	            return true;
	        }
	        else if (type == typeof(Color))
	        {
	            objectValue = objProperty.FindPropertyRelative("colorValue");
	            objectValue.colorValue = EditorGUILayout.ColorField(new GUIContent(label), objectValue.colorValue);

	            return true;
	        }
	        else if (type == typeof(UnityEngine.GameObject))
	        {
	            objectValue = objProperty.FindPropertyRelative("gameObjectValue");
	            objectValue.objectReferenceValue = EditorGUILayout.ObjectField(new GUIContent(label), objectValue.objectReferenceValue, typeof(UnityEngine.GameObject), true);

	            return true;
	        }
	        else if (type == typeof(UnityEngine.Material))
	        {
	            objectValue = objProperty.FindPropertyRelative("materialValue");
	            objectValue.objectReferenceValue = EditorGUILayout.ObjectField(new GUIContent(label), objectValue.objectReferenceValue, typeof(UnityEngine.Material), true);

	            return true;
	        }
	        else if (type == typeof(UnityEngine.Sprite))
	        {
	            objectValue = objProperty.FindPropertyRelative("spriteValue");
	            objectValue.objectReferenceValue = EditorGUILayout.ObjectField(new GUIContent(label), objectValue.objectReferenceValue, typeof(UnityEngine.Sprite), true);

	            return true;
	        }
	        else if (type == typeof(UnityEngine.Texture))
	        {
	            objectValue = objProperty.FindPropertyRelative("textureValue");
	            objectValue.objectReferenceValue = EditorGUILayout.ObjectField(new GUIContent(label), objectValue.objectReferenceValue, typeof(UnityEngine.Texture), true);

	            return true;
	        }
	        else if (type == typeof(UnityEngine.Vector2))
	        {
	            objectValue = objProperty.FindPropertyRelative("vector2Value");
	            objectValue.vector2Value = EditorGUILayout.Vector2Field(new GUIContent(label), objectValue.vector2Value);

	            return true;
	        }
	        else if (type == typeof(UnityEngine.Vector3))
	        {
	            objectValue = objProperty.FindPropertyRelative("vector3Value");
	            objectValue.vector3Value = EditorGUILayout.Vector3Field(new GUIContent(label), objectValue.vector3Value);

	            return true;
	        }
	        else if (type.IsSubclassOf(typeof(UnityEngine.Object)))
	        {
	            objectValue = objProperty.FindPropertyRelative("objectValue");
	            objectValue.objectReferenceValue = EditorGUILayout.ObjectField(new GUIContent(label), objectValue.objectReferenceValue, type, true);

	            return true;
	        }
	        else if (type.IsEnum)
	        {
	            var enumNames = Enum.GetNames(type);
	            objectValue = objProperty.FindPropertyRelative("intValue");
	            objectValue.intValue = EditorGUILayout.Popup(label, objectValue.intValue, enumNames);

	            return true;
	        }

	        return false;
	    }

	    private string[] GetFungusVariablesByType(List<Variable> variables, Type type)
	    {
	        string[] result = new string[0];

	        if (type == typeof(int))
	        {
	            result = (from v in variables where v.GetType() == typeof(IntegerVariable) select v.key).ToArray();
	        }
	        else if (type == typeof(bool))
	        {
	            result = (from v in variables where v.GetType() == typeof(BooleanVariable) select v.key).ToArray();
	        }
	        else if (type == typeof(float))
	        {
	            result = (from v in variables where v.GetType() == typeof(FloatVariable) select v.key).ToArray();
	        }
	        else if (type == typeof(string))
	        {
	            result = (from v in variables where v.GetType() == typeof(StringVariable) select v.key).ToArray();
	        }
	        else if (type == typeof(Color))
	        {
	            result = (from v in variables where v.GetType() == typeof(ColorVariable) select v.key).ToArray();
	        }
	        else if (type == typeof(UnityEngine.GameObject))
	        {
	            result = (from v in variables where v.GetType() == typeof(GameObjectVariable) select v.key).ToArray();
	        }
	        else if (type == typeof(UnityEngine.Material))
	        {
	            result = (from v in variables where v.GetType() == typeof(MaterialVariable) select v.key).ToArray();
	        }
	        else if (type == typeof(UnityEngine.Sprite))
	        {
	            result = (from v in variables where v.GetType() == typeof(SpriteVariable) select v.key).ToArray();
	        }
	        else if (type == typeof(UnityEngine.Texture))
	        {
	            result = (from v in variables where v.GetType() == typeof(TextureVariable) select v.key).ToArray();
	        }
	        else if (type == typeof(UnityEngine.Vector2))
	        {
	            result = (from v in variables where v.GetType() == typeof(Vector2Variable) select v.key).ToArray();
	        }
	        else if (type == typeof(UnityEngine.Vector3))
	        {
	            result = (from v in variables where v.GetType() == typeof(Vector3Variable) select v.key).ToArray();
	        }
	        else if (type.IsSubclassOf(typeof(UnityEngine.Object)))
	        {
	            result = (from v in variables where v.GetType() == typeof(ObjectVariable) select v.key).ToArray();
	        }

	        return result;
	    }

	    private string FormatParameters(ParameterInfo[] paramInfo)
	    {
	        string result = " (";

	        for (int i = 0; i < paramInfo.Length; i++)
	        {
	            var pi = paramInfo[i];
	            result += pi.ParameterType.Name; // " arg" + (i + 1); 

	            if (i < paramInfo.Length - 1)
	            {
	                result += ", ";
	            }
	        }

	        return result + ")";
	    }

	    private object GetDefaultValue(Type t)
	    {
	        if (t.IsValueType)
	            return Activator.CreateInstance(t);

	        return null;
	    }
	}

}