using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace RigidBody {

	public partial class RigidBodyGame : Microsoft.Xna.Framework.Game {
		readonly GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;
		PlaneRenderer planeRenderer;
		Quaternion cameraRotation = Quaternion.Identity;
		Matrix view;
		Matrix projection;
		Model model;
		Model boxModel;

		/// <summary>
		/// 物体のリスト
		/// </summary>
		readonly List<Body> bodies = new List<Body>();

		/// <summary>
		/// 重力
		/// </summary>
		readonly Vector3 gravity = new Vector3(0, -9.8f, 0);
		/// <summary>
		/// 平面1
		/// </summary>
		Plane plane1 = new Plane(Vector3.Normalize(new Vector3(0.5f, 1, 0)), 0);
		/// <summary>
		/// 平面2
		/// </summary>
		Plane plane2 = new Plane(Vector3.Normalize(new Vector3(-0.5f, 1, 0)), 0);

		Random random = new Random();

		bool fbEuler = true;

		CollisionMethod collisionMethod = CollisionMethod.Penalty;

		KeyboardState previousKeyboardState;
		KeyboardState currentKeyboardState;

		public RigidBodyGame() {
			graphics = new GraphicsDeviceManager(this);
			graphics.PreferMultiSampling = true;
			Content.RootDirectory = "Content";
		}

		protected override void LoadContent() {
			spriteBatch = new SpriteBatch(GraphicsDevice);
			model = Content.Load<Model>("Ball");
			boxModel = Content.Load<Model>("CheckedBox");
			planeRenderer = new PlaneRenderer(GraphicsDevice, ((BasicEffect)model.Meshes[0].Effects[0]).Texture);
			TargetElapsedTime = TimeSpan.FromSeconds(1.0 / 60);
			InitializeScene();
		}

		/// <summary>
		/// シーン設定
		/// </summary>
		void InitializeScene() {
			bodies.Clear();
			AddSphere();
			AddBox();
		}
		void AddSphere() {
			var body = new Body();
			var radius = 3.0f;
			body.MassProperty = MassProperty.CreateSphere(radius, 1);
			body.Position = new Vector3(15, 35, 0);
			body.Shape = new Sphere() { Radius = radius, };
			body.Visual = model;
			bodies.Add(body);
		}
		void AddBox() {
			var body = new Body();
			var scale = new Vector3(3.0f, 3.5f, 4.0f);
			body.Shape = new Box(scale);
			body.MassProperty = MassProperty.CreateBox(scale, 0.5f);
			body.Position = new Vector3(-25, 155, 0);
			body.Rotation = Quaternion.CreateFromAxisAngle(Vector3.Normalize(new Vector3(2, 3, 1)),
				(float)random.NextDouble() * MathHelper.TwoPi);
			body.Visual = boxModel;
			bodies.Add(body);
		}

		/// <summary>
		/// 平面と各剛体との衝突
		/// </summary>
		/// <param name="plane"></param>
		void Collides(ref Plane plane, float elapsed) {
			foreach (var body in bodies) {
				body.Shape.Collides(body, ref plane, collisionMethod, elapsed);
			}
		}

		/// <summary>
		/// ユーザー操作に応答する。
		/// </summary>
		void HandleInput() {
			previousKeyboardState = currentKeyboardState;
			currentKeyboardState = Keyboard.GetState();

			if (currentKeyboardState.IsKeyDown(Keys.Space)) {
				InitializeScene();
			}
			if (currentKeyboardState.IsKeyDown(Keys.A)) {
				cameraRotation = Quaternion.CreateFromAxisAngle(Vector3.Up, -0.01f) * cameraRotation;
				cameraRotation.Normalize();
			}
			if (currentKeyboardState.IsKeyDown(Keys.D)) {
				cameraRotation = Quaternion.CreateFromAxisAngle(Vector3.Up, +0.01f) * cameraRotation;
				cameraRotation.Normalize();
			}
			if (currentKeyboardState.IsKeyDown(Keys.M) && previousKeyboardState.IsKeyUp(Keys.M)) {
				fbEuler = !fbEuler;
			}
			if (currentKeyboardState.IsKeyDown(Keys.C) && previousKeyboardState.IsKeyUp(Keys.C)) {
				switch (collisionMethod) {
				case CollisionMethod.Penalty: collisionMethod = CollisionMethod.Impulse; break;
				case CollisionMethod.Impulse: collisionMethod = CollisionMethod.Penalty; break;
				}
			}

			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
				this.Exit();
		}


		protected override void Update(GameTime gameTime) {
			HandleInput();
			UpdateBodies(gameTime.ElapsedGameTime.TotalSeconds, 20);
			base.Update(gameTime);
		}

		void SetupModel(Model model) {
			foreach (BasicEffect effect in model.Meshes[0].Effects) {
				effect.PreferPerPixelLighting = true;
				effect.EnableDefaultLighting();
				effect.World = Matrix.Identity;
				effect.View = view;
				effect.Projection = projection;
			}
		}

		void SetupDrawing() {
			// ビュー
			Matrix.CreateFromQuaternion(ref cameraRotation, out view);
			view *= Matrix.CreateLookAt(new Vector3(0, 30, -100), new Vector3(0, 30, 0), Vector3.Up);
			// 投影
			projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
				GraphicsDevice.Viewport.AspectRatio, 1, 10000);

			SetupModel(model);
			SetupModel(boxModel);
		}

		/// <summary>
		/// 描画する。
		/// </summary>
		/// <param name="gameTime"></param>
		protected override void Draw(GameTime gameTime) {
			GraphicsDevice.Clear(Color.CornflowerBlue);

			SetupDrawing();

			planeRenderer.Draw(plane1, 100, view, projection);
			planeRenderer.Draw(plane2, 100, view, projection);

			foreach (var body in bodies) {
				var visual = body.Visual;
				var shape = body.Shape;
				foreach (BasicEffect effect in visual.Meshes[0].Effects) {
					effect.World = body.Transform;
					effect.View = view;
				}

				visual.Meshes[0].Draw();
			}

			base.Draw(gameTime);
		}
	}

	public partial class RigidBodyGame {
		/// <summary>
		/// 剛体運動の更新
		/// </summary>
		/// <param name="elapsed">合計の経過時間</param>
		/// <param name="iterations">分割数</param>
		void UpdateBodies(double elapsed, int iterations) {
			var elapsedTime = (float)(elapsed / iterations);
			for (int i = 0; i < iterations; i++) {
				// 力を初期化する。
				foreach (var body in bodies) {
					body.ClearForce();
				}

				// 衝突
				Collides(ref plane1, elapsedTime);
				Collides(ref plane2, elapsedTime);

				// 重力
				foreach (var body in bodies) {
					body.AddForce(body.Position, gravity * body.MassProperty.Mass);
				}

				// 運動
				foreach (var body in bodies) {
					body.Update(elapsedTime, fbEuler);
				}

			}
		}
	}

}
