using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
#if JELLO_PHYSICS
using JelloPhysics;
#endif
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace XELF.Sphynx {

	enum StageMode {
		Editor,
		Game,
	}

	static class XmlDocumentHelper {
		public static T Value<T>(this XmlElement element, string name)
		where T : struct {
			return (T)Enum.Parse(typeof(T), element.Attributes[name].Value);
		}
		public static bool ValueAsBoolean(this XmlElement element, string name) {
			return element.Attributes[name].ValueAsBoolean();
		}
		public static int ValueAsInt32(this XmlElement element, string name) {
			return element.Attributes[name].ValueAsInt32();
		}
		public static float ValueAsSingle(this XmlElement element, string name) {
			return element.Attributes[name].ValueAsSingle();
		}
		public static Vector2 ValueAsVector2(this XmlElement element, string name) {
			return element.Attributes[name].ValueAsVector2();
		}

		public static void Write(this XmlElement element, string name, Enum value) {
			element.AppendAttribute(name, value.ToString());
		}
		public static void Write(this XmlElement element, string name, bool value) {
			element.AppendAttribute(name, value.ToString());
		}
		public static void Write(this XmlElement element, string name, int value) {
			element.AppendAttribute(name, value.ToString());
		}
		public static void Write(this XmlElement element, string name, float value) {
			element.AppendAttribute(name, value.ToString());
		}
		public static void Write(this XmlElement element, string name, Vector2 value) {
			element.AppendAttribute(name, value);
		}
		public static void WriteReference(this XmlElement element, string name, Gimmick gimmick) {
			if (gimmick != null) {
				element.AppendAttribute(name, gimmick.Unique.Key.ToString());
			}
		}
		public static ReferenceGimmick Refer(this XmlElement element, string name, Factories factories) {
			var attribute = element.Attributes[name];
			if (attribute == null) return null;
			return factories.Gimmicks.Refer(int.Parse(attribute.InnerText));
		}

		public static void AppendAttribute(this XmlElement parent, string name, Vector2 value) {
			AppendAttribute(parent, name, string.Format("{0},{1}", value.X, value.Y));
		}
		public static void AppendAttribute(this XmlElement parent, string name, string value) {
			XmlAttribute attribute = parent.OwnerDocument.CreateAttribute(name);
			attribute.InnerText = value;
			parent.Attributes.Append(attribute);
		}
		public static XmlElement AppendChild(this XmlDocument document, string name) {
			var element = document.CreateElement(name);
			document.AppendChild(element);
			return element;
		}
		public static XmlElement AppendChild(this XmlNode parent, string name) {
			var element = parent.OwnerDocument.CreateElement(name);
			parent.AppendChild(element);
			return element;
		}
		public static Vector2 ValueAsVector2(this XmlAttribute attribute) {
			var data = attribute.Value.Split(',');
			return new Vector2(float.Parse(data[0]), float.Parse(data[1]));
		}
		public static float ValueAsSingle(this XmlAttribute attribute) {
			return float.Parse(attribute.Value);
		}
		public static int ValueAsInt32(this XmlAttribute attribute) {
			return int.Parse(attribute.Value);
		}
		public static bool ValueAsBoolean(this XmlAttribute attribute) {
			return bool.Parse(attribute.Value);
		}
		public static void SetValue(this XmlAttribute attribute, float value) {
			attribute.Value = value.ToString();
		}

	}

	class StageDocument {
		readonly XmlDocument document = new XmlDocument();

		public StageDocument() {
		}

		const string SceneElement = "Scene";
		const string NameAttribute = "Name";
		const string CommentAttribute = "Comment";
		const string GimmicksElement = "Gimmicks";
		const string GimmickElement = "Gimmick";

		public void Save(string path, Stage stage) {
			var scene = document.AppendChild(SceneElement);
			scene.AppendAttribute(NameAttribute, stage.Name);
			scene.AppendAttribute(CommentAttribute, stage.Comment);
			var gimmicks = scene.AppendChild(GimmicksElement);
			foreach (var gimmick in stage.Gimmicks) {
				gimmick.AppendChildTo(gimmicks);
			}

			document.Save(path);
		}
		public void Load(string path, Stage stage) {
			document.Load(path);
			var scene = document[SceneElement];
			var gimmicks = scene[GimmicksElement];
			foreach (XmlElement gimmick in gimmicks.ChildNodes) {
				//stage.Gimmicks.Add(stage.Factories.Gimmicks.Create(gimmick));

				var _gimmick = stage.Factories.Gimmicks.Create(gimmick);
				_gimmick.Registered = true;
				_gimmick.Enabled = true;
			}
		}
	}

	class Stage : IDisposable {
		public string Name;
		public string Comment;

		bool isPlay;

		/// <summary>
		/// ステージ番号
		/// </summary>
		public int Index;
		/// <summary>
		/// ステージの数
		/// </summary>
		public const int NumberStages = 10;

		StageMode mode;
		public StageMode Mode {
			get { return mode; }
			set {
				mode = value;
				if (ModeChanged != null) ModeChanged(this, EventArgs.Empty);
			}
		}
		public event EventHandler ModeChanged;
		public bool DebugDraw = true;

		readonly GraphicsDevice GraphicsDevice;
		public readonly Factories Factories;
		readonly KeyboardInput input;

		public readonly GameStatus Status = new GameStatus();

		public bool IsPlay {
			get { return isPlay; }
			set {
				if (isPlay != value) {
					isPlay = value;
					foreach (var item in Gimmicks) item.Enabled = isPlay;
					foreach (var item in Factories.Bodies.World._.BodyList) item.Enabled = isPlay;
					foreach (var item in Factories.Bodies.World._.JointList) item.Enabled = isPlay;
					foreach (var item in Factories.Bodies.World._.SpringList) item.Enabled = isPlay;
					foreach (var item in Factories.Bodies.World._.ControllerList) item.Enabled = isPlay;
					if (IsPlayChanged != null) IsPlayChanged(this, EventArgs.Empty);
				}
			}
		}

		public event EventHandler IsPlayChanged;

		public void Load(string path) {
			Clear();
			Unique.Clear();
			var document = new StageDocument();
			try {
				document.Load(path, this);
			} catch {
			}
		}
		public void Save(string path) {
			var document = new StageDocument();
			document.Save(path, this);
		}

		public void Clear() {
			foreach (var gimmick in Gimmicks) {
				gimmick.BodyRegistered = false;
				gimmick.Dispose();
			}
			Gimmicks.Clear();
		}

		public Camera Camera { get; protected set; }

		public Stage(GraphicsDevice graphicsDevice, SpriteFont font, World world,
			BasicEffect effect, Texture2D texture, Camera camera, KeyboardInput input) {
			GraphicsDevice = graphicsDevice;
			Camera = camera;
			this.input = input;
			StagePopup = new Popup(graphicsDevice, font) { Offset = new Vector2(0x00, -0x60), };
			StagePopup.Finished += new EventHandler(StagePopup_Finished);
			GoalPopup = new Popup(graphicsDevice, font) { TextString = "Goal!", Offset = new Vector2(0x00, 0x60) };
			GoalPopup.Finished += OnGoalPopupFinished;
			Factories = new Factories(this, world, graphicsDevice, effect, texture, camera);
		}

		void StagePopup_Finished(object sender, EventArgs e) {
			StagePopup.Inactivate();
		}
		public void Dispose() {
		}

		public readonly List<Gimmick> Gimmicks = new List<Gimmick>();

		public Gimmick Resolve(Gimmick gimmick) {
			if (gimmick == null) return null;
			return Gimmicks.Find(delegate(Gimmick g) { return g.Unique == gimmick.Unique; });
		}
		public void ResolveAll() {
			ReferenceGimmick.CreateDictionary(Gimmicks);
			foreach (var gimmick in Gimmicks) {
				gimmick.Resolve();
			}
		}

		public ActorGimmick Actor { get; set; }

		public void Update(GameTime gameTime) {
			input.Update();
			if (IsPlay) {
				StagePopup.Update();
				GoalPopup.Update();

				foreach (Gimmick gimmick in Gimmicks) {
					if (gimmick.Enabled && !gimmick.IsDisposed) {
						gimmick.Update();
					}
				}
				/// 寿命を迎えたギミックを消滅させる。
				for (int i = 0; i < Gimmicks.Count; i++) {
					var gimmick = Gimmicks[i];
					if (!gimmick.Alive) {
						gimmick.Registered = false;
						gimmick.Dispose();
						Gimmicks.RemoveAt(i);
						i--;
					} else if (gimmick.IsDisposed) {
						Gimmicks.RemoveAt(i);
						i--;
					}
				}

				HandleActorInput();
			}

			{
				float elapsed = (float)(gameTime.ElapsedGameTime.TotalSeconds * 0.25f);
				for (int i = 0; i < 4; i++) {
					Factories.Bodies.World.Update(elapsed);
				}
			}

		}

		void HandleActorInput() {
			if (Actor != null) {
				Vector2 velocity = Vector2.Zero;
				var doll = Actor as DollGimmick;

				if (doll != null) {
					if (input.Current.IsKeyDown(Keys.Space)) {
						doll.Grab(true);
					} else {
						doll.Grab(false);
					}
				}
				if (input.Current.IsKeyDown(Keys.A)) {
					velocity.X -= 100;
					if (doll != null) doll.MoveRotation += 0.05f;
				}
				if (input.Current.IsKeyDown(Keys.D)) {
					velocity.X += 100;
					if (doll != null) doll.MoveRotation -= 0.05f;
				}
				if (input.Current.IsKeyDown(Keys.W)) {
					if (doll != null && doll.CanJump) doll.Crouch(true);
				} else {
					if (doll != null) doll.Crouch(false);
				}
				if (!input.Current.IsKeyDown(Keys.W) && input.Previous.IsKeyDown(Keys.W)) {
					if (doll != null && doll.CanJump) doll.Jump();
					//velocity.Y += 100;
				}
				Actor.Move(velocity);
			}
		}
		public void Draw(VectorGraphics graphics) {
			if (!IsPlay && IsPlaytest) {
				graphics.Begin();
				foreach (Gimmick gimmick in Gimmicks) {
					gimmick.Draw(graphics);
				}
				graphics.End();
			}
		}
		public void Draw() {
			foreach (Gimmick gimmick in Gimmicks) {
				gimmick.Draw();
			}
			StagePopup.Draw();
			GoalPopup.Draw();
		}

		public Gimmick StartGimmick {
			get {
				Gimmick startWay = Gimmicks.Find(delegate(Gimmick value) {
					var item = value as WayGimmick;
					if (item == null) return false;
					return item.WayKind == WayKind.Start;
				}) as WayGimmick;
				return startWay;
			}
		}

		public Popup StagePopup { get; private set; }
		public Popup GoalPopup { get; private set; }

		public void FinishGame() {
			if (!GoalPopup.Enabled) {
				Status.Win();
			}
			GoalPopup.Activate();
		}
		void OnGoalPopupFinished(object sender, EventArgs e) {
			var camera = Camera as ActiveCamera;
			if (camera != null) {
				camera.EditStyleScroll = true;
			}
			if (ScoreboardFinished != null)
				ScoreboardFinished(sender, e);
		}
		public event EventHandler ScoreboardFinished;

		/// <summary>
		/// ゲーム開始のための初期化をする。
		/// </summary>
		void InitializeGame() {
			IsPlay = false;
			Factories.Bodies.World.Update(1);
			IsPlay = true;
			Mode = StageMode.Game;
			var actor = Gimmicks.Find(delegate(Gimmick value) { return value is ActorGimmick; });
			GoalPopup.Inactivate();
			Actor = actor as ActorGimmick;
			var camera = Camera as ActiveCamera;
			if (camera != null) {
				camera.Target = Actor;
				camera.EditStyleScroll = false;
			}

			if (Actor != null) {
				var startWay = StartGimmick;
				if (startWay != null)
					actor.Position = startWay.Position;
			}

			StagePopup.Text.Clear();
			StagePopup.Text.AppendFormat("Stage {0}", Index);
			StagePopup.Inactivate();
			StagePopup.Activate();

			ResolveAll();
		}

		/// <summary>
		/// ゲーム開始（全ステージ通しプレイ）
		/// </summary>
		public void StartGame() {
			Index = -1;
			DebugDraw = false;
			IsPlaytest = false;
			Status.Reset();
			NextGame();
		}

		/// <summary>
		/// ゲームのテストプレイ開始
		/// </summary>
		public void StartPlaytest() {
			IsPlaytest = true;
			Status.Reset();
			InitializeGame();
		}

		/// <summary>
		/// ゲームのロードと開始
		/// </summary>
		public bool NextGame() {
			if (Index >= NumberStages - 1) return false;
			Index++;
			Load(GetFileName(Index));
			new EntireRecord(this).Do(this);
			InitializeGame();
			return true;
		}

		public static string GetFileName(int stageIndex) {
			return string.Format("stage{0:000}.xml", stageIndex);
		}

		/// <summary>
		/// テストプレイ中（ステージを終了すると編集に戻る）か？
		/// </summary>
		public bool IsPlaytest { get; private set; }
	}

}
