using System.Collections.Generic;

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

namespace PanCardView.Processors
{
    public class CoverFlowProcessor : CarouselProcessor
    {
        public CoverFlowProcessor()
        {
            NoViewMaxShiftValue = 0;
        }

        public override void Init(CardsView cardsView, params ProcessorItem[] items)
        {
            var step = GetStep(cardsView);
            foreach (var item in items)
            {
                if (item.IsFront)
                {
                    SetTranslationX(item.Views.FirstOrDefault(), 0, cardsView, true, true);
                    continue;
                }
                var index = 0;
                foreach (var view in item.Views ?? Enumerable.Empty<View>())
                {
                    ++index;
                    if (view == null)
                    {
                        continue;
                    }
                    SetTranslationX(view, Sign((int)item.Direction)
                        * (cardsView.IsRightToLeftFlowDirectionEnabled ? -1 : 1)
                        * step
                        * index, cardsView, false, true);
                }
            }
        }

        public override 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 override void Change(CardsView cardsView, double value, params ProcessorItem[] items)
        {
            var step = GetStep(cardsView);
            foreach (var item in items)
            {
                var view = item.Views.FirstOrDefault();

                if (Abs(value) > step || (item.Direction == AnimationDirection.Prev && value < 0) || (item.Direction == AnimationDirection.Next && value > 0))
                {
                    return;
                }

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

                    if (view != null)
                    {
                        SetTranslationX(view, value, cardsView, true);
                    }
                    continue;
                }

                if (item.Direction == AnimationDirection.Null || view == null)
                {
                    Init(cardsView,
                        new ProcessorItem
                        {
                            Views = item.Views,
                            Direction = (AnimationDirection)Sign(GetTranslationX(view, cardsView)),
                        },
                        new ProcessorItem
                        {
                            Views = item.InactiveViews,
                            Direction = (AnimationDirection)Sign(GetTranslationX(item.InactiveViews?.FirstOrDefault(), cardsView))
                        });
                    continue;
                }

                var otherViews = item.Views.Union(item.InactiveViews ?? Enumerable.Empty<View>()).Except(Enumerable.Repeat(view, 1));
                ProceedPositionChanged(Sign((int)item.Direction) * step + value, view, otherViews, cardsView);
            }
        }

        public override Task Navigate(CardsView cardsView, params ProcessorItem[] items)
        {
            var step = GetStep(cardsView);
            var animation = new AnimationWrapper();
            foreach (var item in items)
            {
                var view = item.Views.FirstOrDefault();
                if (view == null)
                {
                    continue;
                }

                if (item.IsFront)
                {
                    animation.Add(0, 1, new AnimationWrapper(v => SetTranslationX(view, v, cardsView, true), GetTranslationX(view, cardsView), 0));
                    continue;
                }

                var otherViews = item.Views.Union(item.InactiveViews ?? Enumerable.Empty<View>()).Except(Enumerable.Repeat(view, 1));
                animation.Add(0, 1, new AnimationWrapper(v => ProceedPositionChanged(v, view, otherViews, cardsView), 0, -Sign((int)item.Direction) * step));

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

        public override Task Proceed(CardsView cardsView, params ProcessorItem[] items)
        {
            var step = GetStep(cardsView);
            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 - (step - Abs(GetTranslationX(view, cardsView))) / step;
                    animLength = Max((uint)(AnimationLength * animTimePercent), 1);
                    animation.Add(0, 1, new AnimationWrapper(v => SetTranslationX(view, v, cardsView, true), GetTranslationX(view, cardsView), 0));
                    continue;
                }

                var otherViews = item.Views.Union(item.InactiveViews ?? Enumerable.Empty<View>()).Except(Enumerable.Repeat(view, 1));
                animation.Add(0, 1, new AnimationWrapper(v => ProceedPositionChanged(v, view, otherViews, cardsView), GetTranslationX(view, cardsView), -Sign((int)item.Direction) * step));
            }
            return animation.Commit(cardsView, Path.GetRandomFileName(), 16, animLength, AnimationEasing);
        }

        public override Task Reset(CardsView cardsView, params ProcessorItem[] items)
        {
            var step = GetStep(cardsView);
            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 - (step - Abs(GetTranslationX(view, cardsView))) / step;
                    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;
                }
                var otherViews = item.Views.Union(item.InactiveViews ?? Enumerable.Empty<View>()).Except(Enumerable.Repeat(view, 1));
                animation.Add(0, 1, new AnimationWrapper(v => ProceedPositionChanged(v, view, otherViews, cardsView), GetTranslationX(view, cardsView), Sign((int)item.Direction) * step));
            }
            return animation.Commit(cardsView, Path.GetRandomFileName(), 16, animLength, AnimationEasing);
        }

        private double GetStep(CardsView cardsView)
        {
            var coverFlowView = cardsView.AsCoverFlowView();
            return cardsView.GetSize() * (1 - coverFlowView.PositionShiftPercentage) - coverFlowView.PositionShiftValue;
        }

        private void ProceedPositionChanged(double value, View checkView, IEnumerable<View> views, CardsView cardsView)
        {
            var diff = GetTranslationX(checkView, cardsView) - value;
            SetTranslationX(checkView, value, cardsView, false);

            foreach (var view in views ?? Enumerable.Empty<View>())
            {
                if (view == null)
                {
                    continue;
                }
                SetTranslationX(view, GetTranslationX(view, cardsView) - diff, cardsView, false);
            }
        }
    }
}