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();
		}

		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<HingeJoint> joints = new List<HingeJoint>();

		void Add(PhysicObject item, Vector3 hingePosition) {
			Components.Add(item);
			items.Add(item);

			var previousItem = currentItem;
			currentItem = item;
			if (previousItem != null) {
				// ジョイントを作成する
				var joint = new HingeJoint();
				joint.Initialise(
					previousItem.PhysicsBody, currentItem.PhysicsBody,
					new Vector3(0, 0, 1),
					hingePosition, 1.0f, 180, -180, 0.1f, 0.5f);
				joint.EnableHinge();
				joints.Add(joint);
			}
		}

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

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

			foreach (var joint in joints) {
				joint.DisableController();
			}
			joints.Clear();

			currentItem = null;

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

			// 固定の床を作る
			var floor = new BoxObject(this, context, Content.Load<Model>("Crate"),
				new Vector3(2, 2, 3), Matrix.Identity, Vector3.Zero);
			floor.PhysicsBody.Immovable = true;
			Add(floor, Vector3.Zero);

			AddCrate(new Vector3(3, 0, 0), new Vector3(0.0f, 0, 0));
			AddCrate(new Vector3(6, 0, 0), new Vector3(1.5f, 0, 0));
			AddCrate(new Vector3(9, 0, 0), new Vector3(1.5f, 0, 0));
			AddCrate(new Vector3(12, 0, 0), new Vector3(1.5f, 0, 0));
			AddCrate(new Vector3(15, 0, 0), new Vector3(1.5f, 0, 0));
		}
		/// <summary>
		/// 木箱を追加する。
		/// </summary>
		void AddCrate(Vector3 position, Vector3 hingePosition) {
			var crate = new BoxObject(this, context, Content.Load<Model>("Crate"),
				new Vector3(2, 0.25f, 2), Matrix.Identity, position);
			Add(crate, hingePosition);
		}
	}
	#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.Delete)) InitializeScene();
		}
	}
	#endregion
}
