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

namespace _3DTutorial01
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        
        // Skybox
        Model skyboxMesh;
        public Vector3 skyboxPosition;
        public Matrix skyboxRotation;
        public Matrix skyboxScale;
        public TextureCube environ;
        Effect shader;

        // Set the 3D models to draw.
        Model myModel;

        // The aspect ratio determines how to scale 3d to 2d projection.
        float aspectRatio;

        // Set the position of the model in world space, and set the rotation.
        Vector3 modelPosition = Vector3.Zero;

        Matrix yawPitchRoll = Matrix.Identity;

        float modelRotation = 0.0f;
        float modelVelocity = 0.0f;

        //Set the sound effects to use
        SoundEffect soundEngine;
        bool soundEnginePlaying = false;
        SoundEffectInstance soundEngineInstance;
        SoundEffect soundHyperspaceActivation;

        // Set the position of the camera in world space, for our view matrix.
        Vector3 cameraPosition; 

        KeyboardState keybState;

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

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            cameraPosition = modelPosition + new Vector3(0.0f, 500.0f, 5000.0f);
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            //effect = Content.Load<Effect>("Effect\\effects"); 
            //texture = Content.Load<Texture2D>("Texture\\ground");
            myModel = Content.Load<Model>("Models\\p1_wedge");
            soundEngine = Content.Load<SoundEffect>("Audio\\Waves\\engine_2");
            soundHyperspaceActivation = Content.Load<SoundEffect>("Audio\\Waves\\hyperspace_activate");

            skyboxMesh = Content.Load<Model>("Models\\skybox");
            //skyboxMesh = Content.Load<Model>("Models\\cube");
            shader = Content.Load<Effect>("Shaders\\skybox");
            //environ = Content.Load<TextureCube>("Textures\\cubeMap");
            //environ = Content.Load<TextureCube>("Textures\\SkyBox");
            //environ = Content.Load<TextureCube>("Textures\\Islands");
            environ = Content.Load<TextureCube>("Textures\\Sunset");
            
            skyboxPosition = new Vector3(0, 0, 0);
            skyboxRotation = new Matrix();
            skyboxRotation = Matrix.Identity * Matrix.CreateRotationX(0) * Matrix.CreateRotationY(0) *
                Matrix.CreateRotationZ(0);
            skyboxScale = new Matrix();
            skyboxScale = Matrix.Identity * Matrix.CreateScale(10000);

            aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        protected void UpdateInput()
        {
            // Get the keyboard state.
            keybState = Keyboard.GetState();

            if (keybState.IsKeyDown(Keys.Escape))
                this.Exit();

            if (keybState.IsKeyDown(Keys.Left))
                // Rotate the model left.
                modelRotation += 0.01f;

            if (keybState.IsKeyDown(Keys.Right))
                // Rotate the model left.
                modelRotation -= 0.01f;

            if (keybState.IsKeyDown(Keys.Up))
                // Add velocity.
                modelVelocity -= 5.0f;

            if (keybState.IsKeyDown(Keys.Down))
                // Subtract velocity.
                modelVelocity += 5.0f;

            //Play engine sound only when the engine is on.
            if (modelVelocity < -0.1f && !soundEnginePlaying)
            {
                if (soundEngineInstance == null)
                    soundEngineInstance = soundEngine.Play(0.5f, 0.0f, 0.0f, true);
                else
                    soundEngineInstance.Resume();
                soundEnginePlaying = true;
            }
            else if (modelVelocity > -0.1f && soundEnginePlaying)
            {
                soundEngineInstance.Pause();
                soundEnginePlaying = false;
            }

            // In case you get lost, press A to warp back to the center.
            if (keybState.IsKeyDown(Keys.A))
                {
                    modelPosition = Vector3.Zero;
                    modelVelocity = 0.0f;
                    modelRotation = 0.0f;
                    soundHyperspaceActivation.Play();
                }
            }
        
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            
            // TODO: Add your update logic here
            // modelRotation += (float)gameTime.ElapsedGameTime.TotalMilliseconds * MathHelper.ToRadians(0.1f);

            // Get some input.
            UpdateInput();

            // Add velocity and rotation to the current position.
            
            Vector3 v = new Vector3(0.0f, 0.0f, 0.0f);
            Matrix m = Matrix.CreateTranslation(new Vector3(0.0f, 0.0f, modelVelocity)) *
                Matrix.CreateFromYawPitchRoll(modelRotation, 0.0f, 0.0f);
            v = Vector3.Transform(v, m);
            modelPosition += v;

            // Bleed off velocity over time.
            modelVelocity *= 0.99f;

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            // Draw the Skybox
            //skyBox.Draw(gameTime);
            Matrix World = skyboxScale * skyboxRotation;

            shader.Parameters["World"].SetValue(World);
            shader.Parameters["View"].SetValue(Matrix.CreateLookAt(cameraPosition, modelPosition, Vector3.Up));
            shader.Parameters["Projection"].SetValue(Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                        aspectRatio, 1.0f, 300000.0f));
            shader.Parameters["surfaceTexture"].SetValue(environ);
            shader.Parameters["EyePosition"].SetValue(cameraPosition);

            for (int pass = 0; pass < shader.CurrentTechnique.Passes.Count; pass++)
            {
                for (int msh = 0; msh < skyboxMesh.Meshes.Count; msh++)
                {
                    ModelMesh mesh = skyboxMesh.Meshes[msh];
                    for (int prt = 0; prt < mesh.MeshParts.Count; prt++)
                        mesh.MeshParts[prt].Effect = shader;
                    mesh.Draw();
                }
            }

            // Copy any parent transforms.
            Matrix[] transforms = new Matrix[myModel.Bones.Count];
            myModel.CopyAbsoluteBoneTransformsTo(transforms);
            // Draw the model. A model can have multiple meshes, so loop.
            foreach (ModelMesh mesh in myModel.Meshes)
            {
              // This is where the mesh orientation is set, as well as our camera and projection.
                foreach (BasicEffect effect in mesh.Effects)
                {
                    //effect.DirectionalLight0.Enabled = true;
                    //effect.DirectionalLight0.DiffuseColor = Vector3.One;
                    //effect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(1.0f, -1.0f, 0.0f));
                    //effect.DirectionalLight0.SpecularColor = Vector3.One;
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateRotationY(modelRotation)
                        * Matrix.CreateTranslation(modelPosition);
                    effect.View = Matrix.CreateLookAt(cameraPosition, modelPosition, Vector3.Up);
                    effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                        aspectRatio, 1.0f, 300000.0f);

                }
                // Draw the mesh, using the effects set above.
                mesh.Draw();
            }

            base.Draw(gameTime);
        }
    }
}
