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;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace BreadAndCheese {

#region Game Sprites

    public class BaseSprite {
        protected Texture2D spriteTexture;
        protected Rectangle spriteRectangle;

        /// <summary>
        /// Ładuje teksturę używaną w ramach danego sprajtu.
        /// </summary>
        /// <param name="inSpriteTexture">Używana tekstura.</param>
        public void LoadTexture(Texture2D inSpriteTexture) {
            spriteTexture = inSpriteTexture;
        }

        /// <summary>
        /// Ładuje prostokąt wyznaczający wymiary na potrzeby 
        /// operacji rysowania tego sprajtu tła.
        /// </summary>
        /// <param name="inSpriteRectangle">Prostokąt, który ma zostać użyty.</param>
        public void SetRectangle(Rectangle inSpriteRectangle) {
            spriteRectangle = inSpriteRectangle;
        }

        /// <summary>
        /// Metoda wywoływana w momencie uruchamiania gry
        /// </summary>
        public virtual void StartGame() {
        }

        /// <summary>
        /// Metoda wywoływana w momencie zatrzymywania gry
        /// </summary>
        public virtual void EndGame() {
        }

        /// <summary>
        /// Rysuje teksturę na podstawie prostokąta określającego jej wymiary.
        /// </summary>
        /// <param name="spriteBatch">Obiekt klasy SpriteBatch, który ma zostać użyty
        /// przez operację rysowania.</param>
        public virtual void Draw(SpriteBatch spriteBatch) {
            spriteBatch.Draw(spriteTexture, spriteRectangle, Color.White);
        }

        /// <summary>
        /// Aktualizuje zachowanie dla tytułu. Sprajt bazowy nie zawiera żadnego
        /// zachowania aktualizacji, ale pozostałe sprajty mogą zawierać takie zachowania.
        /// </summary>
        /// <param name="game">Gra, której przebieg ma być kontrolowany.</param>
        public virtual void Update(BreadAndCheeseGame game) {
        }

        public BaseSprite(Texture2D inSpriteTexture, Rectangle inRectangle) {
            LoadTexture(inSpriteTexture);
            SetRectangle(inRectangle);
        }

    }

    public class TitleSprite : BaseSprite {
        /// <summary>
        /// Aktualizuje zachowanie dla tytułu. Metoda testuje stan przycisku A i uruchamia grę,
        /// jeśli ten przycisk jest wciśnięty.
        /// </summary>
        /// <param name="game">Gra, której przebieg ma być kontrolowany.</param>
        public override void Update(BreadAndCheeseGame game) {
            if (game.GamePad1.Buttons.A == ButtonState.Pressed) {
                game.StartGame();
            }
        }

        public TitleSprite(Texture2D inSpriteTexture, Rectangle inRectangle)
        : base(inSpriteTexture, inRectangle) {
        }
    }

    public class MovingSprite : BaseSprite {
        protected float x;
        protected float y;
        protected float xSpeed;
        protected float ySpeed;

        protected float initialX;
        protected float initialY;

        protected float minDisplayX;
        protected float maxDisplayX;

        protected float minDisplayY;
        protected float maxDisplayY;

        public float XPos
        {
            get
            {
                return x;
            }
        }

        public float YPos
        {
            get
            {
                return y;
            }
        }

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

        /// <summary>
        /// Tworzy poruszający się sprajt
        /// </summary>
        /// <param name="inSpriteTexture">Tekstura używana dla tego sprajtu</param>
        /// <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ątkowe położenie sprajtu w poziomie (X)</param>
        /// <param name="initialY">początkowe położenie sprajtu w pionie (Y)</param>
        public MovingSprite(
                Texture2D inSpriteTexture,
                float widthFactor,
                float ticksToCrossScreen,
                float inMinDisplayX,
                float inMaxDisplayX,
                float inMinDisplayY,
                float inMaxDisplayY,
                float inInitialX,
                float inInitialY)
        : base(inSpriteTexture, Rectangle.Empty) {
            minDisplayX = inMinDisplayX;
            minDisplayY = inMinDisplayY;
            maxDisplayX = inMaxDisplayX;
            maxDisplayY = inMaxDisplayY;
            initialX = inInitialX;
            initialY = inInitialY;

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

        public override void StartGame() {
            x = initialX;
            y = initialY;
            spriteRectangle.X = (int) x;
            spriteRectangle.Y = (int) y;
            base.StartGame();
        }
    }

    public class BatSprite : MovingSprite {
        /// <summary>
        /// Aktualizuje położenie pałki zależnie od stanu pada.
        /// </summary>
        public override void Update(BreadAndCheeseGame game) {
            x = x + (xSpeed * game.GamePad1.ThumbSticks.Left.X);
            y = y - (ySpeed * game.GamePad1.ThumbSticks.Left.Y);
            spriteRectangle.X = (int) x;
            spriteRectangle.Y = (int) y;
        }

        public BatSprite(Texture2D inSpriteTexture,
                float widthFactor, float ticksToCrossScreen,
                float inMinDisplayX, float inMaxDisplayX,
                float inMinDisplayY, float inMaxDisplayY,
                float inInitialX, float inInitialY)
        : base(inSpriteTexture, widthFactor, ticksToCrossScreen,
        inMinDisplayX, inMaxDisplayX,
        inMinDisplayY, inMaxDisplayY,
        inInitialX, inInitialY) {
        }
    }

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

            spriteRectangle.X = (int) (x + 0.5f);
            spriteRectangle.Y = (int) (y + 0.5f);

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

            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 BallSprite(Texture2D inSpriteTexture,
                float widthFactor, float ticksToCrossScreen,
                float inMinDisplayX, float inMaxDisplayX,
                float inMinDisplayY, float inMaxDisplayY,
                float inInitialX, float inInitialY)
        : base(inSpriteTexture, widthFactor, ticksToCrossScreen,
        inMinDisplayX, inMaxDisplayX,
        inMinDisplayY, inMaxDisplayY,
        inInitialX, inInitialY) {
        }
    }

    public class TargetRowSprite : BaseSprite {
        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;

        /// <summary>
        /// Rozpoczyna grę. Konfiguruje cele i przygotowuje je do użycia.
        /// </summary>
        /// <param name="inNumberOfTargets">Liczba celów na ekranie.</param>
        /// <param name="inTargetStepFactor">Część ekranu, o którą cele zostaną przesunięte w dół
        /// po zniszczeniu całego rzędu.</param>
        /// <param name="inMinDisplayX"></param>
        /// <param name="inMaxDisplayX"></param>
        /// <param name="inMinDisplayY"></param>
        /// <param name="inMaxDisplayY"></param>
        public TargetRowSprite(
                Texture2D inSpriteTexture,
                int inNumberOfTargets,
                float inTargetStepFactor,
                float inMinDisplayX,
                float inMaxDisplayX,
                float inMinDisplayY,
                float inMaxDisplayY)
        : base(inSpriteTexture, Rectangle.Empty) {
            numberOfTargets = inNumberOfTargets;
            targetStepFactor = inTargetStepFactor;

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

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

            targetWidth = displayWidth / numberOfTargets;

            float aspectRatio = (float) spriteTexture.Width / spriteTexture.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;
            }
        }

        public override void StartGame() {
            targetHeight = minDisplayY;

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

            base.StartGame();
        }

        /// <summary>
        /// Aktualizuje stan celów na ekranie. Jeśli wszystkie cele zostały zniszczone
        /// położenie rzędu celów jest zmieniane, a same cele są ponownie wyświetlane na ekranie.
        /// </summary>
        /// <param name="game">Gra zawierająca rząd celów. Ten parametr nie jest obecnie używany
        /// ale może zostać użyty w przyszłości, aby umożliwić celom przyspieszanie ruchu sera
        /// po każdym zniszczeniu całego rzędu celów.</param>
        public override void Update(BreadAndCheeseGame game) {
            for (int i = 0; i < numberOfTargets; i++) {
                if (targetVisibility[i]) {
                    // Zwraca sterowanie w razie znalezienia widocznego pomidora.
                    return;
                }
            }

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

            // Położenie rysowanego rzędu celów jest obniżane.
            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;
            }
        }

        /// <summary>
        /// Rysuje wszystkie widoczne cele.
        /// </summary>
        /// <param name="spriteBatch">Obiekt klasy SpriteBatch, który ma zostać użyty podczas rysowania.</param>
        public override void Draw(SpriteBatch spriteBatch) {
            for (int i = 0; i < numberOfTargets; i++) {
                if (targetVisibility[i]) {
                    spriteBatch.Draw(spriteTexture, targets[i], Color.White);
                }
            }
        }

        /// <summary>
        /// Sprawdza, czy jakiś obiekt nie koliduje z celem. Jeśli tak,
        /// cel zostaje uznany za zniszczony.
        /// </summary>
        /// <param name="target">Położenie celu.</param>
        /// <returns>true, jeśli miała miejsce kolizja i jeśli cel został usunięty.</returns>
        public bool CheckCollision(Rectangle target) {
            for (int i = 0; i < numberOfTargets; i++) {
                if (targetVisibility[i]) {
                    // Wykryto kolizję.
                    if (targets[i].Intersects(target)) {
                        // Cel jest niszczony.
                        targetVisibility[i] = false;
                        // Zwraca wartość reprezentującą wystąpienie kolizji.
                        return true;
                    }
                }
            }
            return false;
        }
    }

    public class DeadlySprite : MovingSprite {
        private bool isDeadly;


        public override void StartGame() {
            // Na początku gry papryki nie stanowią zagrożenia.
            isDeadly = false;

            // Papryki pozostają niegroźne do momentu osiągnięcia 200 punktów.
            deadlyTriggerScore = 200;

            base.StartGame();
        }

        public DeadlySprite(
                Texture2D inSpriteTexture,
                float widthFactor, float ticksToCrossScreen,
                float inMinDisplayX, float inMaxDisplayX, float inMinDisplayY,
                float inMaxDisplayY, float initialX, float initialY)
        : base(
        inSpriteTexture, widthFactor,
        ticksToCrossScreen, inMinDisplayX, inMaxDisplayX,
        inMinDisplayY, inMaxDisplayY, initialX, initialY) {
        }

        public override void Draw(SpriteBatch spriteBatch) {
            if (isDeadly) {
                spriteBatch.Draw(spriteTexture, spriteRectangle, Color.Red);
            } else {
                spriteBatch.Draw(spriteTexture, spriteRectangle, Color.Green);
            }
        }

        /// <summary>
        /// Liczba punktów, po której jest włączany tryb śmiertelnego zagrożenia.
        /// </summary>
        int deadlyScoreStep = 100;

        /// <summary>
        /// Następny poziom punktów, którego osiągnięcie spowoduje przejście w tryb śmiertelnego zagrożenia.
        /// </summary>
        int deadlyTriggerScore = 200;

        /// <summary>
        /// Aktualizuje położenie sprajtu. Obsługuje kolizje z pałką
        /// i piłką. 
        /// </summary>
        /// <param name="game">Gra, w której występuje ta piłka</param>
        public override void Update(BreadAndCheeseGame game) {
            // Sprajt zawsze odbija się od krawędzi ekranu i reaguje na kontakt
            // z pozostałymi obiektami, pod warunkiem że są widoczne.

            x = x + xSpeed;
            y = y + ySpeed;

            spriteRectangle.X = (int) (x + 0.5f);
            spriteRectangle.Y = (int) (y + 0.5f);

            if (x + spriteRectangle.Width >= maxDisplayX) {
                // Sprajt zderzył się z prawą krawędzią.
                xSpeed = Math.Abs(xSpeed) * -1;
            }

            if (x <= minDisplayX) {
                // Sprajt zderzył się z lewą krawędzią.
                xSpeed = Math.Abs(xSpeed);
            }

            if (y + spriteRectangle.Height >= maxDisplayY) {
                // Sprajt zderzył się z dolną krawędzią.
                ySpeed = Math.Abs(ySpeed) * -1;
            }

            if (y <= minDisplayY) {
                // Papryka uderzyła w górną krawędź ekranu.
                ySpeed = Math.Abs(ySpeed);
            }

            if (game.GetScore() > deadlyTriggerScore) {
                // Wynik przekroczył próg.
                // Przechodzi w tryb śmiertelnego zagrożenia i przesuwa próg.
                isDeadly = true;
                deadlyTriggerScore = deadlyTriggerScore + deadlyScoreStep;
            }

            if (isDeadly) {
                if (game.BreadBat.CheckCollision(spriteRectangle)) {
                    // Pałka uderzyła w ten sprajt.
                    isDeadly = false;
                    // utrata życia
                    game.LoseLife();
                }

                if (game.CheeseBall.CheckCollision(spriteRectangle)) {
                    // piłka zderzyła się z tym sprajtem
                    isDeadly = false;
                    // aktualizuje liczbę punktów
                    game.UpdateScore(50);
                }
            }
        }
    }

    public class KillerSprite : MovingSprite {
        private bool isDeadly;

        public override void StartGame() {
            isDeadly = false;
            killerTriggerScore = 50;
            base.StartGame();
        }

        /// <summary>
        /// Liczba punktów, po której jest włączany tryb śmiertelnego zagrożenia.
        /// </summary>
        int killerScoreStep = 400;

        /// <summary>
        /// Następny poziom punktów, którego osiągnięcie spowoduje przejście w tryb śmiertelnego zagrożenia.
        /// </summary>
        int killerTriggerScore;

        public override void Update(BreadAndCheeseGame game) {

            if (game.GetScore() > killerTriggerScore) {
                // Wynik przekroczył próg.
                // Włącza zabójcze mandarynki i przesuwa próg.
                isDeadly = true;
                killerTriggerScore = killerTriggerScore + killerScoreStep;
            }

            if (isDeadly) {
                if (game.BreadBat.CheckCollision(spriteRectangle)) {
                    // pałka zderzyła z tym zabójczym sprajtem
                    isDeadly = false;
                    // odejmuje kilka punktów
                    game.UpdateScore(-10);
                }

                if (game.CheeseBall.CheckCollision(spriteRectangle)) {
                    // Piłka trafiła w zabójczy sprajt.
                    isDeadly = false;
                    // aktualizuje liczbę punktów
                    game.UpdateScore(10);
                }

                if (game.BreadBat.XPos > x) {
                    x += xSpeed;
                } else {
                    x -= xSpeed;
                }
                if (game.BreadBat.YPos > y) {
                    y += ySpeed;
                } else {
                    y -= ySpeed;
                }
            }

            spriteRectangle.X = (int) (x + 0.5f);
            spriteRectangle.Y = (int) (y + 0.5f);

            base.Update(game);
        }

        public override void Draw(SpriteBatch spriteBatch) {
            if (isDeadly) {
                base.Draw(spriteBatch);
            }
        }


        static Random rand = new Random(1);

        public KillerSprite(
                Texture2D inSpriteTexture,
                float widthFactor, float ticksToCrossScreen,
                float inMinDisplayX, float inMaxDisplayX,
                float inMinDisplayY, float inMaxDisplayY,
                float inInitialX, float inInitialY)
        : base(inSpriteTexture, widthFactor, ticksToCrossScreen,
        inMinDisplayX, inMaxDisplayX,
        inMinDisplayY, inMaxDisplayY,
        inInitialX, inInitialY) {
        }
    }



#endregion

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

        // Świat gry
        public GamePadState GamePad1;

        public BatSprite BreadBat;
        public BallSprite CheeseBall;
        public DeadlySprite DeadlyPepper;
        public TargetRowSprite TomatoTargets;
        public TitleSprite Title;
        public BaseSprite Background;

        public List<BaseSprite> GameSprites = new List<BaseSprite>();

        public KillerSprite KillerTangerine;

        /// <summary>
        /// Generator liczb losowych. Wartość początkowa generatora jest stała,
        /// zatem losowe sekwencje zawsze będą takie same.
        /// </summary>
        private Random rand = new Random(1);

#region Wartości reprezentujące wymiary ekranu

        int displayWidth;
        int 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 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, 10);
            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 Wyświetlanie wyniku

        void drawScore() {
            drawText("Punkty: " + score + " Życia: " + lives, Color.Blue, minDisplayX, maxDisplayY - 50);
        }

        void drawHighScore() {
            drawText("Najlepszy wynik: " + highScore + " Naciśnij A, aby rozpocząć", Color.Blue, minDisplayX, minDisplayY);
        }

#endregion

#region Zarządzanie grą i punktacją

        int score;
        int lives;
        int highScore;

        public enum GameState {
            titleScreen,
            playingGame
        }

        GameState state = GameState.titleScreen;

        public void StartGame() {
            score = 0;
            lives = 3;

            foreach(BaseSprite sprite in GameSprites) {
                sprite.StartGame();
            }

            state = GameState.playingGame;
        }

        void gameOver() {
            if (score > highScore) {
                highScore = score;
            }
            state = GameState.titleScreen;
        }

        public void LoseLife() {
            lives--;
            if (lives == 0) {
                gameOver();
            }
        }

        public void UpdateScore(int update) {
            score += update;
        }

        public int GetScore() {
            return score;
        }

        public int GetLives() {
            return lives;
        }

        public GameState GetState() {
            return state;
        }

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

            Background = new BaseSprite(
                    Content.Load<Texture2D > ("Images/Background"),
                    new Rectangle((int) minDisplayX, (int) minDisplayY,
                    (int) (maxDisplayX - minDisplayX),
                    (int) (maxDisplayY - minDisplayY)
                    ));

            GameSprites.Add(Background);

            Title = new TitleSprite(
                    Content.Load<Texture2D > ("Images/Title"),
                    new Rectangle((int) minDisplayX, (int) minDisplayY,
                    (int) (maxDisplayX - minDisplayX),
                    (int) (maxDisplayY - minDisplayY)
                    ));

            CheeseBall = new BallSprite(
                    Content.Load<Texture2D > ("Images/Cheese"),
                    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 od lewej krawędzi
                    displayHeight / 4); // jedna czwarta wysokości ekranu od górnej krawędzi

            GameSprites.Add(CheeseBall);

            BreadBat = new BatSprite(
                    Content.Load<Texture2D > ("Images/Bread"),
                    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)

            GameSprites.Add(BreadBat);

            DeadlyPepper = new DeadlySprite(
                    Content.Load<Texture2D > ("Images/Pepper"),
                    0.1f,
                    500,
                    minDisplayX, maxDisplayX, minDisplayY, maxDisplayY,
                    minDisplayX, minDisplayY);

            GameSprites.Add(DeadlyPepper);

            TomatoTargets = new TargetRowSprite(
                    Content.Load<Texture2D > ("Images/Tomato"),
                    20,
                    0.1f,
                    minDisplayX,
                    maxDisplayX,
                    minDisplayY,
                    maxDisplayY);

            GameSprites.Add(TomatoTargets);

            for (int i = 0; i < 100; i++) {
                KillerSprite Tangerine;

                float initialX = rand.Next((int) minDisplayX, (int) (maxDisplayX));
                float initialY = rand.Next((int) minDisplayY, (int) (maxDisplayY));
                int speed = 500 + rand.Next(500);

                Tangerine = new KillerSprite(
                        Content.Load<Texture2D > ("Images/Tangerine"),
                        0.03f,
                        speed,
                        minDisplayX,
                        maxDisplayX,
                        minDisplayY,
                        maxDisplayY,
                        initialX,
                        initialY);
                GameSprites.Add(Tangerine);
            }

            loadFont();
        }

        /// <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();

            switch (state) {
                case GameState.titleScreen:
                    Title.Update(this);
                    break;
                case GameState.playingGame:
                    foreach(BaseSprite sprite in GameSprites)
                {
                    sprite.Update(this);
                }
                    break;
            }

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

            switch (state) {
                case GameState.titleScreen:
                    Title.Draw(spriteBatch);
                    drawHighScore();
                    break;
                case GameState.playingGame:
                    foreach(BaseSprite sprite in GameSprites)
                {
                    sprite.Draw(spriteBatch);
                }
                    drawScore();
                    break;
            }

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
