using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using HandyControl.Data;
using HandyControl.Interactivity;
using HandyControl.Tools;

namespace HandyControl.Controls
{
    [ContentProperty("Content")]
    public class Drawer : FrameworkElement
    {
        private Storyboard _storyboard;

        private AdornerContainer _container;

        private ContentControl _animationControl;

        private TranslateTransform _translateTransform;

        private double _animationLength;

        private string _animationPropertyName;

        private FrameworkElement _maskElement;

        private AdornerLayer _layer;

        private System.Windows.Window _window;

        private UIElement _windowContentElement;

        private Point _contentRenderTransformOrigin;

        static Drawer()
        {
            DataContextProperty.OverrideMetadata(typeof(Drawer), new FrameworkPropertyMetadata(DataContextPropertyChanged));
        }

        public Drawer()
        {
            Loaded += Drawer_Loaded;
            Unloaded += Drawer_Unloaded;
        }

        private void Drawer_Loaded(object sender, RoutedEventArgs e)
        {
            if (IsOpen)
            {
                OnIsOpenChanged(true);
            }
        }

        private void Drawer_Unloaded(object sender, RoutedEventArgs e)
        {
            Loaded -= Drawer_Loaded;

            if (_maskElement != null)
            {
                _maskElement.PreviewMouseLeftButtonDown -= MaskElement_PreviewMouseLeftButtonDown;
            }

            if (_storyboard != null)
            {
                _storyboard.Completed -= Storyboard_Completed;
            }

            _container = null;
        }

        private static void DataContextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
            ((Drawer) d).OnDataContextPropertyChanged(e);

        private void OnDataContextPropertyChanged(DependencyPropertyChangedEventArgs e) => UpdateDataContext(_animationControl, e.OldValue, e.NewValue);

        public static readonly RoutedEvent OpenedEvent =
            EventManager.RegisterRoutedEvent("Opened", RoutingStrategy.Bubble,
                typeof(RoutedEventHandler), typeof(Drawer));

        public event RoutedEventHandler Opened
        {
            add => AddHandler(OpenedEvent, value);
            remove => RemoveHandler(OpenedEvent, value);
        }

        public static readonly RoutedEvent ClosedEvent =
            EventManager.RegisterRoutedEvent("Closed", RoutingStrategy.Bubble,
                typeof(RoutedEventHandler), typeof(Drawer));

        public event RoutedEventHandler Closed
        {
            add => AddHandler(ClosedEvent, value);
            remove => RemoveHandler(ClosedEvent, value);
        }

        public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
            "IsOpen", typeof(bool), typeof(Drawer), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsOpenChanged));

        private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctl = (Drawer) d;
            ctl.OnIsOpenChanged((bool) e.NewValue);
        }

        public bool IsOpen
        {
            get => (bool) GetValue(IsOpenProperty);
            set => SetValue(IsOpenProperty, ValueBoxes.BooleanBox(value));
        }

        public static readonly DependencyProperty MaskCanCloseProperty = DependencyProperty.Register(
            "MaskCanClose", typeof(bool), typeof(Drawer), new PropertyMetadata(ValueBoxes.TrueBox));

        public bool MaskCanClose
        {
            get => (bool) GetValue(MaskCanCloseProperty);
            set => SetValue(MaskCanCloseProperty, ValueBoxes.BooleanBox(value));
        }

        public static readonly DependencyProperty ShowMaskProperty = DependencyProperty.Register(
            "ShowMask", typeof(bool), typeof(Drawer), new PropertyMetadata(ValueBoxes.TrueBox));

        public bool ShowMask
        {
            get => (bool) GetValue(ShowMaskProperty);
            set => SetValue(ShowMaskProperty, ValueBoxes.BooleanBox(value));
        }

        public static readonly DependencyProperty DockProperty = DependencyProperty.Register(
            "Dock", typeof(Dock), typeof(Drawer), new PropertyMetadata(default(Dock)));

        public Dock Dock
        {
            get => (Dock) GetValue(DockProperty);
            set => SetValue(DockProperty, value);
        }

        public static readonly DependencyProperty ShowModeProperty = DependencyProperty.Register(
            "ShowMode", typeof(DrawerShowMode), typeof(Drawer), new PropertyMetadata(default(DrawerShowMode)));

        public DrawerShowMode ShowMode
        {
            get => (DrawerShowMode) GetValue(ShowModeProperty);
            set => SetValue(ShowModeProperty, value);
        }

        public static readonly DependencyProperty MaskBrushProperty = DependencyProperty.Register(
            "MaskBrush", typeof(Brush), typeof(Drawer), new PropertyMetadata(default(Brush)));

        public Brush MaskBrush
        {
            get => (Brush) GetValue(MaskBrushProperty);
            set => SetValue(MaskBrushProperty, value);
        }

        public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
            "Content", typeof(object), typeof(Drawer), new PropertyMetadata(default(object)));

        public object Content
        {
            get => GetValue(ContentProperty);
            set => SetValue(ContentProperty, value);
        }

        private void CreateContainer()
        {
            _storyboard = new Storyboard();
            _storyboard.Completed += Storyboard_Completed;

            _translateTransform = new TranslateTransform();
            _animationControl = new ContentControl
            {
                Content = Content,
                RenderTransform = _translateTransform,
                DataContext = this
            };

            var panel = new SimplePanel
            {
                ClipToBounds = true
            };

            if (ShowMask)
            {
                _maskElement = new Border
                {
                    Background = MaskBrush,
                    Opacity = 0
                };
                _maskElement.PreviewMouseLeftButtonDown += MaskElement_PreviewMouseLeftButtonDown;
                panel.Children.Add(_maskElement);
            }

            _animationControl.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            var size = _animationControl.DesiredSize;

            switch (Dock)
            {
                case Dock.Left:
                    _animationControl.HorizontalAlignment = HorizontalAlignment.Left;
                    _animationControl.VerticalAlignment = VerticalAlignment.Stretch;
                    _translateTransform.X = -size.Width;
                    _animationLength = -size.Width;
                    _animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.X)";
                    break;
                case Dock.Top:
                    _animationControl.HorizontalAlignment = HorizontalAlignment.Stretch;
                    _animationControl.VerticalAlignment = VerticalAlignment.Top;
                    _translateTransform.Y = -size.Height;
                    _animationLength = -size.Height;
                    _animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.Y)";
                    break;
                case Dock.Right:
                    _animationControl.HorizontalAlignment = HorizontalAlignment.Right;
                    _animationControl.VerticalAlignment = VerticalAlignment.Stretch;
                    _translateTransform.X = size.Width;
                    _animationLength = size.Width;
                    _animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.X)";
                    break;
                case Dock.Bottom:
                    _animationControl.HorizontalAlignment = HorizontalAlignment.Stretch;
                    _animationControl.VerticalAlignment = VerticalAlignment.Bottom;
                    _translateTransform.Y = size.Height;
                    _animationLength = size.Height;
                    _animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.Y)";
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            _animationControl.DataContext = DataContext;
            _animationControl.CommandBindings.Clear();
            _animationControl.CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) => SetCurrentValue(IsOpenProperty, ValueBoxes.FalseBox)));
            panel.Children.Add(_animationControl);
            _container = new AdornerContainer(_layer)
            {
                Child = panel,
                ClipToBounds = true
            };
        }

        private void Storyboard_Completed(object sender, EventArgs e)
        {
            if (!IsOpen)
            {
                _windowContentElement.SetCurrentValue(RenderTransformOriginProperty, _contentRenderTransformOrigin);
                _layer.Remove(_container);
                RaiseEvent(new RoutedEventArgs(ClosedEvent, this));
            }
            else
            {
                RaiseEvent(new RoutedEventArgs(OpenedEvent, this));
            }
        }

        private void OnIsOpenChanged(bool isOpen)
        {
            if (Content == null || DesignerHelper.IsInDesignMode) return;

            _window = WindowHelper.GetActiveWindow();
            if (_window == null) return;

            _windowContentElement = _window.Content as UIElement;
            if (_windowContentElement == null) return;

            _contentRenderTransformOrigin = _windowContentElement.RenderTransformOrigin;

            var decorator = VisualHelper.GetChild<AdornerDecorator>(_window);
            _layer = decorator?.AdornerLayer;
            if (_layer == null) return;

            if (_container == null)
            {
                CreateContainer();
            }

            switch (ShowMode)
            {
                case DrawerShowMode.Push:
                    ShowByPush(isOpen);
                    break;
                case DrawerShowMode.Press:
                    _windowContentElement.SetCurrentValue(RenderTransformOriginProperty, new Point(0.5, 0.5));
                    ShowByPress(isOpen);
                    break;
            }

            if (isOpen)
            {
                if (_maskElement != null)
                {
                    var maskAnimation = AnimationHelper.CreateAnimation(1);
                    Storyboard.SetTarget(maskAnimation, _maskElement);
                    Storyboard.SetTargetProperty(maskAnimation, new PropertyPath(OpacityProperty.Name));
                    _storyboard.Children.Add(maskAnimation);
                }

                var drawerAnimation = AnimationHelper.CreateAnimation(0);
                Storyboard.SetTarget(drawerAnimation, _animationControl);
                Storyboard.SetTargetProperty(drawerAnimation, new PropertyPath(_animationPropertyName));
                _storyboard.Children.Add(drawerAnimation);
                _layer.Remove(_container);
                _layer.Add(_container);
            }
            else
            {
                if (_maskElement != null)
                {
                    var maskAnimation = AnimationHelper.CreateAnimation(0);
                    Storyboard.SetTarget(maskAnimation, _maskElement);
                    Storyboard.SetTargetProperty(maskAnimation, new PropertyPath(OpacityProperty.Name));
                    _storyboard.Children.Add(maskAnimation);
                }

                var drawerAnimation = AnimationHelper.CreateAnimation(_animationLength);
                Storyboard.SetTarget(drawerAnimation, _animationControl);
                Storyboard.SetTargetProperty(drawerAnimation, new PropertyPath(_animationPropertyName));
                _storyboard.Children.Add(drawerAnimation);
            }
            _storyboard.Begin();
        }

        private void ShowByPush(bool isOpen)
        {
            string animationPropertyName;

            switch (Dock)
            {
                case Dock.Left:
                case Dock.Right:
                    animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.X)";
                    _windowContentElement.RenderTransform = new TranslateTransform
                    {
                        X = isOpen ? 0 : -_animationLength
                    };
                    break;
                case Dock.Top:
                case Dock.Bottom:
                    animationPropertyName = "(UIElement.RenderTransform).(TranslateTransform.Y)";
                    _windowContentElement.RenderTransform = new TranslateTransform
                    {
                        Y = isOpen ? 0 : -_animationLength
                    };
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            var animation = isOpen
                ? AnimationHelper.CreateAnimation(-_animationLength)
                : AnimationHelper.CreateAnimation(0);
            Storyboard.SetTarget(animation, _windowContentElement);
            Storyboard.SetTargetProperty(animation, new PropertyPath(animationPropertyName));

            _storyboard.Children.Add(animation);
        }

        private void ShowByPress(bool isOpen)
        {
            _windowContentElement.RenderTransform = isOpen
                ? new ScaleTransform()
                : new ScaleTransform
                {
                    ScaleX = 0.9,
                    ScaleY = 0.9
                };

            var animationX = isOpen
                ? AnimationHelper.CreateAnimation(.9)
                : AnimationHelper.CreateAnimation(1);
            Storyboard.SetTarget(animationX, _windowContentElement);
            Storyboard.SetTargetProperty(animationX, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)"));

            _storyboard.Children.Add(animationX);

            var animationY = isOpen
                ? AnimationHelper.CreateAnimation(.9)
                : AnimationHelper.CreateAnimation(1);
            Storyboard.SetTarget(animationY, _windowContentElement);
            Storyboard.SetTargetProperty(animationY, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)"));

            _storyboard.Children.Add(animationY);
        }

        private void MaskElement_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (MaskCanClose)
            {
                SetCurrentValue(IsOpenProperty, ValueBoxes.FalseBox);
            }
        }

        private void UpdateDataContext(FrameworkElement target, object oldValue, object newValue)
        {
            if (target == null || BindingOperations.GetBindingExpression(target, DataContextProperty) != null) return;
            if (ReferenceEquals(this, target.DataContext) || Equals(oldValue, target.DataContext))
            {
                target.DataContext = newValue ?? this;
            }
        }
    }
}
