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;

        GamePadState gamePad1;

        struct GameSpriteStruct {
            public Texture2D SpriteTexture;
            public Rectangle SpriteRectangle;
            public float X;
            public float Y;
            public float XSpeed;
            public float X;
            public float WidthFactor;
            public float TicksToCrossScreen;
            public bool Visible;
        }

#region Zarządzanie grą

        int score;
        int lives;

        void setupGame() {
            score = 0;
            lives = 3;
        }

#endregion

#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;
        }

        void setupSprite(
                ref GameSpriteStruct sprite,
                float widthFactor,
                float ticksToCrossScreen,
                float initialX,
                float initialY,
                bool initialVisibility) {
            sprite.WidthFactor = widthFactor;
            sprite.TicksToCrossScreen = ticksToCrossScreen;
            sprite.SpriteRectangle.Width = (int) ((displayWidth * widthFactor) + 0.5f);
            float aspectRatio =
                    (float) sprite.SpriteTexture.Width / sprite.SpriteTexture.Height;
            sprite.SpriteRectangle.Height =
                    (int) ((sprite.SpriteRectangle.Width / aspectRatio) + 0.5f);
            sprite.X = initialX;
            sprite.Y = initialY;
            sprite.XSpeed = displayWidth / ticksToCrossScreen;
            sprite.YSpeed = sprite.XSpeed;
            sprite.Visible = initialVisibility;
        }

#endregion

#region Rysowanie tekstu

        SpriteFont font;

        private void loadFont() {
            font = Content.Load<SpriteFont > ("SpriteFont1");
        }

        /// <summary>
        /// Rysuje tekst na ekranie
        /// </summary>
        /// <param name="text">tekst do wyświetlenia</param>
        /// <param name="textColor">kolor tekstu</param>
        /// <param name="x">lewa krawędź tekstu</param>
        /// <param name="y">górna krawędź tekstu</param>

        void drawText(string text, Color textColor, float x, float y) {
            int layer;
            Vector2 textVector = new Vector2(x, y);

            // Rysuje cień
            Color backColor = new Color(0, 0, 0, 20);
            for (layer = 0; layer < 10; layer++) {
                spriteBatch.DrawString(font, text, textVector, backColor);
                textVector.X++;
                textVector.Y++;
            }

            // Rysuje podstawę znaków
            backColor = new Color(190, 190, 190);
            for (layer = 0; layer < 5; layer++) {
                spriteBatch.DrawString(font, text, textVector, backColor);
                textVector.X++;
                textVector.Y++;
            }

            // Rysuje przednią część znaków
            spriteBatch.DrawString(font, text, textVector, textColor);
        }

#endregion

#region Kod i dane sprajtu chleba

        GameSpriteStruct bread;

        private void loadBreadContent() {
            bread.SpriteTexture = Content.Load<Texture2D > ("Images/Bread");
            setupSprite(ref bread, 0.15f, 120.0f, displayWidth / 2, displayHeight / 2, true);
        }

        void updateBread() {
            bread.X = bread.X + (bread.XSpeed * gamePad1.ThumbSticks.Left.X);
            bread.Y = bread.Y - (bread.YSpeed * gamePad1.ThumbSticks.Left.Y);
            bread.SpriteRectangle.X = (int) bread.X;
            bread.SpriteRectangle.Y = (int) bread.Y;

            if (bread.SpriteRectangle.Intersects(cheese.SpriteRectangle)) {
                cheese.YSpeed = cheese.YSpeed * -1;
            }
        }

        private void drawBread() {
            spriteBatch.Draw(bread.SpriteTexture, bread.SpriteRectangle, Color.White);
        }

#endregion

#region Kod i dane sprajtu sera

        GameSpriteStruct cheese;

        private void loadCheeseContent() {
            cheese.SpriteTexture = Content.Load<Texture2D > ("Images/Cheese");
            setupSprite(ref cheese, 0.05f, 200.0f, 200, 100, true);
        }

        void updateCheese() {
            cheese.X = cheese.X + cheese.XSpeed;
            cheese.Y = cheese.Y + cheese.YSpeed;
            cheese.SpriteRectangle.X = (int) (cheese.X + 0.5f);
            cheese.SpriteRectangle.Y = (int) (cheese.Y + 0.5f);

            if (cheese.X + cheese.SpriteRectangle.Width >= maxDisplayX) {
                cheese.XSpeed = Math.Abs(cheese.XSpeed) * -1;
            }

            if (cheese.X <= minDisplayX) {
                cheese.XSpeed = Math.Abs(cheese.XSpeed);
            }

            if (cheese.Y + cheese.SpriteRectangle.Height >= maxDisplayY) {
                cheese.YSpeed = Math.Abs(cheese.YSpeed) * -1;
                if (lives > 0) {
                    lives--;
                }
            }

            if (cheese.Y <= minDisplayY) {
                cheese.YSpeed = Math.Abs(cheese.YSpeed);
            }
        }

        private void drawCheese() {
            spriteBatch.Draw(cheese.SpriteTexture, cheese.SpriteRectangle, Color.White);
        }

#endregion

#region Kod i dane sprajtu pomidora

        Texture2D tomatoTexture;
        GameSpriteStruct[] tomatoes;
        int numberOfTomatoes = 20;
        float tomatoHeight;
        float tomatoStepFactor = 0.1f;
        float tomatoHeightLimit;

        private void loadTomatoContent() {
            tomatoTexture = Content.Load<Texture2D > ("Images/Tomato");
            tomatoHeight = minDisplayY;
            tomatoHeightLimit = minDisplayY + ((maxDisplayY - minDisplayY) / 2);
            tomatoes = new GameSpriteStruct[numberOfTomatoes];

            float tomatoSpacing = (maxDisplayX - minDisplayX) / numberOfTomatoes;

            for (int i = 0; i < numberOfTomatoes; i++) {
                tomatoes[i].SpriteTexture = tomatoTexture;
                setupSprite(
                        ref tomatoes[i],
                        0.05f, // 20 pomidorów na całym ekranie
                        1000, // 1000 taktów na pokonanie całego ekranu
                        minDisplayX + (i * tomatoSpacing), minDisplayY,
                        true // początkowo sprajty są widoczne
                        );
            }
        }

        void resetTomatoDisplay() {
            tomatoHeight = tomatoHeight + (displayHeight * tomatoStepFactor);

            if (tomatoHeight > tomatoHeightLimit) {
                tomatoHeight = minDisplayY;
            }

            for (int i = 0; i < numberOfTomatoes; i++) {
                tomatoes[i].Visible = true;
                tomatoes[i].Y = tomatoHeight;
            }
        }

        void updateTomatoes() {
            bool noTomatoes = true;

            for (int i = 0; i < numberOfTomatoes; i++) {
                if (tomatoes[i].Visible) {
                    noTomatoes = false;
                    if (cheese.SpriteRectangle.Intersects(tomatoes[i].SpriteRectangle)) {
                        cheese.YSpeed = cheese.YSpeed * -1;
                        score = score + 10;
                        tomatoes[i].Visible = false;
                        break;
                    }
                }

                tomatoes[i].SpriteRectangle.X = (int) tomatoes[i].X;
                tomatoes[i].SpriteRectangle.Y = (int) tomatoes[i].Y;
            }

            if (noTomatoes) {
                resetTomatoDisplay();
            }
        }

        void drawTomatoes() {

            for (int i = 0; i < numberOfTomatoes; i++) {
                if (tomatoes[i].Visible) {
                    spriteBatch.Draw(tomatoes[i].SpriteTexture, tomatoes[i].SpriteRectangle, Color.White);
                }
            }

        }

#endregion

#region Kod i dane tła

        GameSpriteStruct background;

        private void loadBackgroundContent() {
            background.SpriteTexture =
                    Content.Load<Texture2D > ("Images\\Background");
            background.SpriteRectangle =
                    new Rectangle(
                    (int) minDisplayX, (int) minDisplayY,
                    (int) (maxDisplayX - minDisplayX),
                    (int) (maxDisplayY - minDisplayY)
                    );
        }

        private void updateBackground() {
        }

        private void drawBackground() {
            spriteBatch.Draw(background.SpriteTexture,
                    background.SpriteRectangle, Color.White);
        }

#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);

            loadCheeseContent();
            loadBreadContent();
            loadTomatoContent();
            loadBackgroundContent();
            loadFont();
            setupGame();
        }

        /// <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);
            // Pozwala opuścić grę
            if (gamePad1.Buttons.Back == ButtonState.Pressed)
                this.Exit();

            updateCheese();

            if (lives == 0) {
                return;
            }

            updateBread();

            updateTomatoes();

            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) {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            drawBackground();

            drawCheese();

            drawBread();

            drawTomatoes();

            drawText(
                    "Wynik: " + score.ToString() + " Życia: " + lives.ToString(),
                    Color.White,
                    minDisplayX,
                    maxDisplayY - 50);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

