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

#region 物理エンジンとの結合部分

#if JELLO_PHYSICS
using JelloPhysics;
class World {
	readonly JelloPhysics.World _ = new JelloPhysics.World();

	public bool AutoAddBody { get { return _.AutoAddBody; } set { _.AutoAddBody = value; } }

	public void SetBounds(Vector2 min, Vector2 max) {
		_.setWorldLimits(min, max);
	}

	public void addBody(Body body) {
		_.addBody(body._);
	}
	public void removeBody(Body body) {
		_.removeBody(body._);
	}
	public bool AudoAddBody {
		get { return _.AutoAddBody; }
		set { _.AutoAddBody = value; }
	}
	public Body getBodyContaining(ref Vector2 point) {
		return _.getBodyContaining(ref point).ObjectTag as Body;
	}
	public int BodyCount {
		get { return _.BodyCount; }
	}
	public Body getBody(int index) {
		return _.getBody(index).ObjectTag as Body;
	}

	public void Update(float elapsed) {
		_.update(elapsed);
	}
}
class PressureBody : Body {
}
class Body {
	public World World { get; private set; }
	public JelloPhysics.Body _;

	public void addGlobalForce(ref Vector2 point, ref Vector2 force) {
		_.addGlobalForce(ref point, ref force);
	}
	public Body() {
	}
	public void CopyTo(Body value) {
	}
	public bool IsStatic;
	public object Tag {
		get { return _.ObjectTag; }
		set { _.ObjectTag = value; }
	}
	public virtual void accumulateExternalForces() {
		_.accumulateExternalForces();
	}
}
#else

using FarseerGames.FarseerPhysics;

namespace XELF.Sphynx {

	class World : IDisposable {
		public PhysicsSimulator _ { get; protected set; }
		public World() {
			_ = new PhysicsSimulator();
		}
		public void Dispose() {
		}
		/// <summary>
		/// 重力加速度
		/// </summary>
		public Vector2 Gravity {
			get { return _.Gravity; }
			set { _.Gravity = value; }
		}
		public int BodyCount {
			get { return _.BodyList.Count; }
		}
		public bool AutoAddBody;
		public void AddBody(Body body) {
			_.Add(body._);
			_.Add(body.Geometry._);
		}
		public void RemoveBody(Body body) {
			_.Remove(body._);
			_.Remove(body.Geometry._);
		}
		public void Update(float elapsed) {
			_.Update(elapsed);
		}
		public Body GetBody(int index) {
			return _.BodyList[index].Tag as Body;
		}
		public void SetBounds(Vector2 min, Vector2 max) {
		}
		/*
		public Body getBodyContaining(ref Vector2 point) {
			FarseerGames.FarseerPhysics.Dynamics.Body;
			return null;
		}
		*/
		public Body CollideBody(ref Vector2 point) {
			var geometry = Collide(ref point);
			if (geometry == null)
				return null;
			return geometry.Body;
		}
		public Geometry Collide(ref Vector2 point) {
			var geometry = _.Collide(point);
			if (geometry == null) return null;
			return geometry.Tag as Geometry;
		}
		public List<Body> CollideAllBodies(Vector2 point) {
			var geometries = _.CollideAll(point);
			var result = new List<Body>(geometries.Count);
			foreach (var geometry in geometries) {
				var body = geometry.Body.Tag as Body;
				if (body != null) {
					result.Add(body);
				}
			}
			return result;
		}
		public List<Geometry> CollideAll(Vector2 point) {
			var geometries = _.CollideAll(point);
			var result = new List<Geometry>(geometries.Count);
			foreach (var geometry in geometries) {
				result.Add(geometry.Tag as Geometry);
			}
			return result;
		}
		public void DrawAllSprings(VectorGraphics graphics, Effect effect, bool reserved) {
			graphics.Begin();

			foreach (var _spring in _.SpringList) {
				var spring = _spring.Tag as Spring;
				if (spring == null) continue;

				var linearSpring = spring as LinearSpring;
				if (linearSpring != null) {
					graphics.DrawLine(linearSpring.Position1, Color.Coral, linearSpring.Position2, Color.Coral);
				}
			}

			graphics.End();
		}
		public void DrawAllBodies(VectorGraphics graphics, Effect effect, bool reserved) {
			graphics.Begin();

			foreach (var _body in _.BodyList) {
				var body = _body.Tag as Body;
				if (body != null) {

					Color color;
					var gimmick = body.Tag as Gimmick;
					if (gimmick == null) color = Settings.DefaultGuideColor;
					else color = gimmick.GuideColor;
					var vertices = body.Geometry._.WorldVertices;
					if (vertices.Count >= 0) {
						Vector2 start = vertices[vertices.Count - 1], end;
						for (int i = 0; i < vertices.Count; i++) {
							end = vertices[i];
							graphics.DrawLine(start, color, end, color);
							start = end;
						}
						for (int i = 0; i < vertices.Count; i++) {
							graphics.DrawPoint(vertices[i], color);
						}
					}
					/// 軸の描画
					{
						Matrix transform = body._.GetBodyMatrix();
						graphics.DrawLine(transform.Translation, Color.Red, transform.Translation + transform.Left, Color.Red);
						graphics.DrawLine(transform.Translation, Color.Green, transform.Translation + transform.Up, Color.Green);
					}
				}
			}

			graphics.End();
		}
		public void DrawPointVelocities(VectorGraphics graphics, Effect effect) {

		}
	}

	class Geometry : IDisposable {
		public FarseerGames.FarseerPhysics.Collisions.Geom _ { get; private set; }
		public Body Body {
			get { return _.Body.Tag as Body; }
			set { _.SetBody(value._); }
		}
		public Geometry(FarseerGames.FarseerPhysics.Collisions.Geom _) {
			this._ = _;
			this._.Tag = this;
		}
		public CollisionCategories CollisionCategories {
			get {
				return (CollisionCategories)_.CollisionCategories;
			}
			set {
				_.CollisionCategories = (FarseerGames.FarseerPhysics.CollisionCategory)value;
			}
		}
		public CollisionCategories CollidesWith {
			get { return (CollisionCategories)_.CollidesWith; }
			set { _.CollidesWith = (CollisionCategory)value; }
		}
		public bool IsSensor { get { return _.IsSensor; } set { _.IsSensor = value; } }

		public void Dispose() {
			_.Dispose();
		}
		public override string ToString() {
			return string.Format("Geometry{{Points={0}, CollisionCategories={1}, CollidesWith={2}, Friction={3}, Restitution={4}}}",
			_.WorldVertices.Count, CollisionCategories, CollidesWith, FrictionCoefficient, RestitutionCoefficient);
		}
		public Geometry CloneWith(ClosedShape shape) {

			var geom = _.Clone(Body._, shape);
			//geom.GridCellSize = FarseerGames.FarseerPhysics.Factories.GeomFactory.Instance.CalculateGridCellSizeFromAABB(shape);
			var value = new Geometry(geom);

			/*
			var value = new Geometry(
				new FarseerGames.FarseerPhysics.Collisions.Geom(Body._, shape,
				FarseerGames.FarseerPhysics.Factories.GeomFactory.Instance.CalculateGridCellSizeFromAABB(shape)));
			*/
			value._.Tag = value;
			/*
			value.IsSensor = IsSensor;
			value.CollisionCategories = CollisionCategories;
			value.CollidesWith = CollidesWith;
			value.FrictionCoefficient = FrictionCoefficient;
			value.RestitutionCoefficient = RestitutionCoefficient;
			*/
			return value;
		}
		public Geometry Clone() {
			var value = new Geometry(
				new FarseerGames.FarseerPhysics.Collisions.Geom(Body._, _));
			_.CopyTo(value._);
			value._.Tag = value;
			/*
			value.IsSensor = IsSensor;
			value.CollisionCategories = CollisionCategories;
			value.CollidesWith = CollidesWith;
			value.FrictionCoefficient = FrictionCoefficient;
			value.RestitutionCoefficient = RestitutionCoefficient;
			*/
			return value;
		}
		public float FrictionCoefficient {
			get { return _.FrictionCoefficient; }
			set { _.FrictionCoefficient = value; }
		}
		public float RestitutionCoefficient {
			get { return _.RestitutionCoefficient; }
			set { _.RestitutionCoefficient = value; }
		}
	}

	class ClosedShape : FarseerGames.FarseerPhysics.Collisions.Vertices {
		public ClosedShape(List<Vector2> points)
			: base(points.ToArray()) {
		}
		public ClosedShape Clone() {
			return new ClosedShape(this);
		}
	}

	[Flags]
	enum CollisionCategories : uint {
		None = 0,
		C1 = 1 << 0,
		C2 = 1 << 1,
		C3 = 1 << 2,
		C4 = 1 << 3,
		C5 = 1 << 4,
		C6 = 1 << 5,
		C7 = 1 << 6,
		C8 = 1 << 7,
		C9 = 1 << 8,
		C10 = 1 << 9,
		C11 = 1 << 10,
		C12 = 1 << 11,
		C13 = 1 << 12,
		All = 0x7FFFFFFFu,
	}

	class Body : IDisposable {
		public World World { get; private set; }
		public FarseerGames.FarseerPhysics.Dynamics.Body _ { get; private set; }
		public Geometry Geometry { get; private set; }
		public bool Enabled {
			get { return _.Enabled; }
			set { _.Enabled = value; }
		}
		public virtual void Dispose() {
			_.Dispose();
		}
		protected Body() { }
		public Body Clone() {
			var value = new Body();
			value.World = World;
			value._ = FarseerGames.FarseerPhysics.Factories.BodyFactory.Instance.CreateBody(_);
			_.CopyTo(value._);
			value._.Tag = value;
			value.Geometry = Geometry.Clone();
			value.Geometry.Body = value;
			return value;
		}
		public virtual void CopyTo(Body body) {
			body.World = World;
			body._ = FarseerGames.FarseerPhysics.Factories.BodyFactory.Instance.CreateBody(_);

			body.Geometry = Geometry.Clone();
			_.CopyTo(body._);
			body._.Tag = body;
			/*
			body.Position = Position;
			body.Rotation = Rotation;
			body._.AngularVelocity = _.AngularVelocity;
			body._.LinearVelocity = _.LinearVelocity;
			body.Mass = Mass;
			body.Enabled = Enabled;
			body.IsStatic = IsStatic;
			*/

			body.Geometry.Body = body;
		}

		/// <summary>
		/// 形状を変更します。Geometryは作りなおされます。
		/// </summary>
		/// <param name="shape"></param>
		public void RebuildGeometry(ClosedShape shape) {
			var old = Geometry;
			bool registered = World._.GeomList.Contains(old._);
			old._.OnCollision -= OnCollision;
			//var oldCentroid = old._.WorldVertices.GetCentroid();
			if (registered) {
				World._.Remove(Geometry._);
			}
			Geometry = old.CloneWith(shape);
			old._.Dispose();
			Geometry._.OnCollision += OnCollision;
			if (registered) {
				World._.Add(Geometry._);
			}
			//var newCentroid = Geometry._.WorldVertices.GetCentroid();
			//_.Position += oldCentroid - newCentroid;
		}

		/*
		 
		 
		 LineIntersect
交点で分割

A-B-C-D-A
A-B-x-C-D-x-A

LineSegmentGeomIntersect

A-x
x-y
y-B
B-C
C-y
y-D
D-x
x-E
E-A*/

		public Body(World world, ClosedShape shape, float mass) {
			World = world;

			var centroid = shape.GetCentroid();

			_ = FarseerGames.FarseerPhysics.Factories.BodyFactory.Instance.CreatePolygonBody(shape, mass);
			_.Tag = this;
			Geometry = new Geometry(FarseerGames.FarseerPhysics.Factories.GeomFactory.Instance.CreatePolygonGeom(
				_, shape, 0));
				//FarseerGames.FarseerPhysics.Factories.GeomFactory.Instance.CalculateGridCellSizeFromAABB(shape) * 0.25f));

			//Position += centroid;

			Geometry._.OnCollision += OnCollision;
			//geometry = new FarseerGames.FarseerPhysics.Collisions.Geom(_, shape, 1);
			//World._.Add(geometry);
			if (mass == float.PositiveInfinity) {
				IsStatic = true;
			}
			Enabled = false;
		}

		bool OnCollision(FarseerGames.FarseerPhysics.Collisions.Geom geometry1,
			FarseerGames.FarseerPhysics.Collisions.Geom geometry2,
			FarseerGames.FarseerPhysics.Collisions.ContactList contactList) {
			return true;
		}
		public void ApplyForceAtWorldPoint(ref Vector2 point, ref Vector2 force) {
			_.ApplyForceAtWorldPoint(ref force, ref point);
		}

		public List<Vector2> WorldVertices {
			get {
				return Geometry._.WorldVertices;
			}
		}
		/// <summary>
		/// 空間に固定するか
		/// </summary>
		public bool IsStatic { get { return _.IsStatic; } set { _.IsStatic = value; } }

		public object Tag { get; set; }

		public void SetPositionRotation(Vector2 position, float rotation, Vector2 scale) {
			Position = position * scale;
			Rotation = rotation;
		}
		/// <summary>
		/// 回転
		/// </summary>
		public float Rotation {
			get { return _.Rotation; }
			set { _.Rotation = value; }
		}
		/// <summary>
		/// 位置
		/// </summary>
		public Vector2 Position {
			get { return _.Position; }
			set { _.Position = value; }
		}
		/// <summary>
		/// 質量
		/// </summary>
		public float Mass {
			get { return _.Mass; }
			set { _.Mass = value; }
		}
	}

	class DollJoint {
		readonly World World;

		public readonly RevoluteJoint Joint;
		public readonly AngleSpring Spring;

		public DollJoint(World world) {
			World = world;
			Joint = new RevoluteJoint(world);
			Spring = new AngleSpring(world);
		}

		public bool Registered {
			get { return World._.JointList.Contains(Joint._); }
			set {
				if (value) {
					World._.Add(Joint._);
					World._.Add(Spring._);
				} else {
					World._.Remove(Joint._);
					World._.Remove(Spring._);
				}
			}
		}

		public Body Body1 {
			get { return Joint.Body1; }
			set {
				Joint.Body1 = Spring.Body1 = value;
			}
		}
		public Body Body2 {
			get { return Joint.Body2; }
			set {
				Joint.Body2 = Spring.Body2 = value;
			}
		}
		public Vector2 Anchor {
			get { return Joint.Anchor; }
			set { Joint.Anchor = value; }
		}

		public float SpringConstant {
			get { return Spring.SpringConstant; }
			set { Spring.SpringConstant = value; }
		}
		public float DampingConstant {
			get { return Spring.DampingConstant; }
			set { Spring.DampingConstant = value; }
		}

		public float TargetAngle {
			get { return Spring.TargetAngle; }
			set { Spring.TargetAngle = value; }
		}
	}

	abstract class Spring : IDisposable {
		protected FarseerGames.FarseerPhysics.Dynamics.Springs.Spring __;

		public World World { get; private set; }

		public Spring(World world, FarseerGames.FarseerPhysics.Dynamics.Springs.Spring _) {
			World = world;
			__ = _;
			__.Tag = this;
		}
		public virtual void Dispose() {
			__.Dispose();
		}
		public float SpringConstant {
			get { return __.SpringConstant; }
			set { __.SpringConstant = value; }
		}
		public float DampingConstant {
			get { return __.DampingConstant; }
			set { __.DampingConstant = value; }
		}
		public virtual bool Registered {
			get {
				return World._.SpringList.Contains(__);
			}
			set {
				if (value) {
					World._.Add(__);
				} else {
					World._.Remove(__);
				}
			}
		}
		public virtual bool Enabled { get { return __.Enabled; } set { __.Enabled = value; } }
	}

	class AngleSpring : Spring {
		public FarseerGames.FarseerPhysics.Dynamics.Springs.AngleSpring _ { get; private set; }

		public AngleSpring(World world)
			: base(world, new FarseerGames.FarseerPhysics.Dynamics.Springs.AngleSpring()) {
			_ = __ as FarseerGames.FarseerPhysics.Dynamics.Springs.AngleSpring;
		}

		public Body Body1 {
			get { return _.Body1 == null ? null : _.Body1.Tag as Body; }
			set { _.Body1 = value == null ? null : value._; }
		}
		public Body Body2 {
			get { return _.Body2 == null ? null : _.Body2.Tag as Body; }
			set { _.Body2 = value == null ? null : value._; }
		}
		public float TargetAngle {
			get { return _.TargetAngle; }
			set { _.TargetAngle = value; }
		}
	}

	class LinearSpring : Spring {
		readonly FarseerGames.FarseerPhysics.Dynamics.Springs.LinearSpring _;

		public LinearSpring(World world)
			: base(world, new FarseerGames.FarseerPhysics.Dynamics.Springs.LinearSpring()) {
			_ = __ as FarseerGames.FarseerPhysics.Dynamics.Springs.LinearSpring;
		}
		public Body Body1 {
			get { return _.Body1.Tag as Body; }
			set { _.Body1 = value._; }
		}
		public Body Body2 {
			get { return _.Body2.Tag as Body; }
			set { _.Body2 = value._; }
		}
		public Vector2 AttachPoint1 {
			get { return _.AttachPoint1; }
			set { _.AttachPoint1 = value; }
		}
		public Vector2 AttachPoint2 {
			get { return _.AttachPoint2; }
			set { _.AttachPoint2 = value; }
		}
		public Vector2 Position1 {
			get { return _.Body1.GetWorldPosition(AttachPoint1); }
		}
		public Vector2 Position2 {
			get { return _.Body2.GetWorldPosition(AttachPoint2); }
		}
		public virtual float RestLength { get { return _.RestLength; } set { _.RestLength = value; } }
	}

	class RevoluteJoint {
		readonly World World;
		public RevoluteJoint(World world) {
			World = world;
			_ = new FarseerGames.FarseerPhysics.Dynamics.Joints.RevoluteJoint();
			_.Softness = 1;
		}
		//Joint Clone() {}
		public FarseerGames.FarseerPhysics.Dynamics.Joints.RevoluteJoint _ { get; private set; }

		public bool Enabled { get { return _.Enabled; } set { _.Enabled = value; } }
		public Body Body1 {
			get { return _.Body1 == null ? null : _.Body1.Tag as Body; }
			set { _.Body1 = value == null ? null : value._; }
		}
		public Body Body2 {
			get { return _.Body2 == null ? null : _.Body2.Tag as Body; }
			set { _.Body2 = value == null ? null : value._; }
		}
		public Vector2 Anchor {
			get { return _.Anchor; }
			set { _.Anchor = value; }
		}
		public float Softness {
			get { return _.Softness; }
			set { _.Softness = value; }
		}
		public float BiasFactor {
			get { return _.BiasFactor; }
			set { _.BiasFactor = value; }
		}
	}

}

#endif

#endregion
