#region File Description
//-----------------------------------------------------------------------------
// Game.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Diagnostics;
using Microsoft.Xna.Framework.Audio;
#endregion

namespace IrelandsCall
{
    /// <summary>
    /// Ireland's Call with the Guitar Hero X-Plorer Controller.
    /// Robert Burke, http://www.robburke.net
    /// Article: http://robburke.net/2007/06/16/irelands-call-on-guitar-hero-controller-for-the-pc-and-xbox-360/
    /// 16 June 2007
    /// 
    /// Started from the ParticleSystem3D Sample from the 
    /// XNA Creators Club.
    /// 
    /// If you're looking at this code, you're probably interested in Guitar Hero X-Plorer integration
    /// or perhaps the HLSL shaders or maybe how I did the audio.
    /// 
    /// X-Plorer: This works just like a normal controller.  Do a seach for [XPLORER] in this file and you
    /// can find the relevant bits about how to work with the X-Plorer.
    /// 
    /// HLSL: I tweaked the HLSL found in the original sample to also allow passing the colour of the
    /// explosions as a parameter.  OK, I know they look more like explosions than fireworks.  Making them
    /// prettier is left as an exercise to someone who, unlike me, has more artistic ability than a slug.
    /// 
    /// Audio: I cut up the audio manually using an audio editing program called GoldWave.  I now have immense
    /// respect for the folks who did the real Guitar Hero audio - making it work is easy, making it sound GOOD
    /// and RESPONSIVE is really hard!!  As a first approximation, what I did was fade in very quickly the start
    /// of each audio section, making sure it was cued so that you got an immediate, on-beat acoustic response.
    /// The goal, of course, being that if you play with a steady rhythm, the song sounds fliud.
    /// I had each section overlap a TINY bit - each audio clip plays a TINY portion of the next note before 
    /// fading out, to make it a bit more forgiving on the timing.  "Dead air" sounds awful between notes, as
    /// I am lacking a "background" track (i.e. all the other instruments in Guitar Hero.)
    /// 
    /// This was incredible fun, although let me put it this way: my patriotic girlfriend went from literally 
    /// shedding a tear the first time we played the Croke Park recording of Ireland's Call, to not ever wanting 
    /// to hear the song again by the time I was done with this demo :)
    /// 
    /// Hope you enjoy and don't hesitate to contact me with comments, suggestions, feedback.
    /// 
    /// May the road rise to meet you,
    /// Rob
    /// </summary>
    public class IrelandsCallGame : Microsoft.Xna.Framework.Game
    {

        #region Fields


        GraphicsDeviceManager graphics;
        ContentManager content;

        SpriteBatch spriteBatch;
        SpriteFont font;
        Model worldsphere;

        //audio api components
        AudioEngine _AudioEngine;
        WaveBank _WaveBank;
        SoundBank _SoundBank;
        AudioCategory SLBackgroundMusicAudioCategory;
        AudioCategory SLDefaultAudioCategory;

        public SoundBank SoundBank { get { return _SoundBank; } }

        // Load the particle texture, and set it onto the effect.
        Texture2D goodbyeTexture;


        // This sample uses five different particle systems.
        ParticleSystem explosionParticles;
        ParticleSystem explosionSmokeParticles;
        ParticleSystem projectileTrailParticles;
        ParticleSystem smokePlumeParticles;
        ParticleSystem fireParticles;


        // The sample can switch between three different visual effects.
        enum ParticleState
        {
            Explosions,
            SmokePlume,
            RingOfFire,
        };

        ParticleState currentState = ParticleState.Explosions;


        // The explosions effect works by firing projectiles up into the
        // air, so we need to keep track of all the active projectiles.
        List<Projectile> projectiles = new List<Projectile>();

        TimeSpan timeToNextProjectile = TimeSpan.Zero;


        // Random number generator for the fire effect.
        Random random = new Random();


        // Input state.
        KeyboardState currentKeyboardState;
        GamePadState currentGamePadState;

        KeyboardState lastKeyboardState;
        GamePadState lastGamePadState;


        // Camera state.
        float cameraArc = -5;
        float cameraRotation = 0;
        float cameraDistance = 200;


        #endregion

        #region Initialization


        /// <summary>
        /// Constructor.
        /// </summary>
        public IrelandsCallGame()
        {
            graphics = new GraphicsDeviceManager(this);
            content = new ContentManager(Services);

            graphics.PreferredBackBufferWidth = 800;
            graphics.PreferredBackBufferHeight = 600;// 450;// 600;

            graphics.MinimumPixelShaderProfile = ShaderProfile.PS_2_0;

            // Construct our particle system components.
            explosionParticles = new ExplosionParticleSystem(this, content);
            explosionSmokeParticles = new ExplosionSmokeParticleSystem(this, content);
            projectileTrailParticles = new ProjectileTrailParticleSystem(this, content);
            smokePlumeParticles = new SmokePlumeParticleSystem(this, content);
            fireParticles = new FireParticleSystem(this, content);

            // Set the draw order so the explosions and fire
            // will appear over the top of the smoke.
            smokePlumeParticles.DrawOrder = 100;
            explosionSmokeParticles.DrawOrder = 200;
            projectileTrailParticles.DrawOrder = 300;
            explosionParticles.DrawOrder = 400;
            fireParticles.DrawOrder = 500;

            // Register the particle system components.
            Components.Add(explosionParticles);
            Components.Add(explosionSmokeParticles);
            Components.Add(projectileTrailParticles);
            Components.Add(smokePlumeParticles);
            Components.Add(fireParticles);

            // Set up sound
            _AudioEngine = new AudioEngine("Sound\\FireworksSound.xgs");
            _WaveBank = new WaveBank(_AudioEngine, "Sound\\Wave Bank.xwb");
            _SoundBank = new SoundBank(_AudioEngine, "Sound\\Sound Bank.xsb");
            SLBackgroundMusicAudioCategory = _AudioEngine.GetCategory("Music");
            SLDefaultAudioCategory = _AudioEngine.GetCategory("Default");
            Instance = this;
        }

        public static IrelandsCallGame Instance = null;


        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            if (loadAllContent)
            {
                spriteBatch = new SpriteBatch(graphics.GraphicsDevice);

                font = content.Load<SpriteFont>("Content/font");
                worldsphere = content.Load<Model>("Content/worldsphere");

                goodbyeTexture = content.Load<Texture2D>("Content/MooseSunset");
            }
        }


        /// <summary>
        /// Unload your graphics content.
        /// </summary>
        protected override void UnloadGraphicsContent(bool unloadAllContent)
        {
            if (unloadAllContent)
                content.Unload();
        }


        #endregion

        #region ToggleFullScreen
        private bool _JustWentFullScreen = false;
        public bool JustWentFullScreen { get { return _JustWentFullScreen; } }
        private void ToggleFullScreen()
        {
            PresentationParameters presentation = graphics.GraphicsDevice.PresentationParameters;

            if (presentation.IsFullScreen)
            {   // going windowed
                graphics.PreferredBackBufferWidth = _PreferredWindowWidth;
                graphics.PreferredBackBufferHeight = _PreferredWindowHeight;
            }
            else
            {
                // going fullscreen, use desktop resolution to minimize display mode changes
                // this also has the nice effect of working around some displays that lie about 
                // supporting 1280x720
                GraphicsAdapter adapter = graphics.GraphicsDevice.CreationParameters.Adapter;
                graphics.PreferredBackBufferWidth = adapter.CurrentDisplayMode.Width;
                graphics.PreferredBackBufferHeight = adapter.CurrentDisplayMode.Height;
            }

            graphics.ToggleFullScreen();
        }
#if XBOX360
        int _PreferredWindowWidth = 1280;
        int _PreferredWindowHeight = 480*2;
#else
        int _PreferredWindowWidth = 640;
        int _PreferredWindowHeight = 480;
#endif
        #endregion

        #region Update and Draw

        private bool _GoFullScreenAtStartup = true;

        /// <summary>
        /// Allows the game to run logic.
        /// </summary>
        protected override void Update(GameTime gameTime)
        {
            if (_GoFullScreenAtStartup)
            {
                ToggleFullScreen();
                _GoFullScreenAtStartup = false;
            }
            HandleInput();

            if (currentKeyboardState.IsKeyDown(Keys.RightAlt) && currentKeyboardState.IsKeyDown(Keys.Enter) && !_JustWentFullScreen)
            {
                ToggleFullScreen();
            }

            if (XnaInputHelper.WasButtonPressed(gameTime, PlayerIndex.One, GamePadButton.Start)) this.SecondVerse = !this.SecondVerse;

            UpdateCamera(gameTime);

            switch (currentState)
            {
                case ParticleState.Explosions:
                    UpdateExplosions(gameTime);
                    break;

                case ParticleState.SmokePlume:
                    UpdateSmokePlume();
                    break;

                case ParticleState.RingOfFire:
                    UpdateFire();
                    break;
            }

            UpdateProjectiles(gameTime);

            base.Update(gameTime);
        }

        private float AsplosionCounter = 1;

#endregion

        #region Some stuff you might be interested in about the X-Plorer controller

        /// <summary>
        /// Helper for updating the explosions effect.
        /// </summary>
        void UpdateExplosions(GameTime gameTime)
        {
            AsplosionCounter++;

            /// [XPLORER]
            /// Here is all you need to know about the XPlorer: the five fret buttons are equivalent to these 5
            /// XBox controller buttons.
            GamePadState gps = GamePad.GetState(PlayerIndex.One);
            // these are the five "fret" buttons on the XPlorer.
            bool b1 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.A);
            bool b2 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.B);
            bool b3 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.Y);
            bool b4 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.X);
            bool b5 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.LeftShoulder);

            if (b1 && b2 && b3 && b4 && b5)
            {
                finalScreenVisible = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.DPadDown)
                    ? false : true;
            }


            /// [XPLORER] "Strumming" the "strings" is the equivalent of pushing DPadDown and DPadUp.
            /// The Y-axis of the Right Thumbstick is mapped to the accelerometer that will tell
            /// you if the player if lifting up the Guitar controller (i.e. "Unleashing Star Power")
            if (XnaInputHelper.WasButtonPressed(gameTime, PlayerIndex.One, GamePadButton.DPadDown)
                || XnaInputHelper.WasButtonPressed(gameTime, PlayerIndex.One, GamePadButton.DPadUp)
                || (gps.ThumbSticks.Right.Y == 1 && SecondVerse == true && (AsplosionCounter % 3 == 0))
                )
            {
                if (XnaInputHelper.WasButtonPressed(gameTime, PlayerIndex.One, GamePadButton.DPadDown)
                || XnaInputHelper.WasButtonPressed(gameTime, PlayerIndex.One, GamePadButton.DPadUp))
                    PlayTheRightSound(gameTime);


                if (b1)
                {
                    projectiles.Add(new Projectile(explosionParticles,
                                                   explosionSmokeParticles,
                                                   projectileTrailParticles,
                                                   Color.Green
                                                   ));
                }
                if (b2)
                {
                    projectiles.Add(new Projectile(explosionParticles,
                                                   explosionSmokeParticles,
                                                   projectileTrailParticles,
                                                   Color.Red
                                                   ));
                }
                if (b3)
                {
                    projectiles.Add(new Projectile(explosionParticles,
                                                   explosionSmokeParticles,
                                                   projectileTrailParticles,
                                                   Color.Yellow
                                                   ));
                }
                if (b4)
                {
                    projectiles.Add(new Projectile(explosionParticles,
                                                   explosionSmokeParticles,
                                                   projectileTrailParticles,
                                                   Color.Blue
                                                   ));
                }
                if (b5)
                {
                    projectiles.Add(new Projectile(explosionParticles,
                                                   explosionSmokeParticles,
                                                   projectileTrailParticles,
                                                   Color.Orange
                                                   ));
                }
                if (!b1 && !b2 && !b3 && !b4 && !b5) CreateRandomExplosion();
                _SoundBank.PlayCue("launch");

  //              Debug.WriteLine("Adding projectile... total is " + projectiles.Count);
            }
            if ((gps.ThumbSticks.Right.X > 0.6f && (AsplosionCounter % 7 == 0)))
            {
                /// [XPLORER] Here's how you do the little twanger thing that they use in the real
                /// Guitar Hero to mess with the pitch of individual notes.
                CreateRandomExplosion();
                _SoundBank.PlayCue("launch");
            }

        }

        private void CreateRandomExplosion()
        {
            Color randomValues = new Color((byte)random.Next(255),
                                           (byte)random.Next(255),
                                           (byte)random.Next(255),
                                           (byte)255);
            projectiles.Add(new Projectile(explosionParticles,
                                           explosionSmokeParticles,
                                           projectileTrailParticles,
                                           randomValues));
        }

        private bool SecondVerse = false;


        /// <summary>
        /// It goes without saying that my technique here is in no way scalable.
        ///  But hey, I only had 21 bits of audio to deal with :)
        /// </summary>
        private void PlayTheRightSound(GameTime gameTime)
        {
            // these are the five "fret" buttons on the XPlorer.
            bool b1 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.A);
            bool b2 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.B);
            bool b3 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.Y);
            bool b4 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.X);
            bool b5 = XnaInputHelper.IsButtonDown(gameTime, PlayerIndex.One, GamePadButton.LeftShoulder);

            if (!SecondVerse)
            {
                if (b1 && b2) _SoundBank.PlayCue("ireland06");
                else if (b1 && b3) _SoundBank.PlayCue("ireland07");
                else if (b2 && b3) _SoundBank.PlayCue("ireland08");
                else if (b2 && b4) _SoundBank.PlayCue("ireland09");
                else if (b3 && b4) _SoundBank.PlayCue("ireland10");
                else if (b1) _SoundBank.PlayCue("ireland01");
                else if (b2) _SoundBank.PlayCue("ireland02");
                else if (b3) _SoundBank.PlayCue("ireland03");
                else if (b4) _SoundBank.PlayCue("ireland04");
                else if (b5) _SoundBank.PlayCue("ireland05");
            }
            else
            {
                if (b1 && b2 && b3) _SoundBank.PlayCue("ireland21");
                else if (b2 && b3 && b4) _SoundBank.PlayCue("ireland22");
                else if (b3 && b4 && b5) _SoundBank.PlayCue("ireland23");
                else if (b1 && b2) _SoundBank.PlayCue("ireland16");
                else if (b1 && b3) _SoundBank.PlayCue("ireland17");
                else if (b2 && b3) _SoundBank.PlayCue("ireland18");
                else if (b2 && b4) _SoundBank.PlayCue("ireland19");
                else if (b3 && b4) _SoundBank.PlayCue("ireland20");
                else if (b1) _SoundBank.PlayCue("ireland11");
                else if (b2) _SoundBank.PlayCue("ireland12");
                else if (b3) _SoundBank.PlayCue("ireland13");
                else if (b4) _SoundBank.PlayCue("ireland14");
                else if (b5) _SoundBank.PlayCue("ireland15");
            }
        }


        /// <summary>
        /// Helper for updating the list of active projectiles.
        /// </summary>
        void UpdateProjectiles(GameTime gameTime)
        {
            int i = 0;

            while (i < projectiles.Count)
            {
                if (!projectiles[i].Update(gameTime))
                {
                    // Remove projectiles at the end of their life.
                    projectiles.RemoveAt(i);
                }
                else
                {
                    // Advance to the next projectile.
                    i++;
                }
            }
        }


        /// <summary>
        /// Helper for updating the smoke plume effect.
        /// </summary>
        void UpdateSmokePlume()
        {
            // This is trivial: we just create one new smoke particle per frame.
            smokePlumeParticles.AddParticle(Vector3.Zero, Vector3.Zero);
        }


        /// <summary>
        /// Helper for updating the fire effect.
        /// </summary>
        void UpdateFire()
        {
            const int fireParticlesPerFrame = 20;

            // Create a number of fire particles, randomly positioned around a circle.
            for (int i = 0; i < fireParticlesPerFrame; i++)
            {
                fireParticles.AddParticle(RandomPointOnCircle(), Vector3.Zero);
            }

            // Create one smoke particle per frmae, too.
            smokePlumeParticles.AddParticle(RandomPointOnCircle(), Vector3.Zero);
        }


        /// <summary>
        /// Helper used by the UpdateFire method. Chooses a random location
        /// around a circle, at which a fire particle will be created.
        /// </summary>
        Vector3 RandomPointOnCircle()
        {
            const float radius = 30;
            const float height = 40;

            double angle = random.NextDouble() * Math.PI * 2;

            float x = (float)Math.Cos(angle);
            float y = (float)Math.Sin(angle);

            return new Vector3(x * radius, y * radius + height, 0);
        }


        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice device = graphics.GraphicsDevice;

            device.Clear(Color.CornflowerBlue);

            // Compute camera matrices.
            float aspectRatio = (float)device.Viewport.Width /
                                (float)device.Viewport.Height;

            Matrix view = Matrix.CreateTranslation(0, -25, 0) *
                          Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
                          Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance),
                                              new Vector3(0, 0, 0), Vector3.Up);

            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                                    aspectRatio,
                                                                    1, 
                                                                    10000);

            // Pass camera matrices through to the particle system components.
            explosionParticles.SetCamera(view, projection);
            explosionSmokeParticles.SetCamera(view, projection);
            projectileTrailParticles.SetCamera(view, projection);
            smokePlumeParticles.SetCamera(view, projection);
            fireParticles.SetCamera(view, projection);

            // Draw our background grid and message text.
            DrawBackdrop(view, projection);

            DrawMessage(gameTime);

            // This will draw the particle system components.
            base.Draw(gameTime);
        }


        /// <summary>
        /// Helper for drawing the background grid model.
        /// </summary>
        void DrawBackdrop(Matrix view, Matrix projection)
        {
            GraphicsDevice device = graphics.GraphicsDevice;

            device.RenderState.AlphaBlendEnable = false;
            device.RenderState.AlphaTestEnable = false;
            device.RenderState.DepthBufferEnable = true;

            device.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            device.SamplerStates[0].AddressV = TextureAddressMode.Wrap;

            foreach (ModelMesh mesh in worldsphere.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.View = view;
                    effect.Projection = projection;
                }

                mesh.Draw();
            }


            //foreach (ModelMesh mesh in grid.Meshes)
            //{
            //    foreach (BasicEffect effect in mesh.Effects)
            //    {
            //        effect.View = view;
            //        effect.Projection = projection;
            //    }

            //    mesh.Draw();
            //}
        }


        /// <summary>
        /// Helper for drawing our message text.
        /// </summary>
        void DrawMessage(GameTime gameTime)
        {
            string message = string.Format("Current effect: {0}!!!\n" +
                                           "Hit the A button or space bar to switch.",
                                           currentState);
            FinalScreenAlpha.Target = finalScreenVisible ? 1 : 0;
            spriteBatch.Begin();
            if (FinalScreenAlpha.GetCurrent(gameTime) > 0.05f)
            {
                FullScreenRectangle.Width = graphics.GraphicsDevice.Viewport.Width;
                FullScreenRectangle.Height = graphics.GraphicsDevice.Viewport.Height;
                finalScreenColor = new Color(255, 255, 255, (byte)(255 * FinalScreenAlpha.GetCurrent(gameTime)));
                spriteBatch.Draw(goodbyeTexture, FullScreenRectangle, finalScreenColor);
            }
            //spriteBatch.DrawString(font, message, new Vector2(50, 50), Color.White);
            spriteBatch.End();
        }

        bool finalScreenVisible = false;
        Color finalScreenColor = new Color(255, 255, 255, 0);
        FloatBlender FinalScreenAlpha = new FloatBlender(0, new GameTime(), 0.3f);
        Rectangle FullScreenRectangle = new Rectangle(0, 0, 30, 30);


        #endregion

        #region Handle Input


        /// <summary>
        /// Handles input for quitting the game and cycling
        /// through the different particle effects.
        /// </summary>
        void HandleInput()
        {
            lastKeyboardState = currentKeyboardState;
            lastGamePadState = currentGamePadState;

            currentKeyboardState = Keyboard.GetState();
            currentGamePadState = GamePad.GetState(PlayerIndex.One);

            // Check for exit.
            if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
                currentGamePadState.Buttons.Back == ButtonState.Pressed)
            {
                Exit();
            }

            //// Check for changing the active particle effect.
            //if (((currentKeyboardState.IsKeyDown(Keys.Space) &&
            //     (lastKeyboardState.IsKeyUp(Keys.Space))) ||
            //    ((currentGamePadState.Buttons.A == ButtonState.Pressed)) &&
            //     (lastGamePadState.Buttons.A == ButtonState.Released)))
            //{
            //    currentState++;

            //    if (currentState > ParticleState.RingOfFire)
            //        currentState = 0;
            //}
        }


        /// <summary>
        /// Handles input for moving the camera.
        /// </summary>
        void UpdateCamera(GameTime gameTime)
        {
            float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;

            // Limit the arc movement.
            if (cameraArc > 90.0f)
                cameraArc = 90.0f;
            else if (cameraArc < -90.0f)
                cameraArc = -90.0f;

            cameraRotation = 120f * (float)Math.Sin(0.000002f * (float)gameTime.TotalGameTime.TotalMilliseconds);

            if (cameraDistance > 500)
                cameraDistance = 500;
            else if (cameraDistance < 10)
                cameraDistance = 10;
        }


        #endregion
    }


    #region Entry Point

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static class Program
    {
        static void Main()
        {
            using (IrelandsCallGame game = new IrelandsCallGame())
            {
                game.Run();
            }
        }
    }

    #endregion
}
