using UnityEngine;
using System.Collections;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
using System;
using UnityEngine.Events;
using MarkerMetro.Unity.WinLegacy.Reflection;

namespace Fungus
{

	[CommandInfo("Scripting", 
	             "Invoke Method", 
	             "Invokes a method of a component via reflection. Supports passing multiple parameters and storing returned values in a Fungus variable.")]
	public class InvokeMethod : Command
	{
		[Tooltip("GameObject containing the component method to be invoked")]
	    public GameObject targetObject;

		[HideInInspector]
		[Tooltip("Name of assembly containing the target component")]
		public string targetComponentAssemblyName;

	    [HideInInspector]
		[Tooltip("Full name of the target component")]
		public string targetComponentFullname;

	    [HideInInspector]
		[Tooltip("Display name of the target component")]
		public string targetComponentText;

	    [HideInInspector]
		[Tooltip("Name of target method to invoke on the target component")]
		public string targetMethod;

	    [HideInInspector]
		[Tooltip("Display name of target method to invoke on the target component")]
		public string targetMethodText;

	    [HideInInspector]
		[Tooltip("List of parameters to pass to the invoked method")]
		public InvokeMethodParameter[] methodParameters;

	    [HideInInspector]
		[Tooltip("If true, store the return value in a flowchart variable of the same type.")]
		public bool saveReturnValue;

	    [HideInInspector]
		[Tooltip("Name of Fungus variable to store the return value in")]
		public string returnValueVariableKey;

	    [HideInInspector]
		[Tooltip("The type of the return value")]
		public string returnValueType;

	    [HideInInspector]
		[Tooltip("If true, list all inherited methods for the component")]
	    public bool showInherited;

	    [HideInInspector]
		[Tooltip("The coroutine call behavior for methods that return IEnumerator")]
	    public Fungus.Call.CallMode callMode;

	    protected Type componentType;
		protected Component objComponent;
		protected Type[] parameterTypes = null;
		protected MethodInfo objMethod;

	    protected virtual void Awake()
	    {
			if (componentType == null)
			{
				componentType = ReflectionHelper.GetType(targetComponentAssemblyName);
			}
			
			if (objComponent == null)
			{
				objComponent = targetObject.GetComponent(componentType);
			}
			
			if (parameterTypes == null)
			{
				parameterTypes = GetParameterTypes();
			}
			
			if (objMethod == null)
			{
				objMethod = UnityEvent.GetValidMethodInfo(objComponent, targetMethod, parameterTypes);
			}
	    }
	  
	    public override void OnEnter()
	    {
			try
			{
				if (targetObject == null || string.IsNullOrEmpty(targetComponentAssemblyName) || string.IsNullOrEmpty(targetMethod))
				{
					Continue();
					return;
				}
				
				if (returnValueType != "System.Collections.IEnumerator")
				{
					var objReturnValue = objMethod.Invoke(objComponent, GetParameterValues());
					
					if (saveReturnValue)
					{
						SetVariable(returnValueVariableKey, objReturnValue, returnValueType);
					}
					
					Continue();
				}
				else
				{
					StartCoroutine(ExecuteCoroutine());
					
					if (callMode == Call.CallMode.Continue)
					{
						Continue();
					}
					else if(callMode == Call.CallMode.Stop)
					{
                        StopParentBlock();
					}
				}
			}
			catch (System.Exception ex)
			{
				Debug.LogError("Error: " + ex.Message);
			}      
		}

	    protected virtual IEnumerator ExecuteCoroutine()
	    {
	        yield return StartCoroutine((IEnumerator)objMethod.Invoke(objComponent, GetParameterValues()));

	        if (callMode == Call.CallMode.WaitUntilFinished)
	        {
	            Continue();
	        }
	    }

		public override Color GetButtonColor()
		{
			return new Color32(235, 191, 217, 255);
		}

	    public override string GetSummary()
	    {
	        if (targetObject == null)
	        {
	            return "Error: targetObject is not assigned";
	        }

	        return targetObject.name + "." + targetComponentText + "." + targetMethodText;
	    }

		protected System.Type[] GetParameterTypes()
	    {
	        System.Type[] types = new System.Type[methodParameters.Length];

	        for (int i = 0; i < methodParameters.Length; i++)
	        {
	            var item = methodParameters[i];
	            var objType = ReflectionHelper.GetType(item.objValue.typeAssemblyname);

	            types[i] = objType;
	        }

	        return types;
	    }

		protected object[] GetParameterValues()
	    {
	        object[] values = new object[methodParameters.Length];
	        var flowChart = GetFlowchart();

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

	            if (string.IsNullOrEmpty(item.variableKey))
	            {
	                values[i] = item.objValue.GetValue();
	            }
	            else
	            {
	                object objValue = null;

	                switch (item.objValue.typeFullname)
	                {
	                    case "System.Int32":
	                        objValue = flowChart.GetIntegerVariable(item.variableKey);
	                        break;
	                    case "System.Boolean":
	                        objValue = flowChart.GetBooleanVariable(item.variableKey);
	                        break;
	                    case "System.Single":
	                        objValue = flowChart.GetFloatVariable(item.variableKey);
	                        break;
	                    case "System.String":
	                        objValue = flowChart.GetStringVariable(item.variableKey);
	                        break;
	                    case "UnityEngine.Color":
	                        var color = flowChart.GetVariable<ColorVariable>(item.variableKey);
	                        if (color != null)
	                            objValue = color.value;
	                        break;
	                    case "UnityEngine.GameObject":
	                        var gameObject = flowChart.GetVariable<GameObjectVariable>(item.variableKey);
	                        if (gameObject != null)
	                            objValue = gameObject.value;
	                        break;
	                    case "UnityEngine.Material":
	                        var material = flowChart.GetVariable<MaterialVariable>(item.variableKey);
	                        if (material != null)
	                            objValue = material.value;
	                        break;
	                    case "UnityEngine.Sprite":
	                        var sprite = flowChart.GetVariable<SpriteVariable>(item.variableKey);
	                        if (sprite != null)
	                            objValue = sprite.value;
	                        break;
	                    case "UnityEngine.Texture":
	                        var texture = flowChart.GetVariable<TextureVariable>(item.variableKey);
	                        if (texture != null)
	                            objValue = texture.value;
	                        break;
	                    case "UnityEngine.Vector2":
	                        var vector2 = flowChart.GetVariable<Vector2Variable>(item.variableKey);
	                        if (vector2 != null)
	                            objValue = vector2.value;
	                        break;
	                    case "UnityEngine.Vector3":
	                        var vector3 = flowChart.GetVariable<Vector3Variable>(item.variableKey);
	                        if (vector3 != null)
	                            objValue = vector3.value;
	                        break;
	                    default:
	                        var obj = flowChart.GetVariable<ObjectVariable>(item.variableKey);
	                        if (obj != null)
	                            objValue = obj.value;
	                        break;
	                }

	                values[i] = objValue;
	            }
	        }

	        return values;
	    }

		protected void SetVariable(string key, object value, string returnType)
	    {
	        var flowChart = GetFlowchart();

	        switch (returnType)
	        {
	            case "System.Int32":
	                flowChart.SetIntegerVariable(key, (int)value);
	                break;
	            case "System.Boolean":
	                flowChart.SetBooleanVariable(key, (bool)value);
	                break;
	            case "System.Single":
	                flowChart.SetFloatVariable(key, (float)value);
	                break;
	            case "System.String":
	                flowChart.SetStringVariable(key, (string)value);
	                break;
	            case "UnityEngine.Color":
	                flowChart.GetVariable<ColorVariable>(key).value = (UnityEngine.Color)value;
	                break;
	            case "UnityEngine.GameObject":
	                flowChart.GetVariable<GameObjectVariable>(key).value = (UnityEngine.GameObject)value;
	                break;
	            case "UnityEngine.Material":
	                flowChart.GetVariable<MaterialVariable>(key).value = (UnityEngine.Material)value;
	                break;
	            case "UnityEngine.Sprite":
	                flowChart.GetVariable<SpriteVariable>(key).value = (UnityEngine.Sprite)value;
	                break;
	            case "UnityEngine.Texture":
	                flowChart.GetVariable<TextureVariable>(key).value = (UnityEngine.Texture)value;
	                break;
	            case "UnityEngine.Vector2":
	                flowChart.GetVariable<Vector2Variable>(key).value = (UnityEngine.Vector2)value;
	                break;
	            case "UnityEngine.Vector3":
	                flowChart.GetVariable<Vector3Variable>(key).value = (UnityEngine.Vector3)value;
	                break;
	            default:
	                flowChart.GetVariable<ObjectVariable>(key).value = (UnityEngine.Object)value;
	                break;
	        }
	    }
	}

	[System.Serializable]
	public class InvokeMethodParameter
	{
	    [SerializeField]
	    public ObjectValue objValue;


	    [SerializeField]
	    public string variableKey;
	}

	[System.Serializable]
	public class ObjectValue
	{
	    public string typeAssemblyname;
	    public string typeFullname;

	    public int intValue;
	    public bool boolValue;
	    public float floatValue;
	    public string stringValue;

	    public Color colorValue;
	    public GameObject gameObjectValue;
	    public Material materialValue;
	    public UnityEngine.Object objectValue;
	    public Sprite spriteValue;
	    public Texture textureValue;
	    public Vector2 vector2Value;
	    public Vector3 vector3Value;

	    public object GetValue()
	    {
	        switch (typeFullname)
	        {
	            case "System.Int32":
	                return intValue;
	            case "System.Boolean":
	                return boolValue;
	            case "System.Single":
	                return floatValue;
	            case "System.String":
	                return stringValue;
	            case "UnityEngine.Color":
	                return colorValue;
	            case "UnityEngine.GameObject":
	                return gameObjectValue;
	            case "UnityEngine.Material":
	                return materialValue;
	            case "UnityEngine.Sprite":
	                return spriteValue;
	            case "UnityEngine.Texture":
	                return textureValue;
	            case "UnityEngine.Vector2":
	                return vector2Value;
	            case "UnityEngine.Vector3":
	                return vector3Value;
	            default:
	                var objType = ReflectionHelper.GetType(typeAssemblyname);

                    if (objType.IsSubclassOf(typeof(UnityEngine.Object)))
                    {
	                    return objectValue;
	                }
	                else if (objType.IsEnum())
	                    return System.Enum.ToObject(objType, intValue);

	                break;
	        }

	        return null;
	    }
	}

	public static class ReflectionHelper
	{
	    static Dictionary<string, System.Type> types = new Dictionary<string, System.Type>();

	    public static System.Type GetType(string typeName)
	    {
	        if (types.ContainsKey(typeName))
	            return types[typeName];

	        types[typeName] = System.Type.GetType(typeName);

	        return types[typeName];
	    }
	}

}