using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace BreadAndCheese {
    /// <summary>
    /// To jest główny typ Twojej gry.
    /// </summary>
    public class BreadAndCheeseGame : Microsoft.Xna.Framework.Game {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

#region Struktury gry

        public struct BatSpriteStruct {
            private Texture2D spriteTexture;
            private Rectangle spriteRectangle;
            private float x;
            private float y;
            private float xSpeed;
            private float ySpeed;

            private float minDisplayX;
            private float maxDisplayX;
            private float minDisplayY;
            private float maxDisplayY;

            /// <summary>
            /// Ładuje teksturę do sprajtu pałki.
            /// </summary>
            /// <param name="inSpriteTexture">tekstura do załadowania</param>
            public void LoadTexture(Texture2D inSpriteTexture) {
                spriteTexture = inSpriteTexture;
            }

            /// <summary>
            /// Metoda rozpoczyna grę. Wyznacza rozmiary wszystkich obiektów na 
            /// ekranie i umieszcza na początkowych pozycjach.
            /// </summary>
            /// <param name="widthFactor">Rozmiar obiektu w grze wyrażony jako ułamek szerokości
            /// ekranu.</param>
            /// <param name="ticksToCrossScreen">Szybkość poruszania się obiektu wyrażona w liczbie taktów (1/60 sekundy) 
            /// potrzebnych do pokonania całego ekranu.</param>
            /// <param name="inMinDisplayX">minimalna wartość X</param>
            /// <param name="inMaxDisplayX">maksymalna wartość X</param>
            /// <param name="inMinDisplayY">minimalna wartość Y</param>
            /// <param name="inMaxDisplayY">maksymalna wartość Y</param>
            /// <param name="initialX">początkowa pozycja pałki (X)</param>
            /// <param name="initialY">początkowa pozycja pałki (Y)</param>

            public void StartGame(
                    float widthFactor,
                    float ticksToCrossScreen,
                    float inMinDisplayX,
                    float inMaxDisplayX,
                    float inMinDisplayY,
                    float inMaxDisplayY,
                    float initialX,
                    float initialY) {
                minDisplayX = inMinDisplayX;
                minDisplayY = inMinDisplayY;
                maxDisplayX = inMaxDisplayX;
                maxDisplayY = inMaxDisplayY;

                float displayWidth = maxDisplayX - minDisplayX;

                spriteRectangle.Width = (int) ((displayWidth * widthFactor) + 0.5f);
                float aspectRatio =
                        (float) spriteTexture.Width / spriteTexture.Height;
                spriteRectangle.Height =
                        (int) ((spriteRectangle.Width / aspectRatio) + 0.5f);
                x = initialX;
                y = initialY;
                xSpeed = displayWidth / ticksToCrossScreen;
                ySpeed = xSpeed;
            }

            /// <summary>
            /// Rysuje pałkę na ekranie.
            /// </summary>
            /// <param name="spriteBatch">Obiekt klasy SpriteBatch używany do rysowania na ekranie</param>
            public void Draw(SpriteBatch spriteBatch) {
                spriteBatch.Draw(spriteTexture, spriteRectangle, Color.White);
            }

            /// <summary>
            /// Aktualizuje położenie pałki zależnie od stanu pada.
            /// </summary>
            public void Update(BreadAndCheeseGame game) {
                GamePadState gamePad1 = GamePad.GetState(PlayerIndex.One);

                x = x + (xSpeed * gamePad1.ThumbSticks.Left.X);
                y = y - (ySpeed * gamePad1.ThumbSticks.Left.Y);
                spriteRectangle.X = (int) x;
                spriteRectangle.Y = (int) y;
            }

            /// <summary>
            /// Sprawdza ewentualne kolizje pomiędzy pałką a pozostałymi obiektami.
            /// </summary>
            /// <param name="target">Prostokąt reprezentujący położenie innego obiektu.</param>
            /// <returns>true, jeśli pałka koliduje z danym obiektem</returns>
            public bool CheckCollision(Rectangle target) {
                return spriteRectangle.Intersects(target);
            }
        }

        public struct BallSpriteStruct {
            private Texture2D spriteTexture;
            private Rectangle spriteRectangle;
            private float x;
            private float y;
            private float xSpeed;
            private float ySpeed;

            private float minDisplayX;
            private float maxDisplayX;
            private float minDisplayY;
            private float maxDisplayY;

            /// <summary>
            /// Ładuje teksturę do sprajtu piłki.
            /// </summary>
            /// <param name="inSpriteTexture">tekstura do załadowania</param>
            public void LoadTexture(Texture2D inSpriteTexture) {
                spriteTexture = inSpriteTexture;
            }

            /// <summary>
            /// Metoda rozpoczyna grę. Wyznacza rozmiary wszystkich obiektów na 
            /// ekranie i umieszcza na początkowych pozycjach.
            /// </summary>
            /// <param name="widthFactor">Rozmiar obiektu w grze wyrażony jako ułamek szerokości
            /// ekranu.</param>
            /// <param name="ticksToCrossScreen">Szybkość poruszania się obiektu wyrażona w liczbie taktów (1/60 sekundy) 
            /// potrzebnych do pokonania całego ekranu.</param>
            /// <param name="inMinDisplayX">minimalna wartość X</param>
            /// <param name="inMaxDisplayX">maksymalna wartość X</param>
            /// <param name="inMinDisplayY">minimalna wartość Y</param>
            /// <param name="inMaxDisplayY">maksymalna wartość Y</param>
            /// <param name="initialX">początkowa pozycja piłki (X)</param>
            /// <param name="initialY">początkowa pozycja piłki (Y)</param>

            public void StartGame(
                    float widthFactor,
                    float ticksToCrossScreen,
                    float inMinDisplayX,
                    float inMaxDisplayX,
                    float inMinDisplayY,
                    float inMaxDisplayY,
                    float initialX,
                    float initialY) {
                minDisplayX = inMinDisplayX;
                minDisplayY = inMinDisplayY;
                maxDisplayX = inMaxDisplayX;
                maxDisplayY = inMaxDisplayY;

                float displayWidth = maxDisplayX - minDisplayX;

                spriteRectangle.Width = (int) ((displayWidth * widthFactor) + 0.5f);
                float aspectRatio =
                        (float) spriteTexture.Width / spriteTexture.Height;
                spriteRectangle.Height =
                        (int) ((spriteRectangle.Width / aspectRatio) + 0.5f);
                x = initialX;
                y = initialY;
                xSpeed = displayWidth / ticksToCrossScreen;
                ySpeed = xSpeed;
            }

            /// <summary>
            /// Rysuje piłkę na ekranie.
            /// </summary>
            /// <param name="spriteBatch">Obiekt klasy SpriteBatch używany do rysowania na ekranie</param>
            public void Draw(SpriteBatch spriteBatch) {
                spriteBatch.Draw(spriteTexture, spriteRectangle, Color.White);
            }

            /// <summary>
            /// Aktualizuje położenie piłki. Obsługuje kolizje z pałką.
            /// </summary>
            /// <param name="game">Gra, w której występuje ta piłka</param>
            public void Update(BreadAndCheeseGame game) {
                x = x + xSpeed;
                y = y + ySpeed;

                // Ustawia nowe położenie prostokąta sprajtu:
                spriteRectangle.X = (int) (x + 0.5f);
                spriteRectangle.Y = (int) (y + 0.5f);

                // Sprawdza, czy piłka trafiła w pałkę
                if (game.BreadBat.CheckCollision(spriteRectangle)) {
                    // Pałka trafiła w piłkę.
                    ySpeed = ySpeed * -1;
                }

                if (x + spriteRectangle.Width >= maxDisplayX) {
                    // Piłka została trafiona z prawej strony.
                    xSpeed = Math.Abs(xSpeed) * -1;
                }

                if (x <= minDisplayX) {
                    // Piłka została trafiona z lewej strony.
                    xSpeed = Math.Abs(xSpeed);
                }

                // Sprawdza, czy piłka trafiła w dolną krawędź ekranu.
                if (y + spriteRectangle.Height >= maxDisplayY) {
                    // Piłka zderzyła się z dolną krawędzią. Powoduje utratę życia.
                    ySpeed = Math.Abs(ySpeed) * -1;
                    game.LoseLife();
                }

                if (y <= minDisplayY) {
                    // Piłka uderzyła w górną część ekranu.
                    ySpeed = Math.Abs(ySpeed);
                }

                if (game.TomatoTargets.CheckCollision(spriteRectangle)) {
                    game.UpdateScore(20);
                    ySpeed = ySpeed * -1;
                }

            }
        }

        public struct TargetRowStruct {
            private Texture2D targetTexture;
            private int numberOfTargets;
            private float targetWidth;
            private float targetHeight;
            private float targetStepFactor;
            private float targetHeightLimit;
            private Rectangle[] targets;
            private bool[] targetVisibility;

            private float minDisplayX;
            private float maxDisplayX;
            private float minDisplayY;
            private float maxDisplayY;
            private float displayHeight;
            private float displayWidth;

            public void LoadTexture(Texture2D inTargetTexture) {
                targetTexture = inTargetTexture;
            }

            public void StartGame(
                    int inNumberOfTargets,
                    float inTargetStepFactor,
                    float inMinDisplayX,
                    float inMaxDisplayX,
                    float inMinDisplayY,
                    float inMaxDisplayY) {
                numberOfTargets = inNumberOfTargets;
                targetStepFactor = inTargetStepFactor;

                minDisplayX = inMinDisplayX;
                minDisplayY = inMinDisplayY;
                maxDisplayX = inMaxDisplayX;
                maxDisplayY = inMaxDisplayY;

                displayWidth = maxDisplayX - minDisplayX;
                displayHeight = maxDisplayY - minDisplayY;

                targetWidth = displayWidth / numberOfTargets;

                float aspectRatio = (float) targetTexture.Width / targetTexture.Height;

                targetHeight = targetWidth / aspectRatio;

                targetHeightLimit = minDisplayY + ((maxDisplayY - minDisplayY) / 2);

                targets = new Rectangle[numberOfTargets];
                targetVisibility = new bool[numberOfTargets];

                for (int i = 0; i < numberOfTargets; i++) {
                    targets[i].Width = (int) targetWidth;
                    targets[i].Height = (int) targetHeight;

                    targets[i].Y = (int) targetHeight;
                    targets[i].X = (int) (minDisplayX + (i * targetWidth) + 0.5f);

                    targetVisibility[i] = true;
                }
            }

            void resetTargetDisplay() {
                targetHeight = targetHeight + (displayHeight * targetStepFactor);

                if (targetHeight > targetHeightLimit) {
                    targetHeight = minDisplayY;
                }

                for (int i = 0; i < numberOfTargets; i++) {
                    targets[i].Y = (int) targetHeight;
                    targetVisibility[i] = true;
                }
            }

            public void Update(BreadAndCheeseGame game) {
                for (int i = 0; i < numberOfTargets; i++) {
                    if (targetVisibility[i]) {
                        // Zwraca sterowanie w razie znalezienia widocznego celu.
                        return;
                    }
                }

                // Jeśli sterowanie dotarło w to miejsce, nie ma widocznych pomidorów.

                // Obniża miejsce rysowania celu na ekranie.
                targetHeight = targetHeight + (displayHeight * targetStepFactor);

                // Sprawdza, czy osiągnięto dolną granicę rysowania celów.
                if (targetHeight > targetHeightLimit) {
                    targetHeight = minDisplayY;
                }

                // Przywraca ustawienia wszystkich celów.
                for (int i = 0; i < numberOfTargets; i++) {
                    targets[i].Y = (int) targetHeight;
                    targetVisibility[i] = true;
                }
            }

            public void Draw(SpriteBatch spriteBatch) {
                for (int i = 0; i < numberOfTargets; i++) {
                    if (targetVisibility[i]) {
                        spriteBatch.Draw(targetTexture, targets[i], Color.White);
                    }
                }
            }

            public bool CheckCollision(Rectangle target) {
                for (int i = 0; i < numberOfTargets; i++) {
                    if (targetVisibility[i]) {
                        // Uzyskuje cel uczestniczący w kolizji.
                        if (targets[i].Intersects(target)) {
                            // Niszczy pomidor.
                            targetVisibility[i] = false;
                            // Zwraca wartość reprezentującą wystąpienie kolizji.
                            return true;
                        }
                    }
                }
                return false;
            }
        }

#endregion

        // Świat gry
        public GamePadState GamePad1;

        public BatSpriteStruct BreadBat;
        public BallSpriteStruct CheeseBall;
        public TargetRowStruct TomatoTargets;

#region Wartości reprezentujące wymiary ekranu

        float displayWidth;
        float displayHeight;
        float overScanPercentage = 10.0f;
        float minDisplayX;
        float maxDisplayX;
        float minDisplayY;
        float maxDisplayY;

        private void setScreenSizes() {
            displayWidth = graphics.GraphicsDevice.Viewport.Width;
            displayHeight = graphics.GraphicsDevice.Viewport.Height;
            float xOverscanMargin = getPercentage(overScanPercentage, displayWidth) / 2.0f;
            float yOverscanMargin = getPercentage(overScanPercentage, displayHeight) / 2.0f;

            minDisplayX = xOverscanMargin;
            minDisplayY = yOverscanMargin;

            maxDisplayX = displayWidth - xOverscanMargin;
            maxDisplayY = displayHeight - yOverscanMargin;
        }


#endregion

#region Metody pomocnicze

        /// <summary>
        /// Oblicza wartości procentowe
        /// </summary>
        /// <param name="percentage">procent, który ma zostać wyznaczony</param>
        /// <param name="inputValue">wartość do przeliczenia</param>
        /// <returns>wartość wynikowa</returns>

        float getPercentage(float percentage, float inputValue) {
            return (inputValue * percentage) / 100;
        }

#endregion

#region Zarządzanie stanem gry

        public void LoseLife() {
        }

        public void UpdateScore(int update) {
        }

#endregion


        public BreadAndCheeseGame() {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Umożliwia ewentualną inicjalizację przed uruchomieniem właściwej gry.
        /// W tym miejscu można odnaleźć wszystkie wymagane zasoby i załadować treści
        /// related content. Wywołanie metody base.Initialize spowoduje przeszukanie wszystkich komponentów
        /// i ich inicjalizację.
        /// </summary>
        protected override void Initialize() {
            setScreenSizes();

            base.Initialize();
        }

        /// <summary>
        /// Metoda LoadContent jest wywoływana tylko raz dla całej gry i jako taka jest właściwym miejscem
        /// dla kodu ładującego treść.
        /// </summary>
        protected override void LoadContent() {
            // Tworzy nowy obiekt klasy SpriteBatch, którego można używać do rysowania tekstur.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            BreadBat.LoadTexture(Content.Load<Texture2D > ("Images/Bread"));
            CheeseBall.LoadTexture(Content.Load<Texture2D > ("Images/Cheese"));
            TomatoTargets.LoadTexture(Content.Load<Texture2D > ("Images/Tomato"));

            CheeseBall.StartGame(
                    0.07f, // szerokość sera wynosi 0,07 szerokości ekranu
                    200, // ser potrzebuje 200 taktów na pokonanie całego ekranu
                    minDisplayX, maxDisplayX, minDisplayY, maxDisplayY,
                    displayWidth / 4, // jedna czwarta szerokości ekranu
                    displayHeight / 4); // jedna czwarta wysokości ekranu od górnej krawędzi

            BreadBat.StartGame(
                    0.166f, // szerokość chleba wynosi 0,166 szerokości ekranu
                    150, // pokonanie ekranu zajmuje 150 taktów
                    minDisplayX, maxDisplayX, minDisplayY, maxDisplayY,
                    displayWidth / 2, // na początku znajduje się na środku ekranu (w poziomie)
                    displayHeight / 2); // na początku znajduje się na środku ekranu (w pionie)

            TomatoTargets.StartGame(
                    20,
                    0.1f,
                    minDisplayX,
                    maxDisplayX,
                    minDisplayY,
                    maxDisplayY);
        }

        /// <summary>
        /// Metoda UnloadContent jest wywoływana tylko raz dla całej gry i jako taka jest właściwym miejscem
        /// dla kodu usuwającego całą treść z pamięci.
        /// </summary>
        protected override void UnloadContent() {
            // TODO: Należy usunąć całą treść, którą nie zarządza ContentManager.
        }

        /// <summary>
        /// Umożliwia grze wykonywanie logiki związanej z aktualizacją świata gry,
        /// sprawdzaniem kolizji, pobieraniem danych wejściowych czy odtwarzaniem dźwięków.
        /// </summary>
        /// <param name="gameTime">Udostępnia wartości reprezentujące bieżący czas.</param>
        protected override void Update(GameTime gameTime) {
            GamePad1 = GamePad.GetState(PlayerIndex.One);

            if (GamePad1.Buttons.Back == ButtonState.Pressed)
                this.Exit();

            BreadBat.Update(this);
            CheeseBall.Update(this);
            TomatoTargets.Update(this);

            base.Update(gameTime);
        }

        /// <summary>
        /// Ta metoda jest wywoływana w momencie, w którym gra musi narysować swój świat.
        /// </summary>
        /// <param name="gameTime">Udostępnia wartości reprezentujące bieżący czas.</param>
        protected override void Draw(GameTime gameTime) {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            BreadBat.Draw(spriteBatch);
            CheeseBall.Draw(spriteBatch);
            TomatoTargets.Draw(spriteBatch);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
