#if XFORMS
namespace Caliburn.Micro.Core.Xamarin.Forms
#else
namespace Caliburn.Micro
#endif 
{
#if WinRT
    using System.Linq;
    using Windows.UI.Xaml;
    using System.Reflection;
#elif XFORMS
    using UIElement = global::Xamarin.Forms.Element;
    using FrameworkElement = global::Xamarin.Forms.VisualElement;
    using DependencyProperty = global::Xamarin.Forms.BindableProperty;
    using DependencyObject = global::Xamarin.Forms.BindableObject;
#else
    using Caliburn.Micro.Core;
    using System.Windows;
#endif
    /// 
    ///   A host for action related attached properties.
    /// 
    public static class Action {
        static readonly ILog Log = LogManager.GetLog(typeof(Action));
        /// 
        ///   A property definition representing the target of an  . The DataContext of the element will be set to this instance.
        /// 
        public static readonly DependencyProperty TargetProperty =
            DependencyPropertyHelper.RegisterAttached(
                "Target",
                typeof(object),
                typeof(Action),
                null, 
                OnTargetChanged
                );
        /// 
        ///   A property definition representing the target of an  . The DataContext of the element is not set to this instance.
        /// 
        public static readonly DependencyProperty TargetWithoutContextProperty =
            DependencyPropertyHelper.RegisterAttached(
                "TargetWithoutContext",
                typeof(object),
                typeof(Action),
                null, 
                OnTargetWithoutContextChanged
                );
        /// 
        ///   Sets the target of the  .
        /// 
        ///  The element to attach the target to. 
        ///  The target for instances of  . 
        public static void SetTarget(DependencyObject d, object target) {
            d.SetValue(TargetProperty, target);
        }
        /// 
        ///   Gets the target for instances of  .
        /// 
        ///  The element to which the target is attached. 
        ///  The target for instances of  
        public static object GetTarget(DependencyObject d) {
            return d.GetValue(TargetProperty);
        }
        /// 
        ///   Sets the target of the  .
        /// 
        ///  The element to attach the target to. 
        ///  The target for instances of  . 
        /// 
        ///   The DataContext will not be set.
        /// 
        public static void SetTargetWithoutContext(DependencyObject d, object target) {
            d.SetValue(TargetWithoutContextProperty, target);
        }
        /// 
        ///   Gets the target for instances of  .
        /// 
        ///  The element to which the target is attached. 
        ///  The target for instances of  
        public static object GetTargetWithoutContext(DependencyObject d) {
            return d.GetValue(TargetWithoutContextProperty);
        }
        ///
        ///  Checks if the  -Target was set.
        ///
        /// DependencyObject to check 
        /// True if Target or TargetWithoutContext was set on  
        public static bool HasTargetSet(DependencyObject element) {
            if (GetTarget(element) != null || GetTargetWithoutContext(element) != null)
                return true;
#if XFORMS
            return false;
#else
            var frameworkElement = element as FrameworkElement;
            if (frameworkElement == null)
                return false;
            return ConventionManager.HasBinding(frameworkElement, TargetProperty)
                   || ConventionManager.HasBinding(frameworkElement, TargetWithoutContextProperty);
#endif
        }
#if !XFORMS
        ///
        ///  Uses the action pipeline to invoke the method.
        ///
        /// The object instance to invoke the method on. 
        /// The name of the method to invoke. 
        /// The view. 
        /// The source of the invocation. 
        /// The event args. 
        /// The method parameters. 
        public static void Invoke(object target, string methodName, DependencyObject view = null, FrameworkElement source = null, object eventArgs = null, object[] parameters = null) {
            var message = new ActionMessage {MethodName = methodName};
            var context = new ActionExecutionContext {
                Target = target,
#if WinRT
                Method = target.GetType().GetRuntimeMethods().Single(m => m.Name == methodName),
#else
                Method = target.GetType().GetMethod(methodName),
#endif
                Message = message,
                View = view,
                Source = source,
                EventArgs = eventArgs
            };
            if (parameters != null) {
                parameters.Apply(x => context.Message.Parameters.Add(x as Parameter ?? new Parameter { Value = x }));
            }
            ActionMessage.InvokeAction(context);
            // This is a bit of hack but keeps message being garbage collected
            Log.Info("Invoking action {0} on {1}.", message.MethodName, target);
        }
#endif
        static void OnTargetWithoutContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            SetTargetCore(e, d, false);
        }
        static void OnTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            SetTargetCore(e, d, true);
        }
        static void SetTargetCore(DependencyPropertyChangedEventArgs e, DependencyObject d, bool setContext) {
            if (e.NewValue == e.OldValue || (Execute.InDesignMode && e.NewValue is string)) {
                return;
            }
            var target = e.NewValue;
            var containerKey = e.NewValue as string;
            if (containerKey != null) {
                target = IoC.GetInstance(null, containerKey);
            }
#if XFORMS
            Log.Info("Attaching message handler {0} to {1}.", target, d);
            Message.SetHandler(d, target);
            if (setContext && d is FrameworkElement) {
                Log.Info("Setting DC of {0} to {1}.", d, target);
                ((FrameworkElement)d).BindingContext = target;
            }
#else
            if (setContext && d is FrameworkElement) {
                Log.Info("Setting DC of {0} to {1}.", d, target);
                ((FrameworkElement)d).DataContext = target;
            }
             Log.Info("Attaching message handler {0} to {1}.", target, d);
             Message.SetHandler(d, target);
#endif
            
        }
    }
}