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

namespace Hair {

	/// <summary>
	/// 髪の毛の表現
	/// </summary>
	public partial class HairGame : Microsoft.Xna.Framework.Game {
		readonly GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;

		readonly PhysicsSystem physicSystem = new PhysicsSystem();

		public Camera Camera;

		public DebugDrawer DebugDrawer;

		Model capsuleModel;

		readonly List<Ribbon> ribbons = new List<Ribbon>();

		PhysicsObject head;
		PhysicsObject neck;
		PhysicsObject shoulder;

		public HairGame() {
			graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";
			Components.Add(Camera = new Camera(this));
			Components.Add(DebugDrawer = new DebugDrawer(this));
			DebugDrawer.Enabled = false;

			graphics.SynchronizeWithVerticalRetrace = false;
			graphics.PreferMultiSampling = true;

			//physicSystem.CollisionSystem = new CollisionSystemGrid(32, 32, 32, 30, 30, 30);
			//physicSystem.CollisionSystem = new CollisionSystemBrute();
			physicSystem.CollisionSystem = new CollisionSystemSAP();

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

			physicSystem.NumCollisionIterations = 5;
			physicSystem.NumContactIterations = 5;
			physicSystem.NumPenetrationRelaxtionTimesteps = 5;

			Camera.Position = Vector3.Down * 12 + Vector3.Backward * 30.0f;
		}

		void Create4Constraints(
			Body body1, Vector3 point1, Body body2, Vector3 point2, float allowedDistance, float timescale) {

			var c0 = new ConstraintPoint(body1, point1 + new Vector3(-1, 0, 0),
				body2, point2 + new Vector3(-1, 0, 0), allowedDistance, timescale);
			var c1 = new ConstraintPoint(body1, point1 + new Vector3(+1, 0, 0),
				body2, point2 + new Vector3(+1, 0, 0), allowedDistance, timescale);
			var c2 = new ConstraintPoint(body1, point1 + new Vector3(0, 0, -1),
				body2, point2 + new Vector3(0, 0, -1), allowedDistance, timescale);
			var c3 = new ConstraintPoint(body1, point1 + new Vector3(0, 0, +1),
				body2, point2 + new Vector3(0, 0, +1), allowedDistance, timescale);
			c0.EnableConstraint();
			c1.EnableConstraint();
			c2.EnableConstraint();
			c3.EnableConstraint();
			DisableCollisions(body1, body2);
		}
		/// <summary>
		/// 2つのボディを衝突させない設定にする。
		/// </summary>
		/// <param name="rb0"></param>
		/// <param name="rb1"></param>
		public static void DisableCollisions(Body body1, Body body2) {
			if ((body1.CollisionSkin == null) || (body2.CollisionSkin == null))
				return;
			body1.CollisionSkin.NonCollidables.Add(body2.CollisionSkin);
			body2.CollisionSkin.NonCollidables.Add(body1.CollisionSkin);
		}

		protected override void LoadContent() {
			spriteBatch = new SpriteBatch(GraphicsDevice);
			capsuleModel = Content.Load<Model>("capsule");

			// 部位を作る。[1]
			Components.Add(head = new CapsuleObject(this, capsuleModel, 3, 3.5f, 2,
				Matrix.CreateRotationX(MathHelper.PiOver2), new Vector3(0, -7, -30)));
			Components.Add(neck = new CapsuleObject(this, capsuleModel, 1.5f, 2.0f, 1.2f,
				Matrix.CreateRotationX(MathHelper.PiOver2), new Vector3(0, -11, -30)));
			Components.Add(shoulder = new CapsuleObject(this, capsuleModel, 3.0f, 8, 4,
				Matrix.CreateRotationY(MathHelper.PiOver2), new Vector3(-4, -15, -30)));

			// 各部位を4点の拘束で接続する。
			float alowedDistance = 0.0001f;
			float timescale = 0.125f;
			Create4Constraints(head.PhysicsBody, new Vector3(0, -3.0f, 0.0f),
				neck.PhysicsBody, new Vector3(0, 1.0f, 0.0f),
				alowedDistance, timescale);
			Create4Constraints(neck.PhysicsBody, new Vector3(0, -1.0f, 0.0f),
				shoulder.PhysicsBody, new Vector3(0, 2.0f, 0.0f),
				alowedDistance, timescale);

			// 肩を固定する。[2]
			shoulder.PhysicsBody.Immovable = true;

			// 髪の毛を作る。
			int count = 20;
			float radius = 1.8f;
			for (int i = 0; i < count; i++) {
				float r = i * (MathHelper.TwoPi / count);
				float c = (float)Math.Sin(r) * radius;
				float s = (float)Math.Cos(r) * radius;
				int segments = (Math.Abs(r - MathHelper.Pi) <= MathHelper.Pi * 0.3f) ? 4 : 11;
				ribbons.Add(new Ribbon(this, physicSystem, capsuleModel, head, new Vector3(c, 3, s), segments));
			}

			foreach (var body in physicSystem.Bodies) {
				body.MoveTo(new Vector3(0, 0, +20) + body.Position,
					body.Orientation
					* Matrix.CreateFromAxisAngle(new Vector3(0, 1, 0), MathHelper.PiOver2));
			}
		}

		/// <summary>
		/// 移動速度
		/// </summary>
		float velocity = 0;

		partial void HandleInput();

		protected override void Update(GameTime gameTime) {
			HandleInput();

			velocity *= 0.93f;
			velocity = Math.Min(velocity, 1.0f);

			if (velocity != 0) {
				Vector3 translation = shoulder.PhysicsBody.Orientation.Forward * velocity;
				shoulder.PhysicsBody.MoveTo(shoulder.PhysicsBody.Position + translation, shoulder.PhysicsBody.Orientation);
			}

			physicSystem.Integrate(1.0f / 60);

			base.Update(gameTime);
		}

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

namespace Hair {
	public partial class HairGame {
		KeyboardState previous;
		KeyboardState current;

		public bool IsKeyPress(Keys key) {
			return current.IsKeyDown(key) && previous.IsKeyUp(key);
		}
		public bool IsKeyDown(Keys key) {
			return current.IsKeyDown(key);
		}

		partial void HandleInput() {
			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
				this.Exit();

			previous = current;
			current = Keyboard.GetState();

			if (IsKeyDown(Keys.Left)) {
				var rotation = shoulder.Rotation * Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), 0.05f);
				rotation.Normalize();
				shoulder.PhysicsBody.MoveTo(shoulder.PhysicsBody.Position,
					Matrix.CreateFromQuaternion(rotation));
			}
			if (IsKeyDown(Keys.Right)) {
				var rotation = shoulder.Rotation * Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), -0.05f);
				rotation.Normalize();
				shoulder.PhysicsBody.MoveTo(shoulder.PhysicsBody.Position,
					Matrix.CreateFromQuaternion(rotation));
			}
			if (IsKeyDown(Keys.Up)) {
				velocity += 0.02f;
			}
			if (IsKeyDown(Keys.Down)) {
				velocity -= 0.02f;
			}
		}
	}
}
