using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using JigLibX.Physics;
using JigLibX.Collision;
using JigLibXTutorial.PhysicObjects;
using System;
using System.Linq;
using System.Collections.Generic;

namespace JigLibXTutorial {

	public class Context {
		public Context(Camera camera, DebugDrawer debugDrawer) {
			this.Camera = camera;
			this.DebugDrawer = debugDrawer;
		}
		public readonly Camera Camera;
		public readonly DebugDrawer DebugDrawer;
	}

	public partial class Game1 : Microsoft.Xna.Framework.Game {
		readonly GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;
		PhysicsSystem physicSystem = new PhysicsSystem();
		readonly Context context;

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

			physicSystem.CollisionSystem = new CollisionSystemSAP();

			physicSystem.EnableFreezing = true;
			physicSystem.SolverType = PhysicsSystem.Solver.Normal;
			physicSystem.CollisionSystem.UseSweepTests = true;

			physicSystem.NumCollisionIterations = 10;
			physicSystem.NumContactIterations = 10;
			physicSystem.NumPenetrationRelaxtionTimesteps = 15;

			var camera = new Camera(this);
			context = new Context(new Camera(this), new DebugDrawer(this, camera));
			Components.Add(context.Camera);
			Components.Add(context.DebugDrawer);
		}

		protected override void LoadContent() {
			spriteBatch = new SpriteBatch(GraphicsDevice);
			InitializeScene();
			AddCrate();
		}

		protected override void Update(GameTime gameTime) {
			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
				this.Exit();

			HandleInput();

			physicSystem.Integrate((float)gameTime.ElapsedGameTime.TotalSeconds);

			base.Update(gameTime);
		}

		protected override void Draw(GameTime gameTime) {
			GraphicsDevice.Clear(Color.CornflowerBlue);
			base.Draw(gameTime);
		}
	}

	#region コントローラの例
	class SampleController : Controller {
		Body body;
		Vector3 force;
		Vector3 torque;

		public SampleController(Body body, Vector3 force, Vector3 torque) {
			this.body = body;
			this.force = force;
			this.torque = torque;
		}
		public override void UpdateController(float dt) {
			if (body == null) return;
			if (force != Vector3.Zero || torque != Vector3.Zero) {
				body.AddWorldForce(force);
				body.AddWorldTorque(torque);
				if (!body.IsActive) body.SetActive();
			}
		}
	}
	#endregion

	#region シーン構築関連
	partial class Game1 {
		Random random = new Random();
		readonly List<PhysicObject> items = new List<PhysicObject>();
		readonly List<Controller> controllers = new List<Controller>();

		void Add(PhysicObject item) {
			Components.Add(item);
			items.Add(item);
			var controller = new SampleController(item.PhysicsBody, new Vector3(0, 12, 0), Vector3.Zero);
			controller.EnableController();
			controllers.Add(controller);
		}

		void InitializeScene() {
			// カメラを設定する
			context.Camera.Position = new Vector3(0, 20, 20);
			context.Camera.Target = new Vector3(0, 0, 0);

			// 古いボディとそのコンポーネントを削除する
			foreach (var item in items) {
				Components.Remove(item);
				item.PhysicsBody.DisableBody();
				item.Dispose();
			}
			items.Clear();

			foreach (var controller in controllers) {
				physicSystem.RemoveController(controller);
			}
			controllers.Clear();

			// 重力
			physicSystem.Gravity = new Vector3(0, -9.8f, 0);

			// 固定の床を作る
			var floor = new BoxObject(this, context, Content.Load<Model>("Crate"),
				new Vector3(10, 1, 10), Matrix.Identity, Vector3.Zero);
			floor.PhysicsBody.Immovable = true;
			Add(floor);
		}
		/// <summary>
		/// 木箱を追加する。
		/// </summary>
		void AddCrate() {
			var crate = new BoxObject(this, context, Content.Load<Model>("Crate"),
				new Vector3(1, 1, 1),
				Matrix.CreateRotationX((float)random.NextDouble()) *
				Matrix.CreateRotationY((float)random.NextDouble()), new Vector3(0, 5, 0));
			Add(crate);
		}
	}
	#endregion

	#region 入力関連
	partial class Game1 {
		KeyboardState previous;
		KeyboardState current;
		bool IsKeyPress(Keys key) {
			return current.IsKeyDown(key) && previous.IsKeyUp(key);
		}
		public void HandleInput() {
			previous = current;
			current = Keyboard.GetState();
			if (IsKeyPress(Keys.Space)) AddCrate();
			if (IsKeyPress(Keys.X)) {
				foreach (var controller in controllers)
					controller.EnableController();
			}
			if (IsKeyPress(Keys.Z)) {
				foreach (var controller in controllers)
					controller.DisableController();
			}
			if (IsKeyPress(Keys.Delete)) InitializeScene();
		}
	}
	#endregion
}
