#if XFORMS
namespace Caliburn.Micro.Core.Xamarin.Forms
#else
namespace Caliburn.Micro
#endif
{
    using System;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Collections.Generic;
#if !SILVERLIGHT
    using System.ComponentModel;
#endif
    /// 
    /// A service that is capable of properly binding values to a method's parameters and creating instances of .
    /// 
    public static class MessageBinder {
        /// 
        /// The special parameter values recognized by the message binder along with their resolvers.
        /// Parameter names are case insensitive so the specified names are unique and can be used with different case variations
        /// 
        public static readonly Dictionary> SpecialValues =
            new Dictionary>(StringComparer.OrdinalIgnoreCase)
            {
                {"$eventargs", c => c.EventArgs},
#if XFORMS
                {"$datacontext", c => c.Source.BindingContext},
                {"$bindingcontext", c => c.Source.BindingContext},
#else
                {"$datacontext", c => c.Source.DataContext},
#endif
                {"$source", c => c.Source},
                {"$executioncontext", c => c},
                {"$view", c => c.View}
            };
        /// 
        /// Custom converters used by the framework registered by destination type for which they will be selected.
        /// The converter is passed the existing value to convert and a "context" object.
        /// 
        public static readonly Dictionary> CustomConverters =
            new Dictionary>
            {
                {
                    typeof (DateTime), (value, context) => {
                        DateTime result;
                        DateTime.TryParse(value.ToString(), out result);
                        return result;
                    }
                }
            };
        /// 
        /// Determines the parameters that a method should be invoked with.
        /// 
        /// The action execution context.
        /// The parameters required to complete the invocation.
        /// The actual parameter values.
        public static object[] DetermineParameters(ActionExecutionContext context, ParameterInfo[] requiredParameters) {
            var providedValues = context.Message.Parameters.OfType().Select(x => x.Value).ToArray();
            var finalValues = new object[requiredParameters.Length];
            for (int i = 0; i < requiredParameters.Length; i++) {
                var parameterType = requiredParameters[i].ParameterType;
                var parameterValue = providedValues[i];
                var parameterAsString = parameterValue as string;
                if (parameterAsString != null)
                    finalValues[i] = CoerceValue(parameterType,
                        EvaluateParameter(parameterAsString, parameterType, context), context);
                else finalValues[i] = CoerceValue(parameterType, parameterValue, context);
            }
            return finalValues;
        }
        /// 
        /// Transforms the textual parameter into the actual parameter.
        /// 
        public static Func EvaluateParameter =
            (text, parameterType, context) => {
                Func resolver;
                return SpecialValues.TryGetValue(text, out resolver) ? resolver(context) : text;
            };
        /// 
        /// Coerces the provided value to the destination type.
        /// 
        /// The destination type.
        /// The provided value.
        /// An optional context value which can be used during conversion.
        /// The coerced value.
        public static object CoerceValue(Type destinationType, object providedValue, object context) {
            if (providedValue == null) {
                return GetDefaultValue(destinationType);
            }
            var providedType = providedValue.GetType();
            if (destinationType.IsAssignableFrom(providedType)) {
                return providedValue;
            }
            if (CustomConverters.ContainsKey(destinationType)) {
                return CustomConverters[destinationType](providedValue, context);
            }
            try {
#if !WinRT && !XFORMS
                var converter = TypeDescriptor.GetConverter(destinationType);
                if (converter.CanConvertFrom(providedType)) {
                    return converter.ConvertFrom(providedValue);
                }
                converter = TypeDescriptor.GetConverter(providedType);
                if (converter.CanConvertTo(destinationType)) {
                    return converter.ConvertTo(providedValue, destinationType);
                }
#endif
#if WinRT || XFORMS
                if (destinationType.GetTypeInfo().IsEnum) {
#else
                if (destinationType.IsEnum) {
#endif
                    var stringValue = providedValue as string;
                    if (stringValue != null) {
                        return Enum.Parse(destinationType, stringValue, true);
                    }
                    return Enum.ToObject(destinationType, providedValue);
                }
                if (typeof (Guid).IsAssignableFrom(destinationType)) {
                    var stringValue = providedValue as string;
                    if (stringValue != null) {
                        return new Guid(stringValue);
                    }
                }
            }
            catch {
                return GetDefaultValue(destinationType);
            }
            try {
                return Convert.ChangeType(providedValue, destinationType, CultureInfo.CurrentCulture);
            }
            catch {
                return GetDefaultValue(destinationType);
            }
        }
        /// 
        /// Gets the default value for a type.
        /// 
        /// The type.
        /// The default value.
        public static object GetDefaultValue(Type type) {
#if WinRT || XFORMS
            var typeInfo = type.GetTypeInfo();
            return typeInfo.IsClass || typeInfo.IsInterface ? null : System.Activator.CreateInstance(type);
#else
            return type.IsClass || type.IsInterface ? null : Activator.CreateInstance(type);
#endif
        }
    }
}