using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using HandyControl.Data;

namespace HandyControl.Controls
{
    public abstract class AdornerElement : Control, IDisposable
    {
        protected FrameworkElement ElementTarget { get; set; }

        public static readonly DependencyProperty TargetProperty = DependencyProperty.Register(
            "Target", typeof(FrameworkElement), typeof(AdornerElement), new PropertyMetadata(default(FrameworkElement), OnTargetChanged));

        private static void OnTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctl = (AdornerElement) d;
            ctl.OnTargetChanged(ctl.ElementTarget, false);
            ctl.OnTargetChanged((FrameworkElement) e.NewValue, true);
        }

        [Bindable(true), Category("Layout")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public FrameworkElement Target
        {
            get => (FrameworkElement) GetValue(TargetProperty);
            set => SetValue(TargetProperty, value);
        }

        public static readonly DependencyProperty InstanceProperty = DependencyProperty.RegisterAttached(
            "Instance", typeof(AdornerElement), typeof(AdornerElement), new PropertyMetadata(default(AdornerElement), OnInstanceChanged));

        private static void OnInstanceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (!(d is FrameworkElement target)) return;
            var element = (AdornerElement) e.NewValue;
            element.OnInstanceChanged(target);
        }

        protected virtual void OnInstanceChanged(FrameworkElement target) => Target = target;

        public static void SetInstance(DependencyObject element, AdornerElement value)
            => element.SetValue(InstanceProperty, value);

        public static AdornerElement GetInstance(DependencyObject element)
            => (AdornerElement) element.GetValue(InstanceProperty);

        public static readonly DependencyProperty IsInstanceProperty = DependencyProperty.RegisterAttached(
            "IsInstance", typeof(bool), typeof(AdornerElement), new PropertyMetadata(ValueBoxes.TrueBox));

        public static void SetIsInstance(DependencyObject element, bool value)
            => element.SetValue(IsInstanceProperty, ValueBoxes.BooleanBox(value));

        public static bool GetIsInstance(DependencyObject element)
            => (bool) element.GetValue(IsInstanceProperty);

        protected virtual void OnTargetChanged(FrameworkElement element, bool isNew)
        {
            if (element == null) return;

            if (!isNew)
            {
                element.Unloaded -= TargetElement_Unloaded;
                ElementTarget = null;
            }
            else
            {
                element.Unloaded += TargetElement_Unloaded;
                ElementTarget = element;
            }
        }

        private void TargetElement_Unloaded(object sender, RoutedEventArgs e)
        {
            if (sender is FrameworkElement element)
            {
                element.Unloaded -= TargetElement_Unloaded;
                Dispose();
            }
        }

        protected abstract void Dispose();

        void IDisposable.Dispose() => Dispose();
    }
}
