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

namespace XELF.Sphynx {

	class AnchorGimmick : Gimmick {
		Gimmick target;

		public event Action<AnchorGimmick> TargetChanged;

		public override void Resolve() {
			Target = ReferenceGimmick.Resolve(Target);
			if (Target != null) Target = Target.CurrentVersion;
			base.Resolve();
		}

		public AnchorGimmick(Factories factories, Vector2 position, Unique unique, GimmickKind kind)
			: base(kind, factories, unique) {
			Body = Factories.Bodies.Create(BodyKind.StartWay, position, this);
			VisualKind visualKind;
			switch (kind) {
			default:
			case GimmickKind.Anchor: visualKind = VisualKind.Anchor; break;
			case GimmickKind.Junction: visualKind = VisualKind.Junction; break;
			}
			Visual = Factories.Visuals.Create(visualKind);
			Body.Geometry.IsSensor = true;
			Enabled = false;
			Body.Geometry.CollisionCategories = CollisionCategories.C5;
			Body.Geometry.CollidesWith = CollisionCategories.C5;
		}
		public override string ToString() {
			return string.Format("{0}{{{1}/{2}[deg], Target={3}}}",
				Kind, Position, Rotation.ToDegrees(), target);
		}
		public Gimmick Target {
			get { return target; }
			set {
				SetAnchor(value, Position);
			}
		}

		public void SetAnchor(Gimmick target, Vector2 worldPosition) {
			var input = target as SwitchGimmick;
			if (input != null) {
				input.Anchors.Remove(this);
			}
			this.target = target;
			input = target as SwitchGimmick;
			if (input != null) {
				input.Anchors.Add(this);
			}
			Position = worldPosition;
			if (TargetChanged != null) TargetChanged(this);
		}

		public override Vector2 Position {
			get {
				return base.Position;
			}
			set {
				base.Position = value;
			}
		}

		public override Gimmick CloneGimmick(bool upgrade) {
			return new AnchorGimmick(Factories, Position, Unique[upgrade], Kind)
			{
				Target = this.Target, // 解決が必要な参照
				//Target = this.Target.CurrentVersion, // 参照
			};
		}
		public override void Save(XmlElement element) {
			element.WriteReference("Target", target);
			base.Save(element);
		}
		public override void Load(XmlElement element) {
			Target = element.Refer("Target", Factories);
			base.Load(element);
		}
		public override void Draw() {
			base.Draw();
		}
		public override void Update() {
			if (target != null) {
				Position = target.Position;
				Rotation = target.Rotation;
			}
			base.Update();
		}
	}

	abstract class OutputGimmick : Gimmick {
		public OutputGimmick(GimmickKind kind, Factories factories, Unique unique)
			: base(kind, factories, unique) {
		}
		public virtual SwitchGimmick Input { get; set; }
		public virtual Operator InputOperand {
			get {
				if (Input == null) return OperatorFactory.True;
				return Input.Output;
			}
		}
		public override void Resolve() {
			if (Input != null) Input.Resolve();
			base.Resolve();
		}
	}

	abstract class JointGimmick : OutputGimmick {
		public AnchorGimmick LeftAnchor { get; protected set; }
		public AnchorGimmick RightAnchor { get; protected set; }

		public override void Resolve() {
			if (LeftAnchor != null) LeftAnchor.Resolve();
			if (RightAnchor != null) RightAnchor.Resolve();
			base.Resolve();
			if (LeftAnchor == null) LeftAnchor = (AnchorGimmick)Factories.Gimmicks.Create(Vector2.Zero, GimmickKind.Anchor, Unique.Create());
			if (RightAnchor == null) RightAnchor = (AnchorGimmick)Factories.Gimmicks.Create(Vector2.Zero, GimmickKind.Anchor, Unique.Create());

			AnchorTargetChanged(null);
		}

		public JointGimmick(GimmickKind kind, Factories factories, Unique unique, Vector2 position,
			AnchorGimmick left, AnchorGimmick right)
			: base(kind, factories, unique) {
			LeftAnchor = left;
			RightAnchor = right;
			if (LeftAnchor != null) LeftAnchor.TargetChanged += AnchorTargetChanged;
			if (RightAnchor != null) RightAnchor.TargetChanged += AnchorTargetChanged;
			Position = position;
			Rotation = 0;
		}
		public JointGimmick(GimmickKind kind, Factories factories, Unique unique, Vector2 position)
			: base(kind, factories, unique) {
			//LeftAnchor = (AnchorGimmick)Factories.Gimmicks.Create(Vector2.Zero, GimmickKind.Anchor, Unique.Create());
			//RightAnchor = (AnchorGimmick)Factories.Gimmicks.Create(Vector2.Zero, GimmickKind.Anchor, Unique.Create());
			//LeftAnchor.TargetChanged += AnchorTargetChanged;
			//RightAnchor.TargetChanged += AnchorTargetChanged;
			Position = position;
			Rotation = 0;
		}

		protected void Initialize() {
		}

		public override bool BodyRegistered {
			get { return base.BodyRegistered; }
			set {
				base.BodyRegistered = value;
				LeftAnchor.BodyRegistered = value;
				RightAnchor.BodyRegistered = value;
			}
		}
		public override Vector2 Position {
			get { return base.Position; }
			set {
				base.Position = value;
				//LeftAnchor.Position = value;
				//RightAnchor.Position = value + new Vector2(0, 1);
			}
		}
		public override float Rotation {
			get { return base.Rotation; }
			set {
				base.Rotation = value;
				if (LeftAnchor != null) LeftAnchor.Rotation = value;
				if (RightAnchor != null) RightAnchor.Rotation = value + MathHelper.Pi;
			}
		}
		public override void Draw() {
			LeftAnchor.Draw();
			RightAnchor.Draw();
			base.Draw();
		}
		public override void Save(XmlElement element) {
			if (LeftAnchor != null) LeftAnchor.AppendChildTo(element);
			if (RightAnchor != null) RightAnchor.AppendChildTo(element);
			base.Save(element);
		}
		public override void Load(XmlElement element) {
			LeftAnchor = (AnchorGimmick)Factories.Gimmicks.Create(element.ChildNodes[0] as XmlElement);
			RightAnchor = (AnchorGimmick)Factories.Gimmicks.Create(element.ChildNodes[1] as XmlElement);
			base.Load(element);
		}

		public virtual void AnchorTargetChanged(AnchorGimmick anchor) { }
		public override void Update() {
			base.Update();
		}
		public override void Dispose() {
			LeftAnchor.Dispose();
			RightAnchor.Dispose();
			base.Dispose();
		}
	}

	class LeadGimmick : JointGimmick {
		public override void Resolve() {
			if (LeftAnchor != null) {
				LeftAnchor.Resolve();
			} else {
				LeftAnchor = (AnchorGimmick)Factories.Gimmicks.Create(Vector2.Zero, GimmickKind.Junction, Unique.Create());
			}
			if (RightAnchor != null) {
				RightAnchor.Resolve();
			} else {
				RightAnchor = (AnchorGimmick)Factories.Gimmicks.Create(Vector2.Zero, GimmickKind.Junction, Unique.Create());
			}
			AnchorTargetChanged(null);
			base.Resolve();
		}
		public override void AnchorTargetChanged(AnchorGimmick anchor) {

			var input = LeftAnchor.Target as SwitchGimmick;
			if (input != null) {
				var output = RightAnchor.Target as JointGimmick;
				if (output != null) {
					output.Input = input;
				}
			} else {
				input = RightAnchor.Target as SwitchGimmick;
				var output = LeftAnchor.Target as JointGimmick;
				if (input != null && output != null) {
					output.Input = input;
				}
			}

			base.AnchorTargetChanged(anchor);
		}

		public override string ToString() {
			return string.Format("Lead{{{0}-{1}}}",
				LeftAnchor, RightAnchor);
		}
		public override Gimmick CloneGimmick(bool upgrade) {
			var value = new LeadGimmick(Factories, Position, Unique[upgrade],
				(AnchorGimmick)LeftAnchor.Clone(upgrade), (AnchorGimmick)RightAnchor.Clone(upgrade));
			return value;
		}

		public override void Draw(VectorGraphics graphics) {
			Color color = Color.Black;
			var p0 = LeftAnchor.Position;
			var p1 = Position;
			var p2 = RightAnchor.Position;
			var d = p2 - p1;
			var p01 = p0 * 2 - p1;
			var p21 = p2 * 2 - p1;
			graphics.DrawCurve(ref p01, ref p0, ref p1, ref p2, color);
			graphics.DrawCurve(ref p0, ref p1, ref p2, ref p21, color);
			//graphics.DrawLine(LeftAnchor.Position, Color.Black, Position, Color.Black);
			//graphics.DrawLine(Position, Color.Black, RightAnchor.Position, Color.Black);
			base.Draw(graphics);
		}

		public LeadGimmick(Factories factories, Vector2 position, Unique unique, AnchorGimmick left, AnchorGimmick right)
			: base(GimmickKind.Lead, factories, unique, position, left, right) {
			Initialize(position);
		}
		public LeadGimmick(Factories factories, Vector2 position, Unique unique)
			: base(GimmickKind.Lead, factories, unique, position, null, null) {

			Initialize(position);
		}
		/*
		public LeadGimmick(Factories factories, Vector2 position, Unique unique)
			: base(GimmickKind.Lead, factories, unique, position,
			(AnchorGimmick)factories.Gimmicks.Create(Vector2.Zero, GimmickKind.Junction, Unique.Create()),
			(AnchorGimmick)factories.Gimmicks.Create(Vector2.Zero, GimmickKind.Junction, Unique.Create())) {
			Initialize(position);
		}
		 * */
		public override void Save(XmlElement element) {
			//element.Write("MinimumLength", MinimumLength);
			//element.Write("MaximumLength", MaximumLength);
			base.Save(element);
		}
		public override void Load(XmlElement element) {
			//MinimumLength = element.ValueAsSingle("MinimumLength");
			//MaximumLength = element.ValueAsSingle("MaximumLength");
			base.Load(element);
		}
		void Initialize(Vector2 position) {
			Body = Factories.Bodies.Create(BodyKind.Slider, position, this);
			Body.Geometry.CollisionCategories = CollisionCategories.C5;
			Body.Geometry.CollidesWith = CollisionCategories.C5;
			Body.Geometry.IsSensor = true;
			//BodyRegistered = true;
			Visual = Factories.Visuals.Create(VisualKind.Slider);
		}
		public override bool BodyRegistered {
			get { return base.BodyRegistered; }
			set {
				base.BodyRegistered = value;
				LeftAnchor.BodyRegistered = value;
				RightAnchor.BodyRegistered = value;
			}
		}
	}

	class SliderGimmick : JointGimmick {
		readonly FarseerGames.FarseerPhysics.Dynamics.Joints.SliderJoint joint
					= new FarseerGames.FarseerPhysics.Dynamics.Joints.SliderJoint();

		public float MinimumLength {
			get { return joint.Min; }
			set {
				joint.Min = value;
			}
		}
		public float MaximumLength {
			get { return joint.Max; }
			set {
				joint.Max = value;
			}
		}

		public override void Resolve() {
			base.Resolve();
			AnchorTargetChanged(null);
		}

		public override void AnchorTargetChanged(AnchorGimmick anchor) {
			var body1 = worldBody;
			var body2 = worldBody;
			if (LeftAnchor.Target != null && LeftAnchor.Target.Body != null)
				body1 = LeftAnchor.Target.Body;
			if (RightAnchor.Target != null && RightAnchor.Target.Body != null)
				body2 = RightAnchor.Target.Body;
			joint.Body1 = body1._;
			joint.Body2 = body2._;

			if (joint != null) {
				joint.Anchor1 = Vector2.Transform(LeftAnchor.Position, body1.Geometry._.MatrixInverse);
				joint.Anchor2 = Vector2.Transform(RightAnchor.Position, body2.Geometry._.MatrixInverse);

				joint.Max = Vector2.Distance(LeftAnchor.Position, RightAnchor.Position);
				joint.Min = joint.Max * 0.5f;
			}
			//Factories.Bodies.World._.Add(joint);
		}

		public override string ToString() {
			return string.Format("Slider{{{0}/{1}[deg], Length={2}/{3}/{4}, Active={5}}}",
				Position, Rotation.ToDegrees(),
				MinimumLength,
				Vector2.Distance(joint.WorldAnchor2, joint.WorldAnchor1),
				MaximumLength,
				InputOperand.Active);
		}
		public override Gimmick CloneGimmick(bool upgrade) {
			var value = new SliderGimmick(Factories, Position, Unique,
				(AnchorGimmick)this.LeftAnchor.Clone(upgrade),
				(AnchorGimmick)this.RightAnchor.Clone(upgrade))
			{
				//BodyRegistered = this.BodyRegistered,
			};
			//value.LeftAnchor.TargetChanged += value.AnchorTargetChanged;
			//value.RightAnchor.TargetChanged += value.AnchorTargetChanged;
			//value.AnchorTargetChanged(null);
			return value;
		}

		Body worldBody;

		private void Initialize(Vector2 position) {
			worldBody = new Body(Factories.Bodies.World,
				new ClosedShape(new List<Vector2> { new Vector2(-1, -1), new Vector2(-1, +1), new Vector2(+1, +1) }),
				float.MaxValue);
			worldBody.IsStatic = true;
			worldBody.Enabled = false;

			Body = Factories.Bodies.Create(BodyKind.Slider, position, this);
			Body.Geometry.CollisionCategories = CollisionCategories.C5;
			Body.Geometry.CollidesWith = CollisionCategories.C5;
			Body.Geometry.IsSensor = true;
			//BodyRegistered = true;
			Visual = Factories.Visuals.Create(VisualKind.Slider);
			//AnchorTargetChanged(null);
		}
		public SliderGimmick(Factories factories, Vector2 position, Unique unique, AnchorGimmick left, AnchorGimmick right)
			: base(GimmickKind.Slider, factories, unique, position, left, right) {
			Initialize(position);
		}
		public SliderGimmick(Factories factories, Vector2 position, Unique unique)
			: base(GimmickKind.Slider, factories, unique, position) {
			Initialize(position);
		}
		public override void Save(XmlElement element) {
			element.Write("MinimumLength", MinimumLength);
			element.Write("MaximumLength", MaximumLength);
			base.Save(element);
		}
		public override void Load(XmlElement element) {
			MinimumLength = element.ValueAsSingle("MinimumLength");
			MaximumLength = element.ValueAsSingle("MaximumLength");
			base.Load(element);
		}

		Motor Motor = new Motor();

		void Control() {
			Motor.Update();
			// 動き
			if (joint.Body1 != null && joint.Body2 != null && InputOperand.Active) {
				var direction = Direction;
				Motor.Minimum = joint.Min;
				Motor.Maximum = joint.Max;
				var a = Motor.GetOutputVelocity(Length);
				a *= 1000;
				joint.Body1.ApplyForceAtWorldPoint(direction * +a, joint.WorldAnchor1);
				joint.Body2.ApplyForceAtWorldPoint(direction * -a, joint.WorldAnchor2);
			}

		}

		public float Length {
			get {
				return Vector2.Distance(joint.WorldAnchor1, joint.WorldAnchor2);
			}
		}
		Vector2 Direction {
			get {
				var direction = RightAnchor.Position - LeftAnchor.Position;
				if (direction.LengthSquared() <= 0)
					return direction;
				direction.Normalize();
				return direction;
			}
		}

		public override void Update() {
			LeftAnchor.Position = joint.WorldAnchor1;
			RightAnchor.Position = joint.WorldAnchor2;
			Position = (LeftAnchor.Position + RightAnchor.Position) * 0.5f;
			var direction = RightAnchor.Position - LeftAnchor.Position;
			if (direction.LengthSquared() > 0) {
				Rotation = -FarseerGames.FarseerPhysics.Mathematics.Calculator.ATan2(direction.X, direction.Y);

				Control();
			}

			base.Update();
		}
		public override void Dispose() {
			BodyRegistered = false;
			Factories.Bodies.World._.Remove(joint);
			joint.Dispose();
			base.Dispose();
		}

		public override bool BodyRegistered {
			get { return base.BodyRegistered; }
			set {
				base.BodyRegistered = value;
				LeftAnchor.BodyRegistered = value;
				RightAnchor.BodyRegistered = value;
				if (joint != null) {
					if (value)
						Factories.Bodies.World._.Add(joint);
					else
						Factories.Bodies.World._.Remove(joint);
				}
			}
		}
	}

}
