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 _3DTest02
{
    class Drawable
    {
        public Vector3 Position {get; set;}
        public float ScaleFactor { get; set; }
        public Matrix View { get; set; }
        public Matrix Projection { get; set; }

        public Drawable()
        {
            Position = Vector3.Zero;
            ScaleFactor = 1.0f;
        }

        public Drawable(Vector3 position)
        {
            Position = position;
            ScaleFactor = 1.0f;
        }

        public Drawable(Vector3 position, float scale)
        {
            Position = position;
            ScaleFactor = scale;
        }

        public virtual void Draw()
        {
        }

    }

    class Movable : Drawable
    {
        public Vector3 OriginalPosition { get; set; }

        public Movable(Vector3 position) : base(position)
        {
            OriginalPosition = position;
        }

        public Movable(Vector3 position, float scale)
            : base(position, scale)
        {
            OriginalPosition = position;
        }

        public virtual void Update() { }

        public void Reset()
        {
            Position = OriginalPosition;
        }

    }

    class DrawableMesh : Drawable
    {
        public Model Mesh { get; set; }

        public DrawableMesh(Vector3 position)
            : base(position)
        {
        }

        public DrawableMesh(Vector3 position, float scale)
            : base(position, scale)
        {
        }

        //public virtual void Draw() { }

    }

    class movableMesh : Movable
    {
        public Model Mesh { get; set; }
        public string Sti { get; set; } 

        public movableMesh(Vector3 position)
            : base(position)
        {
        }

        public movableMesh(Vector3 position, float scale, string m)
            : base(position, scale)
        {
        }

    }

    class Duck : DrawableMesh
    {
        private float rotateX = 0.0f;
        private float rotateY = 0.0f;
        private float rotateZ = 0.0f;

        public Duck(Vector3 position, float scale)
            : base(position, scale)
        {
        }

        public void Draw(Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in Mesh.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = Matrix.Identity * Matrix.CreateScale(ScaleFactor) *
                        Matrix.CreateRotationX(rotateX) * Matrix.CreateRotationY(rotateY) *
                        Matrix.CreateRotationZ(rotateZ) * Matrix.CreateTranslation(Position);
                    effect.View = view;
                    effect.Projection = projection;
                }
                mesh.Draw();
            }
        }
    }

    class Line
    {
        private VertexPositionColor[] point = new VertexPositionColor[2];
        private VertexDeclaration basicEffectVertexDeclaration;

        public Line(Vector3 start, Vector3 slut, Color c)
        {
            point[0] = new VertexPositionColor(start, c);
            point[1] = new VertexPositionColor(slut, c);            
        }

        public void Draw(GraphicsDevice graphics, BasicEffect basicEffect)
        {
            basicEffectVertexDeclaration =
                new VertexDeclaration(graphics, VertexPositionColor.VertexElements);
            graphics.VertexDeclaration = basicEffectVertexDeclaration;
            basicEffect.VertexColorEnabled = true;
            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphics.DrawUserPrimitives(PrimitiveType.LineList, point, 0, 1);
                pass.End();
            }
            basicEffect.VertexColorEnabled = false;
        }
    }

    class Ground : Drawable
    {
        private VertexPositionTexture[] vertices;
        private VertexDeclaration basicEffectVertexDeclaration;
        public Texture2D Billede { get; set; }

        public Ground() : base(Vector3.Zero, 1.0f)
        {
            InitializeVertices();
        }

        public Ground(float scaling) : base(Vector3.Zero, scaling)
        {
            InitializeVertices();
        }

        private void InitializeVertices()
        {
            vertices = new VertexPositionTexture[6];
            vertices[0].Position = new Vector3(-10.0f, 0.0f, -10.0f);
            vertices[0].TextureCoordinate = new Vector2(0.0f, 0.0f);
            vertices[1].Position = new Vector3(10.0f, 0.0f, 10.0f);
            vertices[1].TextureCoordinate = new Vector2(1.0f, 1.0f);
            vertices[2].Position = new Vector3(-10.0f, 0.0f, 10.0f);
            vertices[2].TextureCoordinate = new Vector2(0.0f, 1.0f);
            vertices[3].Position = new Vector3(-10.0f, 0.0f, -10.0f);
            vertices[3].TextureCoordinate = new Vector2(0.0f, 0.0f);
            vertices[4].Position = new Vector3(10.0f, 0.0f, -10.0f);
            vertices[4].TextureCoordinate = new Vector2(1.0f, 0.0f);
            vertices[5].Position = new Vector3(10.0f, 0.0f, 10.0f);
            vertices[5].TextureCoordinate = new Vector2(1.0f, 1.0f);
        }

        public void Draw(GraphicsDevice graphics, BasicEffect effect)
        {
            basicEffectVertexDeclaration =
                new VertexDeclaration(graphics, VertexPositionTexture.VertexElements);
            graphics.VertexDeclaration = basicEffectVertexDeclaration;
            effect.Texture = Billede;
            effect.TextureEnabled = true;
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphics.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 2);
                pass.End();
            }
            effect.TextureEnabled = false;
        }
    }

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        KeyboardState oldState;
        KeyboardState newState;
        SpriteFont font; 

        // Variabler til View og Projektion
        private Vector3 camera;
        private Vector3 target;
        private Matrix view;
        private float aspectRatio;
        private Matrix projection;

        Vector3 originalPosition = new Vector3(0.0f, 0.0f, 1.0f);
        Vector3 actualPosition = Vector3.Zero;
        float rotateX = 0.0f;
        float rotateY = 0.0f;
        float rotateZ = 0.0f;

        private int mainFunction = 0;
        private int subFunction = 0;

        // X, Y og Z akser
        private Matrix coordinateSystem;
        private Line xAkse, yAkse, zAkse;

        BasicEffect basicEffect;

        private Ground ground = new Ground();
        private Matrix duckWorld;
        Model m;
        Duck d = new Duck(new Vector3(2.0f, 0.0f, -2.0f), 5.0f);
        Ground g = new Ground();
        //Model box1;
        //Model box2;

        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
            // Initialisering af View og Projektion
            camera = new Vector3(5.0f, 5.0f, 5.0f);
            target = new Vector3(0.0f, 0.0f, 0.0f);
            view = Matrix.CreateLookAt(camera, target, Vector3.UnitY);
            aspectRatio = graphics.PreferredBackBufferWidth / graphics.PreferredBackBufferHeight;
            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), aspectRatio, 0.1f, 1000.0f);
            coordinateSystem = Matrix.Identity;
            oldState = Keyboard.GetState();

            // Definition af BasicEffect til tegning af diverse primitiver
            basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
            //basicEffect.VertexColorEnabled = true;
            basicEffect.Alpha = 1.0f;
            basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
            basicEffect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
            basicEffect.SpecularPower = 5.0f;
            basicEffect.AmbientLightColor = new Vector3(0.75f, 0.75f, 0.75f);

            basicEffect.DirectionalLight0.Enabled = true;
            basicEffect.DirectionalLight0.DiffuseColor = Vector3.One;
            basicEffect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(1.0f, -1.0f, -1.0f));
            basicEffect.DirectionalLight0.SpecularColor = Vector3.One;

            basicEffect.DirectionalLight1.Enabled = true;
            basicEffect.DirectionalLight1.DiffuseColor = new Vector3(0.5f, 0.5f, 0.5f);
            basicEffect.DirectionalLight1.Direction = Vector3.Normalize(new Vector3(-1.0f, -1.0f, 1.0f));
            basicEffect.DirectionalLight1.SpecularColor = new Vector3(0.5f, 0.5f, 0.5f);

            basicEffect.LightingEnabled = true;

            basicEffect.World = Matrix.Identity;
            //basicEffect.View = view;
            basicEffect.Projection = projection;
            //basicEffectVertexDeclaration =
            //    new VertexDeclaration(graphics.GraphicsDevice, VertexPositionColor.VertexElements);
            //graphics.GraphicsDevice.VertexDeclaration = basicEffectVertexDeclaration;

            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
            // Data til koordinatsystemet
            //xAxis[0] = new VertexPositionColor(new Vector3(0.0f, 0.0f, 0.0f), Color.Green);
            //xAxis[1] = new VertexPositionColor(new Vector3(10.0f, 0.0f, 0.0f), Color.Green);
            //yAxis[0] = new VertexPositionColor(new Vector3(0.0f, 0.0f, 0.0f), Color.Red);
            //yAxis[1] = new VertexPositionColor(new Vector3(0.0f, 10.0f, 0.0f), Color.Red);
            //zAxis[0] = new VertexPositionColor(new Vector3(0.0f, 0.0f, 0.0f), Color.Blue);
            //zAxis[1] = new VertexPositionColor(new Vector3(0.0f, 0.0f, 10.0f), Color.Blue);
            xAkse = new Line(Vector3.Zero, new Vector3(10.0f, 0.0f, 0.0f), Color.Green);
            yAkse = new Line(Vector3.Zero, new Vector3(0.0f, 10.0f, 0.0f), Color.Red);
            zAkse = new Line(Vector3.Zero, new Vector3(0.0f, 0.0f, 10.0f), Color.Blue);
            font = Content.Load<SpriteFont>("myFont");
            m = Content.Load<Model>("Models\\Ducky_highres");
            ground.Billede = Content.Load<Texture2D>("Textures\\2001");
            duckWorld = Matrix.Identity * Matrix.CreateScale(5.0f, 5.0f, 5.0f);
            d.Mesh = Content.Load<Model>("Models\\Ducky_highres");
        }

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

        /// <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
            // Get the keyboard state.
            newState = Keyboard.GetState();

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

            if (newState.IsKeyDown(Keys.M))
                if (!oldState.IsKeyDown(Keys.M))
                {
                    mainFunction = 1;
                    subFunction = 0;
                }

            if (newState.IsKeyDown(Keys.K))
                if (!oldState.IsKeyDown(Keys.K))
                {
                    mainFunction = 2;
                    subFunction = 0;
                }

            if (newState.IsKeyDown(Keys.T))
                if (!oldState.IsKeyDown(Keys.T))
                {
                    mainFunction = 3;
                    subFunction = 0;
                }

            if (newState.IsKeyDown(Keys.X))
                if (!oldState.IsKeyDown(Keys.X))
                    subFunction = 1;

            if (newState.IsKeyDown(Keys.Y))
                if (!oldState.IsKeyDown(Keys.Y))
                    subFunction = 2;

            if (newState.IsKeyDown(Keys.Z))
                if (!oldState.IsKeyDown(Keys.Z))
                    subFunction = 3;

            if (newState.IsKeyDown(Keys.Right))
            {
                if (mainFunction == 1)
                {
                    if (subFunction == 1)
                        originalPosition.X += 0.01f;
                    if (subFunction == 2)
                        originalPosition.Y += 0.01f;
                    if (subFunction == 3)
                        originalPosition.Z += 0.01f;
                }
                if (mainFunction == 2)
                {
                    if (subFunction == 1)
                        camera.X += 0.01f;
                    if (subFunction == 2)
                        camera.Y += 0.01f;
                    if (subFunction == 3)
                        camera.Z += 0.01f;
                }
                if (mainFunction == 3)
                {
                    if (subFunction == 1)
                        rotateX += 0.01f;
                    if (subFunction == 2)
                        rotateY += 0.01f;
                    if (subFunction == 3)
                        rotateZ += 0.01f;
                }
            }

            if (newState.IsKeyDown(Keys.Left))
            {
                if (mainFunction == 1)
                {
                    if (subFunction == 1)
                        originalPosition.X -= 0.01f;
                    if (subFunction == 2)
                        originalPosition.Y -= 0.01f;
                    if (subFunction == 3)
                        originalPosition.Z -= 0.01f;
                }
                if (mainFunction == 2)
                {
                    if (subFunction == 1)
                        camera.X -= 0.01f;
                    if (subFunction == 2)
                        camera.Y -= 0.01f;
                    if (subFunction == 3)
                        camera.Z -= 0.01f;
                }
                if (mainFunction == 3)
                {
                    if (subFunction == 1)
                        rotateX -= 0.01f;
                    if (subFunction == 2)
                        rotateY -= 0.01f;
                    if (subFunction == 3)
                        rotateZ -= 0.01f;
                }
            }

            oldState = newState;

            Matrix m = Matrix.CreateFromYawPitchRoll(rotateX, rotateY, rotateZ);
            actualPosition = Vector3.Transform(originalPosition, m);
            view = Matrix.CreateLookAt(camera, target, Vector3.UnitY);

            base.Update(gameTime);
        }

        //private void DrawCoordinateSystem()
        //{
        //    foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
        //    {
        //        pass.Begin();
        //        //vb = new VertexBuffer(graphics.GraphicsDevice, VertexPositionColor.SizeInBytes * xAxis.Length, BufferUsage.None);
        //        //vb.SetData<VertexPositionColor>(xAxis);
        //        //graphics.GraphicsDevice.Vertices[0].SetSource(vb, 0, VertexPositionColor.SizeInBytes);
        //        //graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 0, 1);
        //        vb = new VertexBuffer(graphics.GraphicsDevice, VertexPositionColor.SizeInBytes * yAxis.Length, BufferUsage.None);
        //        vb.SetData<VertexPositionColor>(yAxis);
        //        graphics.GraphicsDevice.Vertices[0].SetSource(vb, 0, VertexPositionColor.SizeInBytes);
        //        graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 0, 1);
        //        vb = new VertexBuffer(graphics.GraphicsDevice, VertexPositionColor.SizeInBytes * zAxis.Length, BufferUsage.None);
        //        vb.SetData<VertexPositionColor>(zAxis);
        //        graphics.GraphicsDevice.Vertices[0].SetSource(vb, 0, VertexPositionColor.SizeInBytes);
        //        graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 0, 1);
        //        pass.End();
        //    }
        //}

        private void DrawText()
        {
            spriteBatch.DrawString(font, "Camera position - X: " + camera.X.ToString() + " Y: "
                + camera.Y.ToString() + " Z: " + camera.Z.ToString(), new Vector2( 5, 5), Color.White);
            spriteBatch.DrawString(font, "Model position  - X: " + actualPosition.X.ToString() + " Y: "
                + actualPosition.Y.ToString() + " Z: " + actualPosition.Z.ToString(), new Vector2(5, 25), Color.White);
            string tekst = "";
            switch (mainFunction)
            {
                case 1:
                    tekst = "Flytning af model ";
                    break;
                case 2:
                    tekst = "Flytning af kamera ";
                    break;
                case 3:
                    tekst = "Rotation af model ";
                    break;
            }
            switch (subFunction)
            {
                case 1:
                    tekst = tekst + "X-akse";
                    break;
                case 2:
                    tekst = tekst + "Y-akse";
                    break;
                case 3:
                    tekst = tekst + "Z-akse";
                    break;
            }
            spriteBatch.DrawString(font, tekst, new Vector2(5, 45), Color.White);
        }
        
        /// <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
            basicEffect.View = view;
            //basicEffect.Projection = projection;
            basicEffect.Begin();
            xAkse.Draw(graphics.GraphicsDevice, basicEffect);
            yAkse.Draw(graphics.GraphicsDevice, basicEffect);
            zAkse.Draw(graphics.GraphicsDevice, basicEffect);
            //DrawCoordinateSystem();
            ground.Draw(graphics.GraphicsDevice, basicEffect);
            basicEffect.End();

            DrawDuck(m, duckWorld, view, projection);
            d.Draw(view, projection);

            spriteBatch.Begin();
            DrawText();
            spriteBatch.End();

            base.Draw(gameTime);
        }
    
        private void DrawDuck(Model model, Matrix duckWorld, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = duckWorld * 
                        Matrix.CreateRotationX(rotateX) * Matrix.CreateRotationY(rotateY) *
                        Matrix.CreateRotationZ(rotateZ) * Matrix.CreateTranslation(actualPosition);
                    effect.View = view;
                    effect.Projection = projection;

                }

                mesh.Draw();
            }
        }
    }
}
