using System;
using System.Collections.Generic;
using System.ComponentModel;
using Xamarin.Forms;
using TheGameExtreme.viewmodel;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using TouchTracking;
using Xamarin.Essentials;
using TheGameExtreme.Resx;
using TheGameExtreme.model.card.cardType;

namespace TheGameExtreme.view
{

    /**
     * Classe qui permet de gérer la vue du jeu
     */
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {
        private Main viewmodel;
        private List<string> playersNames;
        private int nbPile;
        private int nbCard;
        private int indexMode;
        List<TouchManipulationCard> textCollection = new List<TouchManipulationCard>();
        List<TouchManipulationCard> stackCollection = new List<TouchManipulationCard>();
        Dictionary<long, TouchManipulationCard> textDictionary = new Dictionary<long, TouchManipulationCard>();
        private SKCanvas canvas;


        /**
         * <param name="playersNames">Liste des pseudos des joueurs</param>
         * <param name="nbPile">Nombre de piles pour jouer</param>
         * <param name="indexMode">Version du jeu joué</param>
         */
        public MainPage(List<string> playersNames, int nbPile, int indexMode, int nbCard)
        {
            this.playersNames = playersNames;
            this.nbPile = nbPile;
            this.nbCard = nbCard;
            this.indexMode = indexMode;

            InitializeComponent();
            NavigationPage.SetHasNavigationBar(this, false);

            EndTurnButton.Text = AppResources.StrEndTurn;   // Trouver le moyen d'avoir du binding

            viewmodel = new Main(playersNames, nbPile, nbCard, indexMode);

            viewmodel.EndGame += OnEndGame;

            viewmodel.AlertChanged += OnAlertChanged;

            pseudo.SetBinding(Label.TextProperty, new Binding("Pseudo", source: viewmodel));

            InflateStack();
            InflateHand();
        }


        /**
         * Evénement permettant d'afficher les messages d'alertes générés dans le model
         * <param name="sender">Instance qui envoi l'événement</param>
         * <param name="args">Argument(s) de l'événement</param>
         */
        private void OnAlertChanged(object sender, EventArgs args)
        {
            if (viewmodel.Alert != null)
            {
                DisplayAlert("😆", viewmodel.Alert, "OK");
                viewmodel.Alert = null;
            }
        }


        /**
         * Evénement permettant de peindre la surface du canvas
         * <param name="sender">Instance qui envoi l'événement</param>
         * <param name="args">Arguments de l'événement</param>
         */
        public void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
        {
            canvas = args.Surface.Canvas;
            canvas.Clear();

            for (int i = 0; i < stackCollection.Count; i++)
            {
                if (i < (stackCollection.Count * 0.5))
                {
                    stackCollection[i].Paint(canvas, new SKColor(33, 255, 40));
                }
                else
                {
                    stackCollection[i].Paint(canvas, SKColors.Red);
                }
            }

            foreach (TouchManipulationCard textPaint in textCollection)
            {
                textPaint.Paint(canvas, SKColors.SkyBlue);
            }
        }


        /**
         * Evénement qui permet de gérer le Drag and Drop.
         * Il permet de traiter le clic, le mouvement, le relachement du clic et la fin de traitement d'un clic.
         * <param name="sender">Instance qui envoi l'événement</param>
         * <param name="args">Arguments de l'événement</param>
         */
        public void OnTouchEffectAction(object sender, TouchActionEventArgs args)
        {
            TouchTrackingPoint pt = args.Location;
            SKPoint point =
                new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
                            (float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));

            switch (args.Type)
            {
                case TouchActionType.Pressed:
                    for (int i = textCollection.Count - 1; i >= 0; i--)
                    {
                        TouchManipulationCard textPaint = textCollection[i];

                        if (textPaint.HitTest(point))
                        {
                            // Move card to end of collection
                            textCollection.Remove(textPaint);
                            textCollection.Add(textPaint);

                            // Do the touch processing
                            textDictionary.Add(args.Id, textPaint);
                            textPaint.ProcessTouchEvent(args.Id, args.Type, point);
                            canvasView.InvalidateSurface();
                            break;
                        }
                    }
                    break;

                case TouchActionType.Moved:
                    if (textDictionary.ContainsKey(args.Id))
                    {
                        TouchManipulationCard card = textDictionary[args.Id];
                        point.Y -= 120;
                        point.X -= 50;
                        card.ProcessTouchEvent(args.Id, args.Type, point);
                        canvasView.InvalidateSurface();
                    }
                    break;

                case TouchActionType.Released:


                case TouchActionType.Cancelled:
                    if (textDictionary.ContainsKey(args.Id))
                    {
                        TouchManipulationCard card = textDictionary[args.Id];
                        bool find = false;
                        foreach (TouchManipulationCard stack in stackCollection)
                        {
                            SKPoint pointVisuCard = new SKPoint(point.X, point.Y - 120);
                            if (stack.HitTest(point) || stack.HitTest(pointVisuCard))
                            {
                                int indexPile = stackCollection.IndexOf(stack);
                                if (Played(indexPile, card.Value.Value))
                                {
                                    card.ProcessTouchEvent(args.Id, TouchActionType.Moved, stack.InitialPoint);
                                    card.InitialPoint = stackCollection[stackCollection.IndexOf(stack)].InitialPoint;
                                    stackCollection[stackCollection.IndexOf(stack)] = card;
                                    textCollection.RemoveAt(textCollection.Count - 1);
                                    find = true;
                                }
                                break;
                            }
                        }
                        if (!find)
                        {
                            card.ProcessTouchEvent(args.Id, TouchActionType.Moved, card.InitialPoint);
                        }
                        card.ProcessTouchEvent(args.Id, args.Type, point);
                        textDictionary.Remove(args.Id);
                        canvasView.InvalidateSurface();
                    }
                    break;
            }
        }


        /**
         * Fonction permettant de déployer visuellement les piles
         */
        private void InflateStack()
        {
            stackCollection.Clear();

            SKPaint textPaint = new SKPaint();

            float inflateWidth = 0.024f * (float)(DeviceDisplay.MainDisplayInfo.Width - 20);

            SKPoint position = new SKPoint((float)((DeviceDisplay.MainDisplayInfo.Width - 20 - inflateWidth) / (viewmodel.getListOrderedStacks().Count * 2)) + 10f, (float)((DeviceDisplay.MainDisplayInfo.Height * 0.1) + (DeviceDisplay.MainDisplayInfo.Height * 0.9) * 0.01 + 2 * (0.05f * (float)DeviceDisplay.MainDisplayInfo.Width * textPaint.TextSize / textPaint.MeasureText("001"))));

            position.X -= inflateWidth;

            for (int i = 0; i < viewmodel.getListOrderedStacks().Count; i++)
            {
                textPaint = new SKPaint();
                float textWidth;

                if (viewmodel.getListOrderedStacks()[i].Peek().View.GetType() == typeof(FractionCard))
                {
                    string displayMax = Math.Pow(10, ((FractionCard)(viewmodel.getListOrderedStacks()[i].Peek().View)).Fraction.SizeMax-1).ToString();
                    textWidth = textPaint.MeasureText(displayMax);
                }
                else
                {
                    textWidth = textPaint.MeasureText("00");
                }
                textPaint.TextSize = 0.03f * (float)(DeviceDisplay.MainDisplayInfo.Width - 20) * textPaint.TextSize / textPaint.MeasureText("00");
                position.X -= textWidth * 0.5f;

                stackCollection.Add(new TouchManipulationCard(textPaint, viewmodel.getListOrderedStacks()[i].Peek(), inflateWidth)
                {
                    Matrix = SKMatrix.MakeTranslation(position.X, position.Y),
                    InitialMatrix = SKMatrix.MakeTranslation(position.X, position.Y),
                    InitialPoint = position
                });

                position.X += (float)((DeviceDisplay.MainDisplayInfo.Width - 20 - inflateWidth) / viewmodel.getListOrderedStacks().Count) + textWidth * 0.5f;
            }
        }


        /**
         * Fonction permettant de déployer visuellement les cartes contenues dans la main du joueur actif
         */
        private void InflateHand()
        {
            SKPaint textPaint = new SKPaint();

            float inflateWidth = 0.024f * (float)(DeviceDisplay.MainDisplayInfo.Width - 20);

            SKPoint position = new SKPoint((float)((DeviceDisplay.MainDisplayInfo.Width - 20 - inflateWidth) / (viewmodel.CurrentHand.Count * 2)) + 10f, (float)((DeviceDisplay.MainDisplayInfo.Height * 0.9) - (DeviceDisplay.MainDisplayInfo.Height * 0.9) * 0.1 - 2 * (0.05f * (float)(DeviceDisplay.MainDisplayInfo.Width - 20) * textPaint.TextSize / textPaint.MeasureText("001"))));

            position.X -= inflateWidth;

            for (int i = 0; i < viewmodel.CurrentHand.Count; i++)
            {
                textPaint = new SKPaint();
                float textWidth;

                if (viewmodel.CurrentHand[i].View.GetType() == typeof(FractionCard))
                {
                    string displayMax = Math.Pow(10, ((FractionCard)(viewmodel.CurrentHand[i].View)).Fraction.SizeMax-1).ToString();
                    textWidth = textPaint.MeasureText(displayMax);
                }
                else
                {
                    textWidth = textPaint.MeasureText("00");
                }
                textPaint.TextSize = 0.03f * (float)(DeviceDisplay.MainDisplayInfo.Width - 20) * textPaint.TextSize / textPaint.MeasureText("00");
                position.X -= textWidth * 0.5f;
    
                textCollection.Add(new TouchManipulationCard(textPaint, viewmodel.CurrentHand[i], inflateWidth)
                {
                    Matrix = SKMatrix.MakeTranslation(position.X, position.Y),
                    InitialMatrix = SKMatrix.MakeTranslation(position.X, position.Y),
                    InitialPoint = position
                });

                position.X += (float)((DeviceDisplay.MainDisplayInfo.Width - 20 - inflateWidth) / viewmodel.CurrentHand.Count) + textWidth * 0.5f;
            }
        }


        /**
         * Evénement traitant la fin du jeu
         * <param name="sender">Instance qui envoi l'événement</param>
         * <param name="args">Argument(s) de l'événement</param>
         */
        private void OnEndGame(object sender, EventArgs args)
        {
            gameOption.Children.Clear();
            Button retryButton = new Button();
            retryButton.Text = AppResources.StrRetry;
            retryButton.CornerRadius = 10;
            retryButton.Clicked += Retry;
            retryButton.BackgroundColor = (Color)Application.Current.Resources["Gold"];
            gameOption.Children.Add(retryButton);
        }

        /**
         * Fonction permettant de lancer le déplacement d'une carte sur une pile
         * <param name="numStack">Index de la pile joué</param>
         * <param name="value">Valeur de la carte joué</param>
         * <returns>Booléen qui indique si la carte a pu être joué</returns>
         */
        private bool Played(int numStack, decimal value)
        {
            if (!viewmodel.played(numStack, value))
            {
                return false;
            }
            return true;
        }


        /**
         * Evénement permettant de relancer le jeu avec les mêmes paramètres de jeu
         * <param name="sender">Instance qui envoi l'événement</param>
         * <param name="args">Argument de l'événement</param>
         */
        private void Retry(object sender, EventArgs args)
        {
            viewmodel = new Main(playersNames, nbPile, nbCard, indexMode);

            viewmodel.EndGame += OnEndGame;

            viewmodel.AlertChanged += OnAlertChanged;

            pseudo.SetBinding(Label.TextProperty, new Binding("Pseudo", source: viewmodel));

            textCollection.Clear();
            textDictionary.Clear();
            canvasView.InvalidateSurface();
            InflateStack();
            InflateHand();

            gameOption.Children.Clear();
            Button button = new Button();
            button.Text = AppResources.StrEndTurn;
            button.Clicked += EndTurn;
            button.BackgroundColor = (Color)Application.Current.Resources["Gold"];
            gameOption.Children.Add(button);
        }


        /**
         * Evénement permettant de lancer la fin du tour d'un joueur
         * <param name="sender">Instance qui envoi l'événement</param>
         * <param name="args">Argument de l'événement</param>
         */
        private void EndTurn(object sender, EventArgs args)
        {
            if (!viewmodel.endTurn())
            {
                textCollection.Clear();
                textDictionary.Clear();
                stackCollection.Clear();
                canvasView.InvalidateSurface();
                InflateStack();
                InflateHand();
            }
        }


        /**
         * Evénement permettant de naviguer entre les différentes pages de l'application
         * <param name="sender">Instance qui envoi l'événement</param>
         * <param name="args">Argument de l'événement</param>
         */
        private async void PlayToHome(object sender, EventArgs args)
        {
            await Navigation.PopToRootAsync();
        }
    }
}
