using PanCardView.Enums;
using PanCardView.Extensions;
using PanCardView.Utility;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
using static PanCardView.Processors.Constants;
using static System.Math;

namespace PanCardView.Processors
{
    public class CarouselProcessor : IProcessor
    {
        public uint AnimationLength { get; set; } = 300;

        public Easing AnimationEasing { get; set; } = Easing.SinInOut;

        public double NoViewMaxShiftValue { get; set; } = 25;

        public double ScaleFactor { get; set; } = 1;

        public double OpacityFactor { get; set; } = 1;

        public double RotationFactor { get; set; } = 0;

        public double RotationXFactor { get; set; } = 0;

        public double RotationYFactor { get; set; } = 0;

        private readonly object _viewSizeChangedMapLocker = new object();

        private readonly Dictionary<View, ViewSizeInfoItem> _viewSizeChangedMap = new Dictionary<View, ViewSizeInfoItem>();

        public virtual void Init(CardsView cardsView, params ProcessorItem[] items)
        {
            foreach (var item in items)
            {
                if (item.IsFront)
                {
                    SetTranslationX(item.Views.FirstOrDefault(), 0, cardsView, true, true);
                    continue;
                }
                SetTranslationX(item.Views.FirstOrDefault(), Sign((int)item.Direction) * cardsView.GetSize(), cardsView, false, false);
            }
        }

        public virtual void Clean(CardsView cardsView, params ProcessorItem[] items)
        {
            foreach (var view in items.SelectMany(x => x.Views).Where(x => x != null) ?? Enumerable.Empty<View>())
            {
                SetTranslationX(view, cardsView.GetSize(), cardsView, false, false, true);
            }
        }

        public virtual void Change(CardsView cardsView, double value, params ProcessorItem[] items)
        {
            foreach (var item in items)
            {
                var view = item.Views.FirstOrDefault();
                var inactiveView = item.InactiveViews.FirstOrDefault();

                if (view != null)
                {
                    view.IsVisible = true;
                }

                if (inactiveView != null && inactiveView != view)
                {
                    inactiveView.IsVisible = false;
                }

                if (item.IsFront)
                {
                    if (Abs(value) > cardsView.GetSize() || (item.Direction == AnimationDirection.Prev && value < 0) || (item.Direction == AnimationDirection.Next && value > 0))
                    {
                        continue;
                    }

                    if (item.Direction == AnimationDirection.Null)
                    {
                        value = Sign(value) * Min(Abs(value / 4), NoViewMaxShiftValue);
                    }

                    SetTranslationX(view, value, cardsView, true);
                    continue;
                }

                if (item.Direction == AnimationDirection.Null)
                {
                    continue;
                }

                value = Sign((int)item.Direction) * cardsView.GetSize() + value;
                if (Abs(value) > cardsView.GetSize() || (item.Direction == AnimationDirection.Prev && value > 0) || (item.Direction == AnimationDirection.Next && value < 0))
                {
                    continue;
                }
                SetTranslationX(view, value, cardsView, false);
            }
        }

        public virtual Task Proceed(CardsView cardsView, params ProcessorItem[] items)
        {
            var animation = new AnimationWrapper();
            var animLength = AnimationLength;
            foreach (var item in items)
            {
                var view = item.Views.FirstOrDefault();
                if (view == null)
                {
                    continue;
                }

                if (item.IsFront)
                {
                    var animTimePercent = 1 - (cardsView.GetSize() - Abs(GetTranslationX(view, cardsView))) / cardsView.GetSize();
                    animLength = Max((uint)(AnimationLength * animTimePercent), 1);
                    animation.Add(0, 1, new AnimationWrapper(v => SetTranslationX(view, v, cardsView, true), GetTranslationX(view, cardsView), 0));
                    continue;
                }

                animation.Add(0, 1, new AnimationWrapper(v => SetTranslationX(view, v, cardsView, false), GetTranslationX(view, cardsView), -Sign((int)item.Direction) * cardsView.GetSize()));
            }

            return animation.Commit(cardsView, Path.GetRandomFileName(), 16, animLength, AnimationEasing);
        }

        public virtual Task Reset(CardsView cardsView, params ProcessorItem[] items)
        {
            var animation = new AnimationWrapper();
            var animLength = AnimationLength;
            foreach (var item in items)
            {
                var view = item.Views.FirstOrDefault();
                if (view == null)
                {
                    continue;
                }

                if (item.IsFront)
                {
                    var animTimePercent = 1 - (cardsView.GetSize() - Abs(GetTranslationX(view, cardsView))) / cardsView.GetSize();
                    animLength = Max((uint)(AnimationLength * animTimePercent) * 3 / 2, 1);
                    animation.Add(0, 1, new AnimationWrapper(v => SetTranslationX(view, v, cardsView, true), GetTranslationX(view, cardsView), 0));
                    continue;
                }

                if (view == cardsView.CurrentView)
                {
                    continue;
                }
                animation.Add(0, 1, new AnimationWrapper(v => SetTranslationX(view, v, cardsView, false), GetTranslationX(view, cardsView), Sign((int)item.Direction) * cardsView.GetSize()));
            }

            return animation.Commit(cardsView, Path.GetRandomFileName(), 16, animLength, AnimationEasing);
        }

        public virtual Task Navigate(CardsView cardsView, params ProcessorItem[] items)
        {
            var animation = new AnimationWrapper();
            var lenght = AnimationLength;
            foreach (var item in items)
            {
                var view = item.Views.FirstOrDefault();
                if (view == null)
                {
                    continue;
                }
                view.IsVisible = true;
                if (item.IsFront)
                {
                    animation.Add(0, 1, new AnimationWrapper(v => SetTranslationX(view, v, cardsView, true), GetTranslationX(view, cardsView), 0));
                    continue;
                }

                var destinationPos = item.Direction == AnimationDirection.Prev
                    ? cardsView.GetSize()
                    : -cardsView.GetSize();

                animation.Add(0, 1, new AnimationWrapper(v => SetTranslationX(view, v, cardsView, false), 0, destinationPos));
            }
            return animation.Commit(cardsView, Path.GetRandomFileName(), 16, AnimationLength, AnimationEasing);
        }

        protected virtual double GetTranslationX(View view, CardsView cardsView)
        {
            if (view == null)
            {
                return 0;
            }
            var value = cardsView.IsHorizontalOrientation ? view.TranslationX : view.TranslationY;
            value += Sign(value) * cardsView.GetSize(view) * 0.5 * (1 - view.Scale);
            return value;
        }

        protected virtual void SetTranslationX(View view, double value, CardsView cardsView, bool isFront, bool? isVisible = null, bool isClean = false)
        {
            if (view == null || !CheckSize(view, cardsView, value, isVisible, isFront, isClean))
            {
                return;
            }

            try
            {
                view.BatchBegin();
                view.Scale = CalculateFactoredProperty(value, ScaleFactor, cardsView);
                view.Opacity = CalculateFactoredProperty(value, OpacityFactor, cardsView);
                view.Rotation = CalculateFactoredProperty(value, RotationFactor, cardsView, 0) * Angle360 * Sign(-value);
                view.RotationX = CalculateFactoredProperty(value, RotationXFactor, cardsView, 0) * Angle180 * Sign(-value);
                view.RotationY = CalculateFactoredProperty(value, RotationYFactor, cardsView, 0) * Angle180 * Sign(-value);
                var translation = value - Sign(value) * cardsView.GetSize(view) * 0.5 * (1 - view.Scale);
                if (cardsView.IsHorizontalOrientation)
                {
                    view.TranslationX = translation;
                }
                else
                {
                    view.TranslationY = translation;
                }
                view.IsVisible = isVisible ?? view.IsVisible;
                if (isFront)
                {
                    cardsView.ProcessorDiff = value;
                }
            }
            finally
            {
                view.BatchCommit();
            }
        }

        protected virtual double CalculateFactoredProperty(double value, double factor, CardsView cardsView, double defaultFactorValue = 1)
            => Abs(value) * (factor - defaultFactorValue) / cardsView.GetSize() + defaultFactorValue;

        protected bool CheckSize(View view, CardsView cardsView, double value, bool? isVisible, bool isFront, bool isClean)
        {
            lock (_viewSizeChangedMapLocker)
            {
                CleanViewSizeChanged(view);
                if (cardsView.GetSize(view) < 0 && !isClean)
                {
                    _viewSizeChangedMap[view] = new ViewSizeInfoItem
                    {
                        CardsView = cardsView,
                        Value = value,
                        IsVisible = isVisible,
                        IsFront = isFront
                    };
                    view.SizeChanged += OnViewSizeChanged;
                    return false;
                }
            }
            return true;
        }

        private void OnViewSizeChanged(object sender, System.EventArgs e)
        {
            lock (_viewSizeChangedMapLocker)
            {
                var view = sender as View;
                var info = _viewSizeChangedMap[view];
                if (info.CardsView.GetSize(view) < 0)
                {
                    return;
                }
                CleanViewSizeChanged(view);
                SetTranslationX(view, info.Value, info.CardsView, info.IsFront, info.IsVisible);
            }
        }

        private void CleanViewSizeChanged(View view)
        {
            view.SizeChanged -= OnViewSizeChanged;
            _viewSizeChangedMap.Remove(view);
        }
    }
}