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;

		PhysicObject currentItem;

		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 シーン構築関連
	partial class Game1 {
		Random random = new Random();
		readonly List<PhysicObject> items = new List<PhysicObject>();
		List<Constraint> constraints = new List<Constraint>();

		void Add(PhysicObject item) {
			Components.Add(item);
			items.Add(item);

			var previousItem = currentItem;
			currentItem = item;
			if (previousItem != null) {
				var constraint = new ConstraintPoint(
					previousItem.PhysicsBody, previousItem.Scale * 0.5f,
					currentItem.PhysicsBody, currentItem.Scale * -0.5f, 0.01f, 0.01f);
				constraint.EnableConstraint();
				constraints.Add(constraint);
			}
		}

		void InitializeScene() {
			// カメラを設定する
			context.Camera.Position = new Vector3(0, 10, 10);
			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 constraint in constraints) {
				constraint.DisableConstraint();
			}
			constraints.Clear();

			currentItem = null;

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

			// 固定の床を作る
			var floor = new BoxObject(this, context, Content.Load<Model>("Crate"),
				new Vector3(5, 1, 5), 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.Delete)) InitializeScene();
		}
	}
	#endregion
}
