//----------------------------------------------
//            NGUI: Next-Gen UI kit
// Copyright © 2011-2015 Tasharen Entertainment
//----------------------------------------------

#if UNITY_EDITOR || !UNITY_FLASH
#define REFLECTION_SUPPORT
#endif

#if REFLECTION_SUPPORT
using System.Reflection;
using System.Diagnostics;
#endif

using UnityEngine;
using System;

/// <summary>
/// Reference to a specific field or property that can be set via inspector.
/// </summary>

[System.Serializable]
public class PropertyReference
{
	[SerializeField] Component mTarget;
	[SerializeField] string mName;

#if REFLECTION_SUPPORT
	FieldInfo mField = null;
	PropertyInfo mProperty = null;
#endif

	/// <summary>
	/// Event delegate's target object.
	/// </summary>

	public Component target
	{
		get
		{
			return mTarget;
		}
		set
		{
			mTarget = value;
#if REFLECTION_SUPPORT
			mProperty = null;
			mField = null;
#endif
		}
	}

	/// <summary>
	/// Event delegate's method name.
	/// </summary>

	public string name
	{
		get
		{
			return mName;
		}
		set
		{
			mName = value;
#if REFLECTION_SUPPORT
			mProperty = null;
			mField = null;
#endif
		}
	}

	/// <summary>
	/// Whether this delegate's values have been set.
	/// </summary>

	public bool isValid { get { return (mTarget != null && !string.IsNullOrEmpty(mName)); } }

	/// <summary>
	/// Whether the target script is actually enabled.
	/// </summary>

	public bool isEnabled
	{
		get
		{
			if (mTarget == null) return false;
			MonoBehaviour mb = (mTarget as MonoBehaviour);
			return (mb == null || mb.enabled);
		}
	}

	public PropertyReference () { }
	public PropertyReference (Component target, string fieldName)
	{
		mTarget = target;
		mName = fieldName;
	}

	/// <summary>
	/// Helper function that returns the property type.
	/// </summary>

	public Type GetPropertyType ()
	{
#if REFLECTION_SUPPORT
		if (mProperty == null && mField == null && isValid) Cache();
		if (mProperty != null) return mProperty.PropertyType;
		if (mField != null) return mField.FieldType;
#endif
#if UNITY_EDITOR || !UNITY_FLASH
		return typeof(void);
#else
		return null;
#endif
	}

	/// <summary>
	/// Equality operator.
	/// </summary>

	public override bool Equals (object obj)
	{
		if (obj == null)
		{
			return !isValid;
		}
		
		if (obj is PropertyReference)
		{
			PropertyReference pb = obj as PropertyReference;
			return (mTarget == pb.mTarget && string.Equals(mName, pb.mName));
		}
		return false;
	}

	static int s_Hash = "PropertyBinding".GetHashCode();

	/// <summary>
	/// Used in equality operators.
	/// </summary>

	public override int GetHashCode () { return s_Hash; }

	/// <summary>
	/// Set the delegate callback using the target and method names.
	/// </summary>

	public void Set (Component target, string methodName)
	{
		mTarget = target;
		mName = methodName;
	}

	/// <summary>
	/// Clear the event delegate.
	/// </summary>

	public void Clear ()
	{
		mTarget = null;
		mName = null;
	}

	/// <summary>
	/// Reset the cached references.
	/// </summary>

	public void Reset ()
	{
#if REFLECTION_SUPPORT
		mField = null;
		mProperty = null;
#endif
	}

	/// <summary>
	/// Convert the delegate to its string representation.
	/// </summary>

	public override string ToString () { return ToString(mTarget, name); }

	/// <summary>
	/// Convenience function that converts the specified component + property pair into its string representation.
	/// </summary>

	static public string ToString (Component comp, string property)
	{
		if (comp != null)
		{
			string typeName = comp.GetType().ToString();
			int period = typeName.LastIndexOf('.');
			if (period > 0) typeName = typeName.Substring(period + 1);

			if (!string.IsNullOrEmpty(property)) return typeName + "." + property;
			else return typeName + ".[property]";
		}
		return null;
	}

#if REFLECTION_SUPPORT
	/// <summary>
	/// Retrieve the property's value.
	/// </summary>

	[DebuggerHidden]
	[DebuggerStepThrough]
	public object Get ()
	{
		if (mProperty == null && mField == null && isValid) Cache();

		if (mProperty != null)
		{
			if (mProperty.CanRead)
				return mProperty.GetValue(mTarget, null);
		}
		else if (mField != null)
		{
			return mField.GetValue(mTarget);
		}
		return null;
	}

	/// <summary>
	/// Assign the bound property's value.
	/// </summary>

	[DebuggerHidden]
	[DebuggerStepThrough]
	public bool Set (object value)
	{
		if (mProperty == null && mField == null && isValid) Cache();
		if (mProperty == null && mField == null) return false;

		if (value == null)
		{
			try
			{
				if (mProperty != null)
				{
					if (mProperty.CanWrite)
					{
						mProperty.SetValue(mTarget, null, null);
						return true;
					}
				}
				else
				{
					mField.SetValue(mTarget, null);
					return true;
				}
			}
			catch (Exception) { return false; }
		}

		// Can we set the value?
		if (!Convert(ref value))
		{
			if (Application.isPlaying)
				UnityEngine.Debug.LogError("Unable to convert " + value.GetType() + " to " + GetPropertyType());
		}
		else if (mField != null)
		{
			mField.SetValue(mTarget, value);
			return true;
		}
		else if (mProperty.CanWrite)
		{
			mProperty.SetValue(mTarget, value, null);
			return true;
		}
		return false;
	}

	/// <summary>
	/// Cache the field or property.
	/// </summary>

	[DebuggerHidden]
	[DebuggerStepThrough]
	bool Cache ()
	{
		if (mTarget != null && !string.IsNullOrEmpty(mName))
		{
			Type type = mTarget.GetType();
#if NETFX_CORE
			mField = type.GetRuntimeField(mName);
			mProperty = type.GetRuntimeProperty(mName);
#else
			mField = type.GetField(mName);
			mProperty = type.GetProperty(mName);
#endif
		}
		else
		{
			mField = null;
			mProperty = null;
		}
		return (mField != null || mProperty != null);
	}

	/// <summary>
	/// Whether we can assign the property using the specified value.
	/// </summary>

	bool Convert (ref object value)
	{
		if (mTarget == null) return false;

		Type to = GetPropertyType();
		Type from;

		if (value == null)
		{
#if NETFX_CORE
			if (!to.GetTypeInfo().IsClass) return false;
#else
			if (!to.IsClass) return false;
#endif
			from = to;
		}
		else from = value.GetType();
		return Convert(ref value, from, to);
	}
#else // Everything below = no reflection support
	public object Get ()
	{
		Debug.LogError("Reflection is not supported on this platform");
		return null;
	}

	public bool Set (object value)
	{
		Debug.LogError("Reflection is not supported on this platform");
		return false;
	}

	bool Cache () { return false; }
	bool Convert (ref object value) { return false; }
#endif

	/// <summary>
	/// Whether we can convert one type to another for assignment purposes.
	/// </summary>

	static public bool Convert (Type from, Type to)
	{
		object temp = null;
		return Convert(ref temp, from, to);
	}

	/// <summary>
	/// Whether we can convert one type to another for assignment purposes.
	/// </summary>

	static public bool Convert (object value, Type to)
	{
		if (value == null)
		{
			value = null;
			return Convert(ref value, to, to);
		}
		return Convert(ref value, value.GetType(), to);
	}

	/// <summary>
	/// Whether we can convert one type to another for assignment purposes.
	/// </summary>

	static public bool Convert (ref object value, Type from, Type to)
	{
#if REFLECTION_SUPPORT
		// If the value can be assigned as-is, we're done
#if NETFX_CORE
		if (to.GetTypeInfo().IsAssignableFrom(from.GetTypeInfo())) return true;
#else
		if (to.IsAssignableFrom(from)) return true;
#endif

#else
		if (from == to) return true;
#endif
		// If the target type is a string, just convert the value
		if (to == typeof(string))
		{
			value = (value != null) ? value.ToString() : "null";
			return true;
		}

		// If the value is null we should not proceed further
		if (value == null) return false;

		if (to == typeof(int))
		{
			if (from == typeof(string))
			{
				int val;

				if (int.TryParse((string)value, out val))
				{
					value = val;
					return true;
				}
			}
			else if (from == typeof(float))
			{
				value = Mathf.RoundToInt((float)value);
				return true;
			}
		}
		else if (to == typeof(float))
		{
			if (from == typeof(string))
			{
				float val;

				if (float.TryParse((string)value, out val))
				{
					value = val;
					return true;
				}
			}
		}
		return false;
	}
}
