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.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

using Microsoft.Devices.Sensors;

namespace CheeseLander
{
    /// <summary>
    /// To jest główny typ dla naszej gry
    /// </summary>
    public class CheeseLanderGame : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        int displayWidth;
        int displayHeight;

        // Ser 
        Texture2D cheeseTexture;
        Rectangle cheeseRectangle;
        int cheeseWidth;
        float cheeseWidthFactor = 8;

        Vector2 cheesePosition;
        Vector2 cheeseSpeed;
        Vector2 cheeseAcceleration;

        float cheeseMaxLandSpeed = 0.8f;

        // Chleb
        Texture2D breadTexture;
        Rectangle breadRectangle;
        int breadWidth;
        float breadWidthFactor = 3;

        // Tła i komunikaty
        Texture2D wonTexture;
        Texture2D lostTexture;
        Texture2D backgroundTexture;
        Rectangle fullScreenDrawPos;

        // Wartości przyspieszenia

        Accelerometer accel;
        Vector3 accelReading;
        float acceleratorPower = 0.1f;

        enum gameState
        {
            playing,
            won,
            lost
        }

        private gameState state = gameState.playing;

        SoundEffect wonSound;
        SoundEffect lostSound;
        SoundEffect engineSound;
        SoundEffectInstance engineSoundInstance;

        Random rand = new Random();

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

            graphics.PreferredBackBufferWidth = 240;
            graphics.PreferredBackBufferHeight = 400;

            // Domyślna częstotliwość odświeżania dla Windows Phone wynosi 30 fps.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
        }


        /// <summary>
        /// Pozwala grze wykonać dowolną inicjalizację jakiej potrzebuje przed rozpoczeciem działania.
        /// To tutaj może ona zażądać potrzebnych usług i załadować związaną zawartość nie graficzną.
        /// Wywołanie base.Initialize wyliczy wszystkie komponenty i także je zainicjuje.
        /// </summary>
        protected override void Initialize()
        {
            accel = new Accelerometer();

            accel.ReadingChanged += new EventHandler<AccelerometerReadingEventArgs>(accel_ReadingChanged);

            accel.Start();

            base.Initialize();
        }

        object accelLock = new object();

        void accel_ReadingChanged(object sender, AccelerometerReadingEventArgs e)
        {
            lock (accelLock)
            {
                accelReading.X = (float)e.X;
                accelReading.Y = (float)e.Y;
                accelReading.Z = (float)e.Z;
            }
        }

        /// <summary>
        /// LoadContent bedzie wywołany raz na grę i tu należy ładować
        /// całą zawartość.
        /// </summary>
        protected override void LoadContent()
        {
            displayWidth = GraphicsDevice.Viewport.Width;
            displayHeight = GraphicsDevice.Viewport.Height;

            cheeseWidth = (int)(displayWidth / cheeseWidthFactor);
            breadWidth = (int)(displayWidth / breadWidthFactor);

            // Stwórz prostokąt do rysowania 
            // Rozmiar sera powinien wynosić 1/12 szerokosci ekranu
            // Stwórz nowy SpriteBatch, którego można uzyć do rysowania tekstur.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // Wczytaj obraz sera i ustaw rozmiar prostokąta wyświetlania
            cheeseTexture = Content.Load<Texture2D>("cheese");
            cheeseRectangle.Width = cheeseWidth;
            float aspectRatio = (float)cheeseTexture.Width / cheeseTexture.Height;
            cheeseRectangle.Height = (int)((cheeseWidth / aspectRatio) + 0.5f);

            // Wczytaj obraz chleba i ustaw rozmiar prostokąta wyświetlania
            breadTexture = Content.Load<Texture2D>("bread");
            breadRectangle.Width = breadWidth;
            aspectRatio = (float)breadTexture.Width / breadTexture.Height;
            breadRectangle.Height = (int)((breadWidth / aspectRatio) + 0.5f);

            // Wczytaj obraz tła
            backgroundTexture = Content.Load<Texture2D>("background");

            // Wczytaj tekstury dla zwycięstwa i przegranej
            wonTexture = Content.Load<Texture2D>("wonScreen");
            lostTexture = Content.Load<Texture2D>("lostScreen");

            fullScreenDrawPos = new Rectangle(
                0, 0,
                displayWidth, displayHeight);

            // Wczytaj efekty dźwiękowe
            wonSound = Content.Load<SoundEffect>("wonSound");
            lostSound = Content.Load<SoundEffect>("lostSound");
            engineSound = Content.Load<SoundEffect>("engineSound");

            // Stwórz instancję dźwięków silnika
            engineSoundInstance = engineSound.CreateInstance();
            engineSoundInstance.Volume = 0;
            engineSoundInstance.IsLooped = true;

            newGame();

        }

        /// <summary>
        /// UnloadContent będzie wywołany raz na grę i tu należy usuwać 
        /// całą zawartość.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Usunąć w tym miejscu każdą zawartość nie obsługiwaną przez ContentManager
        }

        float frictionFactor = 0.99f;

        private void updateGame()
        {
            // Aktualizuj składowe X i Y położenia

            // Uzyskaj wektor przyspieszenia

            lock (accelLock)
            {
                cheeseAcceleration.X = accelReading.X * acceleratorPower;
                cheeseAcceleration.Y = -accelReading.Y * acceleratorPower;
            }

            setEngineSound(cheeseAcceleration.Length());

            // Aktualizuj prędkość
            cheeseSpeed = cheeseSpeed + cheeseAcceleration;
            cheeseSpeed = cheeseSpeed * frictionFactor;

            cheesePosition =cheesePosition + cheeseSpeed ;

            // Ustaw jako współrzędne prostokąta dla wyświetlania sera wartości cheeseX oraz cheeseY
            cheeseRectangle.X = (int)(cheesePosition.X + 0.5f);
            cheeseRectangle.Y = (int)(cheesePosition.Y + 0.5f);

            // Ograniczenia na bokach

            if (cheesePosition.X < 0)
            {
                cheeseSpeed.X = (float)Math.Abs(cheeseSpeed.X);
            }

            if (cheesePosition.Y < 0)
            {
                cheeseSpeed.Y = (float)Math.Abs(cheeseSpeed.Y);
            }

            if (cheeseRectangle.Right > displayWidth)
            {
                cheeseSpeed.X = -1 * (float)Math.Abs(cheeseSpeed.X);
            }

            if (cheeseRectangle.Bottom > GraphicsDevice.Viewport.Height)
            {
                cheeseSpeed.Y = -1 * (float)Math.Abs(cheeseSpeed.Y);
            }

            if (cheeseRectangle.Intersects(breadRectangle))
            {
                // Możliwa wygrana 
                if (cheeseRectangle.Top < breadRectangle.Top &&
                    cheeseRectangle.Left >= breadRectangle.Left &&
                    cheeseRectangle.Right <= breadRectangle.Right &&
                    cheeseSpeed.Length() < cheeseMaxLandSpeed)
                {
                    // prawidłowe lądowanie - gracz wygrał
                    gameWon();
                }
                else
                {
                    // nieprawidłowe lądowanie - gracz przegrał
                    gameLost();
                }
            }
        }

        float engineSoundThreshold = 0.07f;
        float enginePitchFactor = 20.0f;

        private void stopEngineSound()
        {
            if (engineSoundInstance.State == SoundState.Playing)
            {
                engineSoundInstance.Pause();
            }
        }

        private void startEngineSound()
        {
            switch (engineSoundInstance.State)
            {
                case SoundState.Paused:
                    engineSoundInstance.Resume();
                    break;
                case SoundState.Stopped:
                    engineSoundInstance.Play();
                    break;
            }
        }

        private void setEngineSound(float control)
        {
            engineSoundInstance.Volume = Math.Min(control / engineSoundThreshold, 1);
            engineSoundInstance.Pitch = Math.Min(control * enginePitchFactor, 1);
        }

        private void gameWon()
        {
            stopEngineSound();
            wonSound.Play();
            state = gameState.won;
        }

        private void gameLost()
        {
            stopEngineSound();
            lostSound.Play();
            state = gameState.lost;
        }

        private void newGame()
        {
            // Umieść ser w losowym miejscu na górze ekranu
            cheesePosition.X = rand.Next(displayWidth - cheeseWidth);
            cheesePosition.Y = 0;

            cheeseSpeed = Vector2.Zero;

            // Przemieść chleb poziomo w nowe miejsce
            breadRectangle.X = rand.Next(displayWidth - breadWidth);
            breadRectangle.Y = displayHeight - breadRectangle.Height;

            // Rozpocznij rozgrywkę
            state = gameState.playing;

            // Uruchom silnik
            startEngineSound();
        }

        private void checkNewGame()
        {
            // Odczytaj ekran dotykowy

            TouchCollection touches = TouchPanel.GetState();

            foreach (TouchLocation location in touches)
            {
                if (location.State == TouchLocationState.Pressed)
                {
                    newGame();
                    break;
                }
            }
        }


        /// <summary>
        /// Pozwala uruchomić w grze logikę, taką jak aktualizacja świata,
        /// sprawdzenie kolizji, pobranie wejścia i odtwarzanie dźwięku.
        /// </summary>
        /// <param name="gameTime">Dostarcza chwilową wartość zmiennych czasowych.</param>
        protected override void Update(GameTime gameTime)
        {
            // Pozwala opuscić grę
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            switch (state)
            {
                case gameState.playing:
                    updateGame();
                    break;
                case gameState.lost:
                    checkNewGame();
                    break;
                case gameState.won:
                    checkNewGame();
                    break;
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// Metoda wywoływana kiedy gra powinna się wyswietlić.
        /// </summary>
        /// <param name="gameTime">Dostarcza chwilową wartość zmiennych czasowych.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            switch (state)
            {
                case gameState.playing:
                    spriteBatch.Draw(backgroundTexture, fullScreenDrawPos, Color.White);
                    spriteBatch.Draw(breadTexture, breadRectangle, Color.White);
                    spriteBatch.Draw(cheeseTexture, cheeseRectangle, Color.White);
                    break;

                case gameState.won:
                    spriteBatch.Draw(wonTexture, fullScreenDrawPos, Color.White);
                    break;

                case gameState.lost:
                    spriteBatch.Draw(lostTexture, fullScreenDrawPos, Color.White);
                    break;
            }

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
