using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media.Animation;
using HandyControl.Data;
using HandyControl.Tools;

namespace HandyControl.Controls
{
    [TemplateVisualState(GroupName = PresentationGroup, Name = NormalState)]
    [TemplateVisualState(GroupName = PresentationGroup, Name = DefaultTransitionState)]
    [TemplatePart(Name = PreviousContentPresentationSitePartName, Type = typeof(ContentControl))]
    [TemplatePart(Name = CurrentContentPresentationSitePartName, Type = typeof(ContentControl))]
    [ContentProperty("Content")]
    public class TransitioningContentControl : Control
    {
        private Storyboard _currentTransition;

        public TransitioningContentControl()
        {
            if (!DesignerProperties.GetIsInDesignMode(this))
            {
                DefaultStyleKey = typeof(TransitioningContentControl);
            }
        }

        private Storyboard CurrentTransition
        {
            set
            {
                if (_currentTransition != null)
                    _currentTransition.Completed -= OnTransitionCompleted;

                _currentTransition = value;

                if (_currentTransition != null)
                    _currentTransition.Completed += OnTransitionCompleted;
            }
        }

        #region Events

        public event RoutedEventHandler TransitionCompleted;

        #endregion Events

        public override void OnApplyTemplate()
        {
            if (DesignerProperties.GetIsInDesignMode(this)) return;

            if (IsTransitioning)
                AbortTransition();

            base.OnApplyTemplate();

            PreviousContentPresentationSite =
                GetTemplateChild(PreviousContentPresentationSitePartName) as ContentPresenter;
            CurrentContentPresentationSite =
                GetTemplateChild(CurrentContentPresentationSitePartName) as ContentPresenter;

            if (CurrentContentPresentationSite != null)
                CurrentContentPresentationSite.Content = Content;

            var transition = GetStoryboard(Transition);
            CurrentTransition = transition;
            if (transition == null)
            {
                Transition = DefaultTransitionState;

                throw new ArgumentException("TransitioningContentControl_TransitionNotFound");
            }

            VisualStateManager.GoToState(this, NormalState, false);
            VisualStateManager.GoToState(this, Transition, true);
        }

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

        private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctl = (TransitioningContentControl) d;
            ctl.StartTransition(e.OldValue, e.NewValue);
        }

        public static readonly DependencyProperty ContentTemplateProperty = DependencyProperty.Register(
            "ContentTemplate", typeof(DataTemplate), typeof(TransitioningContentControl), new PropertyMetadata(default(DataTemplate)));

        public DataTemplate ContentTemplate
        {
            get => (DataTemplate) GetValue(ContentTemplateProperty);
            set => SetValue(ContentTemplateProperty, value);
        }

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

        [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "newContent", Justification = "Should be used in the future.")]
        private void StartTransition(object oldContent, object newContent)
        {
            if (CurrentContentPresentationSite != null && PreviousContentPresentationSite != null)
            {
                CurrentContentPresentationSite.Content = newContent;

                PreviousContentPresentationSite.Content = oldContent;

                if (!IsTransitioning || RestartTransitionOnContentChange)
                {
                    IsTransitioning = true;
                    VisualStateManager.GoToState(this, NormalState, false);
                    VisualStateManager.GoToState(this, Transition, true);
                }
            }
        }

        private void OnTransitionCompleted(object sender, EventArgs e)
        {
            AbortTransition();

            var handler = TransitionCompleted;
            handler?.Invoke(this, new RoutedEventArgs());
        }

        public void AbortTransition()
        {
            VisualStateManager.GoToState(this, NormalState, false);
            IsTransitioning = false;
            if (PreviousContentPresentationSite != null)
                PreviousContentPresentationSite.Content = null;
        }

        private Storyboard GetStoryboard(string newTransition)
        {
            var presentationGroup = VisualHelper.TryGetVisualStateGroup(this, PresentationGroup);
            Storyboard newStoryboard = null;
            if (presentationGroup != null)
                newStoryboard = presentationGroup.States
                    .OfType<VisualState>()
                    .Where(state => state.Name == newTransition)
                    .Select(state => state.Storyboard)
                    .FirstOrDefault();
            return newStoryboard;
        }

        #region Visual state names

        private const string PresentationGroup = "PresentationStates";

        private const string NormalState = "Normal";

        public const string DefaultTransitionState = "DefaultTransition";

        #endregion Visual state names

        #region Template part names

        internal const string PreviousContentPresentationSitePartName = "PreviousContentPresentationSite";

        internal const string CurrentContentPresentationSitePartName = "CurrentContentPresentationSite";

        #endregion Template part names

        #region TemplateParts

        private ContentPresenter CurrentContentPresentationSite { get; set; }

        private ContentPresenter PreviousContentPresentationSite { get; set; }

        #endregion TemplateParts

        #region public bool IsTransitioning

        private bool _allowIsTransitioningWrite;

        public bool IsTransitioning
        {
            get => (bool)GetValue(IsTransitioningProperty);
            private set
            {
                _allowIsTransitioningWrite = true;
                SetValue(IsTransitioningProperty, value);
                _allowIsTransitioningWrite = false;
            }
        }

        public static readonly DependencyProperty IsTransitioningProperty =
            DependencyProperty.Register(
                "IsTransitioning",
                typeof(bool),
                typeof(TransitioningContentControl),
                new PropertyMetadata(OnIsTransitioningPropertyChanged));

        private static void OnIsTransitioningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var source = (TransitioningContentControl)d;

            if (!source._allowIsTransitioningWrite)
            {
                source.IsTransitioning = (bool)e.OldValue;
                throw new InvalidOperationException("TransitiotioningContentControl_IsTransitioningReadOnly");
            }
        }

        #endregion public bool IsTransitioning

        #region public string Transition

        public string Transition
        {
            get => GetValue(TransitionProperty) as string;
            set => SetValue(TransitionProperty, value);
        }

        public static readonly DependencyProperty TransitionProperty =
            DependencyProperty.Register(
                "Transition",
                typeof(string),
                typeof(TransitioningContentControl),
                new PropertyMetadata(DefaultTransitionState, OnTransitionPropertyChanged));

        private static void OnTransitionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var source = (TransitioningContentControl)d;
            var oldTransition = e.OldValue as string;
            var newTransition = e.NewValue as string;

            if (source.IsTransitioning)
                source.AbortTransition();

            var newStoryboard = source.GetStoryboard(newTransition);

            if (newStoryboard == null)
                if (VisualHelper.TryGetVisualStateGroup(source, PresentationGroup) == null)
                {
                    source.CurrentTransition = null;
                }
                else
                {
                    source.SetValue(TransitionProperty, oldTransition);

                    throw new ArgumentException(
                        "TransitioningContentControl_TransitionNotFound");
                }
            else
                source.CurrentTransition = newStoryboard;
        }

        #endregion public string Transition

        #region public bool RestartTransitionOnContentChange

        public bool RestartTransitionOnContentChange
        {
            get => (bool)GetValue(RestartTransitionOnContentChangeProperty);
            set => SetValue(RestartTransitionOnContentChangeProperty, value);
        }

        public static readonly DependencyProperty RestartTransitionOnContentChangeProperty =
            DependencyProperty.Register(
                "RestartTransitionOnContentChange",
                typeof(bool),
                typeof(TransitioningContentControl),
                new PropertyMetadata(ValueBoxes.FalseBox, OnRestartTransitionOnContentChangePropertyChanged));

        private static void OnRestartTransitionOnContentChangePropertyChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            ((TransitioningContentControl)d).OnRestartTransitionOnContentChangeChanged((bool)e.OldValue,
                (bool)e.NewValue);
        }

        protected virtual void OnRestartTransitionOnContentChangeChanged(bool oldValue, bool newValue)
        {
        }

        #endregion public bool RestartTransitionOnContentChange
    }
}