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

namespace XELF.Sphynx {

	static partial class Helper {
		public static void Subdivide(this FarseerGames.FarseerPhysics.Collisions.Vertices vertices, float length) {
			//vertices.SubDivideEdges(length);
			//Subdivide(vertices as List<Vector2>);
		}
		public static void Subdivide(this List<Vector2> shape) {
			Vector2[] source = shape.ToArray();
			int count = source.Length;
			shape.Clear();
			shape.Capacity = count * 3;
			for (int i = 0; i < count; i++) {
				int h = (i + count - 1) % count;
				int j = (i + 1) % count;
				Vector2 v0, v2;
				Vector2.Lerp(ref source[h], ref source[i], 0.99f, out v0);
				Vector2.Lerp(ref source[i], ref source[j], 0.01f, out v2);
				shape.Add(v0);
				shape.Add((v0 + v2) * 0.5f);
				shape.Add(v2);
			}

		}
	}

	/// <summary>
	/// 設定
	/// </summary>
	static class Settings {
		public static readonly Color DefaultGuideColor = new Color(0.2f, 0.3f, 0.8f, 0.65f);
		public static readonly Color HoveredGuideColor = new Color(1.0f, 0.9f, 0.0f, 0.75f);
		public static readonly Color SelectedGuideColor = new Color(1.0f, 0.2f, 0.2f, 0.75f);
	}

	/// <summary>
	/// ステージ編集
	/// </summary>
	class StageEditor : IDisposable {
		public Stage Stage { get; private set; }
		readonly GraphicsDevice GraphicsDevice;
		GimmickKind kind = GimmickKind.None + 1;
		Vector2 position;
		Vector2 previousPosition;

		readonly KeyboardInput keyboard;
		readonly MouseInput input;
		readonly SpriteBatch spriteBatch;
		readonly SpriteFont font;
		readonly BasicEffect effect;
		readonly StringBuilder s = new StringBuilder();
		readonly History history = new History();
		readonly GridDisplay gridDisplay;
		Gimmick selectedItem;

		public Gimmick SelectedItem {
			get { return selectedItem; }
			set {
				if (selectedItem != value) {
					if (selectedItem != null)
						selectedItem.GuideColor = Settings.DefaultGuideColor;
					selectedItem = value;

					{
						var camera = Stage.Camera as ActiveCamera;
						if (camera != null) camera.Target = SelectedItem;
					}

					if (selectedItem != null)
						selectedItem.GuideColor = Settings.SelectedGuideColor;
				}
			}
		}

		public VectorGraphics Graphics { get; protected set; }
		public KeyboardInput Keyboard { get { return keyboard; } }
		public MouseInput Input { get { return input; } }

		StageSelector stageSelector;

		public StageEditor(
			VectorGraphics graphics, MouseInput input, KeyboardInput keyboardInput,
			GraphicsDevice graphicsDevice, SpriteFont font,
			Texture2D iconTexture, BasicEffect effect, Stage stage, StageSelector stageSelector, Desktop desktop) {
			Graphics = graphics;
			this.input = input;
			this.keyboard = keyboardInput;
			gridDisplay = new GridDisplay(Graphics, stage.Camera);
			GraphicsDevice = graphicsDevice;
			spriteBatch = new SpriteBatch(GraphicsDevice);
			this.Stage = stage;
			this.font = font;
			this.effect = effect;
			this.stageSelector = stageSelector;
			this.desktop = desktop;
			stageSelector.StageLoading += new Action<int>(stageSelector_StageLoading);
			stageSelector.StageSaving += new Action<int>(stageSelector_StageSaving);

			defaultTool = new DefaultTool(this);
			selectTool = new SelectTool(this);
			shapeTool = new ShapeTool(this, keyboard, input);
			moveTool = new MoveTool(this);
			Tool = defaultTool;

			SelectNewGimmick();

			history.InsertEntire(stage);

			stage.ScoreboardFinished += ControlGame;
		}

		public void Dispose() {
			history.Dispose();
			if (SelectedItem != null)
				SelectedItem.Dispose();
		}

		public void Draw() {
			if (Stage.Mode == StageMode.Editor) {

				gridDisplay.Draw(GridSize);

				spriteBatch.Begin();
				s.Remove(0, s.Length);
				s.AppendFormat(
@"Play={0}, Mode={1}, Gimmicks={2}, Gimmick={3},
GridSnap={4},
Selected={5},
Hovered={6},
Undo={7}, Redo={8},
Tool={9}/{10},

Bodies={11}, Joints={12}, Geometries={13}, Springs={14}, Arbiters={15},
",
					Stage.IsPlay, Stage.Mode,
					Stage.Gimmicks.Count, kind.ToString(), GridSnap,
					SelectedItem, HoveredItem,
					history.UndoCount, history.RedoCount, Tool, parentTool,
					World._.BodyList.Count, World._.JointList.Count, World._.GeomList.Count,
					World._.SpringList.Count, World._.ArbiterList.Count);

				spriteBatch.DrawString(font, s, new Vector2(0x60, 0x40), Color.White);
				spriteBatch.End();

				if (SelectedItem != null) {
					SelectedItem.Draw();
					Graphics.Begin();
					SelectedItem.Draw(Graphics);
					Graphics.End();
				}

				Stage.Draw(Graphics);

				//DrawCursor();
			}
		}

		/// <summary>
		/// 追加するギミックの候補を変更する。（仮追加）
		/// </summary>
		void ChangeGimmickCandidate() {
			CancelAdd();
			SelectNewGimmick();
		}

		void SelectNewGimmick() {
			SelectedItem = Stage.Factories.Gimmicks.Create(position, kind, Unique.Create());
			//stage.Factories.Bodies.World.addBody(selectedItem.Body);
			SelectedItem.Resolve();
		}

		/// <summary>
		/// ステージ終了後の動作を制御する。
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void ControlGame(object sender, EventArgs e) {
			if (Stage.IsPlaytest || !Stage.NextGame())
				StartEditMode();
		}

		public void StartEditMode() {
			Stage.GoalPopup.Inactivate();
			Stage.StagePopup.Inactivate();
			history.Restore(Stage);
			Stage.Mode = StageMode.Editor;
			Stage.IsPlay = false;
			Stage.DebugDraw = true;
			var camera = Stage.Camera as ActiveCamera;
			if (camera != null) camera.EditStyleScroll = true;
		}

		Desktop desktop;
		bool enabled = true;
		public bool Enabled {
			get {
				return enabled;
			}
			set {
				enabled = value;
				keyboard.Clear();
				desktop.Enabled = value;
			}
		}

		public void Update() {
			if (!Enabled) return;
			Vector3 p = WorldPositionFrom(new Vector2(input.Current.X, input.Current.Y));
			previousPosition = position;
			position.X = p.X;
			position.Y = p.Y;

			if (Stage.Mode == StageMode.Game) {
				// in game
				if (keyboard.IsPress(Keys.Escape)) {
					StartEditMode();
				}
				return;
			}

			if (keyboard.IsPress(Keys.D1)) {
				Tool = defaultTool;
			}
			if (keyboard.IsPress(Keys.D2)) {
				CancelAdd();
				Tool = shapeTool;
			}
			if (keyboard.IsPress(Keys.D3)) {
				CancelAdd();
				Tool = moveTool;
			}

			Tool.Update();
		}

		/// <summary>
		/// 入力操作に対して処理する。（再生・停止）
		/// </summary>
		public void HandleBasic0ToolInput() {
			if (keyboard.IsPress(Keys.G)) {
				GridSnap = !GridSnap;
			}
			if (keyboard.IsPress(Keys.Tab)) {
				Stage.DebugDraw = !Stage.DebugDraw;
			}
		}
		public void HandleSelectedItemInput() {
			if (SelectedItem == null)
				return;

			if (SelectedItem != null && input.IsTranslated) {
				SelectedItem.Position = position;
			}

			if (keyboard.Current.IsKeyDown(Keys.LeftControl) || keyboard.Current.IsKeyDown(Keys.RightControl)) {

				/// 回転
				if (SelectedItem != null) {
					if (GridSnap) {
						if (keyboard.IsPress(Keys.Left)) {
							var rotation = SelectedItem.Rotation;
							rotation /= MathHelper.PiOver2;
							rotation = (float)Math.Round(rotation);
							rotation += 1;
							rotation *= MathHelper.PiOver2;
							SelectedItem.Rotation = rotation;
						}
						if (keyboard.IsPress(Keys.Right)) {
							var rotation = SelectedItem.Rotation;
							rotation /= MathHelper.PiOver2;
							rotation = (float)Math.Round(rotation);
							rotation -= 1;
							rotation *= MathHelper.PiOver2;
							SelectedItem.Rotation = rotation;
						}
					} else {
						if (keyboard.Current.IsKeyDown(Keys.Left)) {
							SelectedItem.Rotation += MathHelper.Pi / 180;
						}
						if (keyboard.Current.IsKeyDown(Keys.Right)) {
							SelectedItem.Rotation -= MathHelper.Pi / 180;
						}
					}
				}

			} else {
				if (keyboard.IsPress(Keys.C)) {
					if (SelectedItem != null) {
						SelectedItem = SelectedItem.Clone(false);
					}
				}
				if (keyboard.IsPress(Keys.Space)) {
					PutSelectedItem();
				}
				if (keyboard.IsPress(Keys.F)) {
					if (SelectedItem != null) {
						SelectedItem.Body.IsStatic = !SelectedItem.Body.IsStatic;
					}
				}
			}
		}

		public void Undo() {
			if (history.UndoCount > 0) {
				history.Undo(Stage);
				Stage.IsPlay = false;
			}
		}
		public void Redo() {
			if (history.RedoCount > 0) {
				history.Redo(Stage);
				Stage.IsPlay = false;
			}
		}

		void stageSelector_StageSaving(int obj) {
			if (obj < 10) {
				Stage.Save(Stage.GetFileName(obj));
			}
			stageSelector.Hide();
			Enabled = true;
		}

		void stageSelector_StageLoading(int obj) {
			if (obj < 10) {
				CancelAdd();
				Stage.Load(Stage.GetFileName(obj));
				RecordEntire();
				SelectNewGimmick();
			}
			stageSelector.Hide();
			Enabled = true;
		}

		public void HandleSceneToolInput() {
			if (keyboard.Current.IsKeyDown(Keys.LeftControl) || keyboard.Current.IsKeyDown(Keys.RightControl)) {
				if (keyboard.IsPress(Keys.S)) {
					stageSelector.ShowSaveMenu();
					Enabled = false;
					//Stage.Save("test.xml");
				}
				if (keyboard.IsPress(Keys.O)) {
					stageSelector.ShowLoadMenu();
					Enabled = false;
					/*CancelAdd();
					Stage.Load("test.xml");
					RecordEntire();
					SelectNewGimmick();
					*/
				}
				if (keyboard.IsPress(Keys.Z)) {
					Undo();
				}
				if (keyboard.IsPress(Keys.Y)) {
					Redo();
				}
			} else {
				if (keyboard.IsPress(Keys.Escape)) {
					Stage.IsPlay = !Stage.IsPlay;
				}
				if (keyboard.IsPress(Keys.F5)) {
					Stage.StartGame();
				}
				if (keyboard.IsPress(Keys.M)) {
					if (Stage.IsPlay)
						history.InsertEntire(Stage);
					Stage.StartPlaytest();
				}

				if (keyboard.IsPress(Keys.Home)) {
					var camera = Stage.Camera as ActiveCamera;
					if (camera != null) {
						camera.Reset();
					}
					if (SelectedItem != null)
						SelectedItem.Position = Vector2.Zero;
				}
			}
		}
		public void HandleBasicToolInput() {
			HandleSceneToolInput();

			if (keyboard.Current.IsKeyDown(Keys.LeftControl) || keyboard.Current.IsKeyDown(Keys.RightControl)) {
				if (keyboard.IsPress(Keys.Delete)) {
					foreach (Gimmick gimmick in Stage.Gimmicks) {
						gimmick.BodyRegistered = false;
					}
					Stage.Gimmicks.Clear();
				}
			} else {
				if (keyboard.IsPress(Keys.Delete)) {
					if (SelectedItem != null) {
						SelectedItem.BodyRegistered = false;
						SelectedItem.Dispose();
						SelectedItem = null;
					}
				}
				if (keyboard.IsPress(Keys.A)) {
					kind--;
					if (kind <= GimmickKind.None) kind = GimmickKind.Size - 1;
					ChangeGimmickCandidate();
				}
				if (keyboard.IsPress(Keys.D)) {
					kind++;
					if (kind >= GimmickKind.Size) kind = GimmickKind.None + 1;
					ChangeGimmickCandidate();
				}

			}
			/*
			if (!input.Handled && input.Current.LeftButton == ButtonState.Pressed) {
				var gimmick = Pick(position);
				if (gimmick != null) {
					CancelAdd();
					SelectedItem = gimmick;
					input.Handled = true;
				}
			}
			*/
			HandleMouseInput();

			HandleBasic0ToolInput();
			HandleSelectedItemInput();
		}

		void CancelAdd() {
			if (SelectedItem != null) {
				if (!Stage.Gimmicks.Contains(SelectedItem)) {
					SelectedItem.Dispose();
				} else {
					history.Do(Stage, new RemoveGimmickRecord(SelectedItem));
				}
				SelectedItem = null;
			}
		}

		public void RecordEntire() {
			history.Do(Stage, new EntireRecord(Stage));
		}

		public void PutSelectedItem() {
			if (SelectedItem != null) {
				// 仮追加のギミックをステージに追加する。

				var current = SelectedItem.CurrentVersion;
				if (current != null) SelectedItem = current;

				if (Stage.Gimmicks.Contains(SelectedItem)) {
					SelectedItem = SelectedItem.Clone(false);
				}

				if (!Stage.Factories.Bodies.World._.BodyList.Contains(SelectedItem.Body._)) {
					//Stage.Factories.Bodies.World.AddBody(SelectedItem.Body);
					SelectedItem.Registered = true;
				}
				RecordEntire();
				//World.Update(-1e-5f);
				//	history.Do(stage, new CreateGimmickRecord(selectedItem));
				SelectNewGimmick();
			}
		}
		Gimmick TryBeginDragItem() {

			CancelAdd();
			//World.Update(1e-5f);
			var gimmick = Pick(new Vector2(input.Current.X, input.Current.Y));
			if (gimmick != null) {
				/*if (!Stage.Gimmicks.Contains(gimmick)) {
					throw new Exception();
				}*/
				CancelAdd();
				SelectedItem = gimmick;
				gimmick.BodyRegistered = false;
				gimmick.Enabled = false;
				//history.InsertEntire(stage);
			}
			return gimmick;
		}

		bool IsSelectedItemInStage {
			get {
				if (selectedItem == null)
					return false;
				if (Stage.Gimmicks.Contains(selectedItem))
					return true;
				return false;
			}
		}

		Gimmick hoveredItem;
		public Gimmick HoveredItem {
			get { return hoveredItem; }
			set {
				if (hoveredItem != null) {
					if (hoveredItem != selectedItem) {
						hoveredItem.GuideColor = Settings.DefaultGuideColor;
					}
				}
				hoveredItem = value;
				if (hoveredItem != null) {
					if (hoveredItem != selectedItem) {
						hoveredItem.GuideColor = Settings.HoveredGuideColor;
					}
				}
			}
		}

		public void UpdateHoveredItem() {
			HoveredItem = Pick(input.Location, SelectedItem);
		}

		int scaleBase;

		void HandleMouseInput() {

			var camera = Stage.Camera as ActiveCamera;
			if (camera != null) {

				int wheel = input.Current.ScrollWheelValue - input.Previous.ScrollWheelValue;
				if (wheel != 0) {
					scaleBase += wheel;
					if (wheel < -7 * 1000) wheel = -7 * 1000;
					if (wheel > +7 * 1000) wheel = +7 + 1000;

					camera.Scale =
					MathHelper.Clamp(
						(float)Math.Pow(2.0, scaleBase * 0.001),
						1.0f / 128, 128.0f);
				}
			}

			if (!input.Handled && input.Current.LeftButton == ButtonState.Pressed && input.Previous.LeftButton == ButtonState.Released) {
				input.Handled = true;
				if (!IsSelectedItemInStage) {
					TryBeginDragItem();
				} else {
					PutSelectedItem();
				}
			}
			/*
			if (input.Current.RightButton == ButtonState.Pressed) {
				var shape = new ClosedShape(new List<Vector2>() {
				new Vector2(1, 2), new Vector2(5, 5), new Vector2(4, 9), new Vector2(1, 5), });

				var body = new GimmickBody(stage.Factories.Bodies.World, shape, 1, 400, 10f, new Vector2(0, 30), 0, Vector2.One);
				stage.Factories.Bodies.World.addBody(body);
				body.addInternalSpring(0, 2, 400, 10f);
				body.addInternalSpring(1, 3, 400, 10f);
			}
			*/

		}

		public World World {
			get { return Stage.Factories.Bodies.World; }
		}

		VertexPositionColor[] p = {
			new VertexPositionColor(Vector3.Zero, Color.Red)
		};

		void DrawCursor() {
			p[0].Position = WorldPositionFrom(new Vector2(input.Current.X, input.Current.Y));

			GraphicsDevice.RenderState.PointSize = 5;
			effect.Begin(SaveStateMode.SaveState);
			effect.CurrentTechnique.Passes[0].Begin();
			GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.PointList,
				p, 0, 1);
			effect.CurrentTechnique.Passes[0].End();
			effect.End();
		}

		public bool GridSnap = true;
		Vector3 GridSize = Vector3.One;

		private static Vector3 Half = new Vector3(0.5f, 0.5f, 0.5f);

		public Vector2 WorldPosition2From(Vector2 location) {
			var position = WorldPositionFrom(location);
			return new Vector2(position.X, position.Y);
		}
		public Vector3 WorldPositionFrom(Vector2 location) {
			Vector3 position = GraphicsDevice.Viewport.Unproject(
				new Vector3(location, 0), effect.Projection, effect.View, effect.World);
			if (GridSnap) {
				Vector3.Divide(ref position, ref GridSize, out position);
				Vector3.Add(ref position, ref Half, out position);
				position.X = (float)Math.Floor(position.X);
				position.Y = (float)Math.Floor(position.Y);
				position.Z = (float)Math.Floor(position.Z);
				Vector3.Multiply(ref position, ref  GridSize, out position);
			}
			return position;
		}

		public Gimmick Pick(Vector2 location) {
			Vector3 position = WorldPositionFrom(location);
			Vector2 position2 = new Vector2(position.X, position.Y);
			//int body;
			//int pointMass;
			//World.getClosestPointMass(position2, out body, out pointMass);
			//GimmickBody _body = World.getBody(body) as GimmickBody;
			Body body = World.CollideBody(ref position2);
			GimmickBody _body = body as GimmickBody;
			if (_body != null) {
				var gimmick = _body.Tag as Gimmick;
				return gimmick;
			}
			return null;
		}

		public Gimmick Pick(Vector2 location, Gimmick exclusion) {
			Vector3 position = WorldPositionFrom(location);
			Vector2 position2 = new Vector2(position.X, position.Y);
			var bodies = World.CollideAllBodies(position2);
			var body = bodies.Find(delegate(Body b) {
				return b.Tag != exclusion;
			});
			GimmickBody _body = body as GimmickBody;
			if (_body == null)
				return null;
			return _body.Tag as Gimmick;
		}

		public void BeginSelect() {
			parentTool = Tool;
			currentTool = selectTool;
		}
		public void EndSelect(Gimmick selectedItem) {
			SelectedItem = selectedItem;
			Tool = parentTool;
		}

		Tool currentTool;
		Tool parentTool;

		/// <summary>
		/// 現在のツール
		/// </summary>
		public Tool Tool {
			get { return currentTool; }
			protected set {
				currentTool = value;
				parentTool = null;
			}
		}
		readonly DefaultTool defaultTool;
		readonly SelectTool selectTool;
		readonly ShapeTool shapeTool;
		readonly MoveTool moveTool;
	}
}
