// Comments next line (#define) to use BasicEffect with default light to draw objects,
// uncomments to use HLSL file effect (not light).
//#define HLSL

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

namespace WindowsGame3D
{
    /// <summary>
    /// Tutorial 2 - from XNA help and forum community contributes - by Toni Greco
    ///                     _Interactive 3D graphic application_
    ///                                _ XNA Beta 2 _
    /// 
    /// This sample show how to load/draw 3D models and move the camera to all directions
    /// using keyboard.
    /// It uses three kind of 3D objects:
    ///     FBX file (ship),
    ///     X file (asteroid),
    ///     low level with vertex buffer (cube).
    /// All three cases can be rendered using standard basic effect, or HLSL fx file:
    /// see the first rows of Game1.cs to choice which rendering type to use.
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        ContentManager content;

        // High level 3d models to draw.
        Model ship;
        Model asteriod;
        
#if HLSL
        Texture2D texture;
        Effect hlslEffect;
        Texture2D textureShip;
#endif

        // Low level 3d model to draw.
        VertexDeclaration cubeVertexDeclaration;
        VertexBuffer cubeVertexBuffer;
        BasicEffect cubeBasicEffect;
        Texture2D cubeTexture;

        // ship position and rotation in world space.
        Vector3 shipPosition = new Vector3(1500, 0, 0);
        float modelrotation = 0.0f;

        // Position of the Camera in world space.
        Matrix view = Matrix.CreateLookAt(new Vector3(0.0f, 50.0f, 5000.0f), Vector3.Zero, Vector3.Up);

        // Projection matrix.
        Matrix projection;

        /// <summary>
        /// Creates an array of non-indexed VertexPositionNormalTexture data
        /// representing a cube.
        /// </summary>
        private void InitializeCube()
        {
            VertexPositionNormalTexture[] nonIndexedCube = new VertexPositionNormalTexture[36];

            // Vertex.
            Vector3 topLeftFront = new Vector3(-400.0f, 400.0f, 400.0f);
            Vector3 bottomLeftFront = new Vector3(-400.0f, -400.0f, 400.0f);
            Vector3 topRightFront = new Vector3(400.0f, 400.0f, 400.0f);
            Vector3 bottomRightFront = new Vector3(400.0f, -400.0f, 400.0f);
            Vector3 topLeftBack = new Vector3(-400.0f, 400.0f, -400.0f);
            Vector3 topRightBack = new Vector3(400.0f, 400.0f, -400.0f);
            Vector3 bottomLeftBack = new Vector3(-400.0f, -400.0f, -400.0f);
            Vector3 bottomRightBack = new Vector3(400.0f, -400.0f, -400.0f);

            // Normals and texture coords.
            Vector3 ZP = new Vector3(0f, 0f, 1f);
            Vector3 ZN = new Vector3(0f, 0f, -1f);
            Vector3 XP = new Vector3(1f, 0f, 0f);
            Vector3 XN = new Vector3(-1f, 0f, 0f);
            Vector3 YP = new Vector3(0f, 1f, 0f);
            Vector3 YN = new Vector3(0f, -1f, 0f);
            Vector2 Texcoords00 = new Vector2(0f, 0f);
            Vector2 Texcoords10 = new Vector2(1f, 0f);
            Vector2 Texcoords11 = new Vector2(1f, 1f);
            Vector2 Texcoords01 = new Vector2(0f, 1f);

            // Back face
            nonIndexedCube[0] =
                new VertexPositionNormalTexture(topLeftFront, ZP, Texcoords00);
            nonIndexedCube[1] =
                new VertexPositionNormalTexture(bottomLeftFront, ZP, Texcoords01);
            nonIndexedCube[2] =
                new VertexPositionNormalTexture(topRightFront, ZP, Texcoords10);
            nonIndexedCube[3] =
                new VertexPositionNormalTexture(bottomLeftFront, ZP, Texcoords01);
            nonIndexedCube[4] =
                new VertexPositionNormalTexture(bottomRightFront, ZP, Texcoords11);
            nonIndexedCube[5] =
                new VertexPositionNormalTexture(topRightFront, ZP, Texcoords10);

            // Front face
            nonIndexedCube[6] =
                new VertexPositionNormalTexture(topLeftBack, ZN, Texcoords00);
            nonIndexedCube[7] =
                new VertexPositionNormalTexture(topRightBack, ZN, Texcoords00);
            nonIndexedCube[8] =
                new VertexPositionNormalTexture(bottomLeftBack, ZN, Texcoords00);
            nonIndexedCube[9] =
                new VertexPositionNormalTexture(bottomLeftBack, ZN, Texcoords00);
            nonIndexedCube[10] =
                new VertexPositionNormalTexture(topRightBack, ZN, Texcoords00);
            nonIndexedCube[11] =
                new VertexPositionNormalTexture(bottomRightBack, ZN, Texcoords00);

            // Top face
            nonIndexedCube[12] =
                new VertexPositionNormalTexture(topLeftFront, XP, Texcoords00);
            nonIndexedCube[13] =
                new VertexPositionNormalTexture(topRightBack, XP, Texcoords00);
            nonIndexedCube[14] =
                new VertexPositionNormalTexture(topLeftBack, XP, Texcoords00);
            nonIndexedCube[15] =
                new VertexPositionNormalTexture(topLeftFront, XP, Texcoords00);
            nonIndexedCube[16] =
                new VertexPositionNormalTexture(topRightFront, XP, Texcoords00);
            nonIndexedCube[17] =
                new VertexPositionNormalTexture(topRightBack, XP, Texcoords00);

            // Bottom face
            nonIndexedCube[18] =
                new VertexPositionNormalTexture(bottomLeftFront, XN, Texcoords00);
            nonIndexedCube[19] =
                new VertexPositionNormalTexture(bottomLeftBack, XN, Texcoords00);
            nonIndexedCube[20] =
                new VertexPositionNormalTexture(bottomRightBack, XN, Texcoords00);
            nonIndexedCube[21] =
                new VertexPositionNormalTexture(bottomLeftFront, XN, Texcoords00);
            nonIndexedCube[22] =
                new VertexPositionNormalTexture(bottomRightBack, XN, Texcoords00);
            nonIndexedCube[23] =
                new VertexPositionNormalTexture(bottomRightFront, XN, Texcoords00);

            // Left face
            nonIndexedCube[24] =
                new VertexPositionNormalTexture(topLeftFront, YP, Texcoords00);
            nonIndexedCube[25] =
                new VertexPositionNormalTexture(bottomLeftBack, YP, Texcoords00);
            nonIndexedCube[26] =
                new VertexPositionNormalTexture(bottomLeftFront, YP, Texcoords00);
            nonIndexedCube[27] =
                new VertexPositionNormalTexture(topLeftBack, YP, Texcoords00);
            nonIndexedCube[28] =
                new VertexPositionNormalTexture(bottomLeftBack, YP, Texcoords00);
            nonIndexedCube[29] =
                new VertexPositionNormalTexture(topLeftFront, YP, Texcoords00);

            // Right face
            nonIndexedCube[30] =
                new VertexPositionNormalTexture(topRightFront, YN, Texcoords00);
            nonIndexedCube[31] =
                new VertexPositionNormalTexture(bottomRightFront, YN, Texcoords00);
            nonIndexedCube[32] =
                new VertexPositionNormalTexture(bottomRightBack, YN, Texcoords00);
            nonIndexedCube[33] =
                new VertexPositionNormalTexture(topRightBack, YN, Texcoords00);
            nonIndexedCube[34] =
                new VertexPositionNormalTexture(topRightFront, YN, Texcoords00);
            nonIndexedCube[35] =
                new VertexPositionNormalTexture(bottomRightBack, YN, Texcoords00);

            // Create/Set vertex buffer.
            cubeVertexBuffer = new VertexBuffer(
                graphics.GraphicsDevice,
                VertexPositionNormalTexture.SizeInBytes * nonIndexedCube.Length,
                ResourceUsage.None,
                ResourceManagementMode.Automatic
            );
            cubeVertexBuffer.SetData<VertexPositionNormalTexture>(nonIndexedCube);
        }

        /// <summary>
        /// Initialize the cube basic effect.
        /// </summary>
        private void InitializeCubeEffect()
        {
            cubeVertexDeclaration = new VertexDeclaration(graphics.GraphicsDevice,
                                                    VertexPositionNormalTexture.VertexElements);

            cubeBasicEffect = new BasicEffect(graphics.GraphicsDevice, null);
            cubeBasicEffect.Alpha = 1.0f;
            cubeBasicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
            cubeBasicEffect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
            cubeBasicEffect.SpecularPower = 5.0f;
            cubeBasicEffect.AmbientLightColor = new Vector3(0.75f, 0.75f, 0.75f);
            cubeBasicEffect.EnableDefaultLighting();
            cubeBasicEffect.LightingEnabled = true;
            cubeBasicEffect.TextureEnabled = true;
            cubeBasicEffect.World = Matrix.Identity;
            cubeBasicEffect.View = view;
            cubeBasicEffect.Projection = projection;
        }

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            content = new ContentManager(Services);

            Window.AllowUserResizing = true;

            Window.ClientSizeChanged += new EventHandler(Window_ClientSizeChanged);

            
        }

        void Window_ClientSizeChanged(object sender, EventArgs e)
        {
            //throw new Exception("The method or operation is not implemented.");

            // Update projection matrix.
            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                                    (float)Window.ClientBounds.Width / Window.ClientBounds.Height,
                                    0.1f, 10000.0f);
        }

        /// <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()
        {
            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                                    (float)Window.ClientBounds.Width / Window.ClientBounds.Height,
                                    1.0f, 10000.0f);

            InitializeCubeEffect();

            base.Initialize();
        }

        /// <summary>
        /// Load your graphics content. If loadAllContent is true, you should
        /// load content from both ResourceManagementMode pools. Otherwise, just
        /// load ResourceManagementMode.Manual content.
        /// </summary>
        /// <param name="loadAllContent">Which type of content to load.</param>
        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            // Load any ResourceManagementMode.Automatic content.
            if (loadAllContent)
            {
#if HLSL
                // Load HLSL fx effect.
                // Mesh and HLSL effect must use the same vertex format:
                // it is not possible to change modelMeshPart.VertexDeclaration
                // to match the one used in HLSL, this property is read only.
                // But is possible to change this property writing a custom
                // processor to use loading mesh.
                // The commented HLSL loading code use not builded resource.
                /*CompiledEffect compiledEffect = 
                    Effect.CompileEffectFromFile(@"Content\Effects\effect.fx", null, null,
                                                 CompilerOptions.None, TargetPlatform.Windows);
                hlslEffect = new Effect(graphics.GraphicsDevice, compiledEffect.GetEffectCode(),
                                        CompilerOptions.None, null);*/
                hlslEffect = content.Load<Effect>(@"Content\Effects\effect");
#endif
                //
                // FBX files.
                //
                
                ship = content.Load<Model>(@"Content\Models\p1_wedge");
#if HLSL
                // The method content.Load<Model> set also texture in BasicEffect, but if we
                // use HLSL fx file we must load texture explicitly.
                // Then for HLSL project need a reference to texture, so next code row
                // can load it.
                // If the project doesn't use HLSL fx file it possible to delete reference
                // to texture from the project, but leaving texture in folder "Content\Textures":
                // building project the texture referenced inside X and FBX file will be
                // builded in XNb file in the right output folder (bin\x86\Debug\Content\textures).
                textureShip = content.Load<Texture2D>(@"Content\Textures\wedge_p1_diff_v1");

                // Set the HLSL effect to use.
                foreach (ModelMesh modelMesh in ship.Meshes)
                {
                    foreach (ModelMeshPart modelMeshPart in modelMesh.MeshParts)
                    {
                        modelMeshPart.Effect = hlslEffect;
                    }
                }
#endif
                //
                // X files.
                //

                // Mesh and texture.
                asteriod = content.Load<Model>(@"Content\Models\asteroid1");
#if HLSL
                // With HLSL fx file it need to load and set texture explicitly.
                texture = content.Load<Texture2D>(@"Content\Textures\asteroid1");
#endif
                // Setup Meshes Effects.
                foreach (ModelMesh modelMesh in asteriod.Meshes)
                {
#if HLSL
                    // Set the HLSL effect to use.
                    foreach (ModelMeshPart modelMeshPart in modelMesh.MeshParts)
                    {
                        modelMeshPart.Effect = hlslEffect;
                    }
#else
                    // Set the basic effect to use.
                    foreach (BasicEffect effect in modelMesh.Effects)
                    {
                        effect.Alpha = 1.0f;
                        effect.DiffuseColor = new Vector3(0.72f, 0.72f, 0.72f);
                        effect.AmbientLightColor = new Vector3(0.2f, 0.2f, 0.2f);
                        effect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
                        //effect.SpecularPower = 0.5f;
                        effect.SpecularPower = 20.0f;
                        effect.AmbientLightColor = new Vector3(0.25f, 0.25f, 0.25f);
                        effect.EnableDefaultLighting();

                        // The method content.Load<Model>, when using BasicEffect do draw
                        // the model, doesn't need to load and set texture explicitly, 
                        // becouse it found texture reference inside X or FBX file.
                        //effect.TextureEnabled = true;
                        //effect.Texture = texture;
                    }
#endif
                }

                //
                // Low level cube.
                //

                InitializeCube();
                cubeTexture = content.Load<Texture2D>(@"Content\Textures\Texture");
                cubeBasicEffect.Texture = cubeTexture;
            }

            // TODO: Load any ResourceManagementMode.Manual content
        }

        /// <summary>
        /// Unload your graphics content. If unloadAllContent is true, you should
        /// unload content from both ResourceManagementMode pools.  Otherwise, just
        /// unload ResourceManagementMode.Manual content. Manual content will get
        /// Disposed by the GraphicsDevice during a Reset.
        /// </summary>
        /// <param name="unloadAllContent">Which type of content to unload.</param>
        protected override void UnloadGraphicsContent(bool unloadAllContent)
        {
            if (unloadAllContent == true)
            {
                content.Unload();
            }
        }

        /// <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 default game to exit on Xbox 360 and Windows.
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            // Update meshes rotation.
            modelrotation += gameTime.ElapsedGameTime.Milliseconds * MathHelper.ToRadians(0.1f);

            // Player moving commands.
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
            if (Keyboard.GetState().IsKeyDown(Keys.LeftShift))
                elapsed *= 4;
            if (Keyboard.GetState().IsKeyDown(Keys.Up))
            {
                view *= Matrix.CreateTranslation(0, 0, 1000 * elapsed);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Down))
            {
                view *= Matrix.CreateTranslation(0, 0, -1000 * elapsed);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Home))
            {
                view *= Matrix.CreateTranslation(0, -1000 * elapsed, 0);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.End))
            {
                view *= Matrix.CreateTranslation(0, 1000 * elapsed, 0);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                if (Keyboard.GetState().IsKeyDown(Keys.LeftAlt))
                    view *= Matrix.CreateTranslation(1000 * elapsed, 0, 0);
                else
                    view *= Matrix.CreateRotationY(-elapsed);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                if (Keyboard.GetState().IsKeyDown(Keys.LeftAlt))
                    view *= Matrix.CreateTranslation(-1000 * elapsed, 0, 0);
                else
                    view *= Matrix.CreateRotationY(elapsed);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.PageUp))
            {
                view *= Matrix.CreateRotationX(-elapsed);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.PageDown))
            {
                view *= Matrix.CreateRotationX(elapsed);
            }

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

            graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;

            // **********************************************************************
            // Draw ship with BasicEffect                                           *
            // **********************************************************************

            // A model can have multiple meshes, so loop.
            foreach (ModelMesh mesh in ship.Meshes)
            {
#if HLSL
                // Set technique and parameters.
                foreach (Effect currentEffect in mesh.Effects)
                {
                    currentEffect.CurrentTechnique = currentEffect.Techniques["TransformAndTexture"];
                    currentEffect.Parameters["WorldViewProj"].SetValue(
                            Matrix.CreateRotationY(modelrotation) * mesh.ParentBone.Transform
                            * Matrix.CreateTranslation(shipPosition) * view * projection);
                    currentEffect.Parameters["UserTexture"].SetValue(textureShip);
                }
#else
                // Set basic effet properties.
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = 
                        Matrix.CreateRotationY(modelrotation) * mesh.ParentBone.Transform
                        * Matrix.CreateTranslation(shipPosition);
                    effect.View = view;
                    effect.Projection = projection;
                }
#endif                
                // Draw the mesh, will use the effects set above.
                mesh.Draw();
            }

            // **********************************************************************
            // Draw asteroid with BasicEffect                                       *
            // **********************************************************************

            foreach (ModelMesh modelMesh in asteriod.Meshes)
            {
#if HLSL
                // Set technique and parameters.
                foreach (Effect currentEffect in modelMesh.Effects)
                {
                    currentEffect.CurrentTechnique = currentEffect.Techniques["TransformAndTexture"];
                    currentEffect.Parameters["WorldViewProj"].SetValue(
                                    Matrix.CreateRotationY(modelrotation) *
                                    Matrix.CreateTranslation(-1500, 0, 0) * view * projection);
                    currentEffect.Parameters["UserTexture"].SetValue(texture);
                }
#else
                // Set basic effet properties.
                foreach (BasicEffect effect in modelMesh.Effects)
                {
                    //Transform
                    effect.View = view;
                    effect.Projection = projection;
                    effect.World = Matrix.CreateRotationY(modelrotation) * 
                                    Matrix.CreateTranslation(-1500, 0, 0);
                }
#endif
                modelMesh.Draw();
            }

            // **********************************************************************
            // Draw cube using vertex buffer                                        *
            // **********************************************************************

            graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
            graphics.GraphicsDevice.VertexDeclaration = cubeVertexDeclaration;

#if HLSL
            hlslEffect.CurrentTechnique = hlslEffect.Techniques["TransformAndTexture"];
            hlslEffect.Parameters["WorldViewProj"].SetValue(
                            Matrix.Identity * view * projection);
            hlslEffect.Parameters["UserTexture"].SetValue(cubeTexture);
            hlslEffect.Begin();
            foreach (EffectPass pass in hlslEffect.CurrentTechnique.Passes)
            {
                pass.Begin();

                graphics.GraphicsDevice.Vertices[0].SetSource(cubeVertexBuffer, 0,
                                                        VertexPositionNormalTexture.SizeInBytes);
                graphics.GraphicsDevice.DrawPrimitives(
                    PrimitiveType.TriangleList,
                    0,
                    12
                );

                pass.End();
            }
            hlslEffect.End();

#else
            cubeBasicEffect.View = view;
            cubeBasicEffect.Begin();
            foreach (EffectPass pass in cubeBasicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();

                graphics.GraphicsDevice.Vertices[0].SetSource(cubeVertexBuffer, 0,
                                                        VertexPositionNormalTexture.SizeInBytes);
                graphics.GraphicsDevice.DrawPrimitives(
                    PrimitiveType.TriangleList,
                    0,
                    12
                );

                pass.End();
            }
            cubeBasicEffect.End();
#endif
            base.Draw(gameTime);
        }
    }
}