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

namespace XELF.Sphynx {

	enum VisualKind {
		None,
		Actor,
		Puff,
		StartWay,
		CheckWay,
		GoalWay,
		ButtonInner,
		ButtonOuter,
		Key,
		Lock,
		Emitter,
		Anchor,
		Junction,
		Slider,

		Wall,

		Head,
		Neck,
		Chest,
		Waist,
		Hip,
		UArmL,
		UArmR,
		LArmL,
		LArmR,
		HandL,
		HandR,
		ULegL,
		ULegR,
		LLegL,
		LLegR,
		FootL,
		FootR,
	}

	class VisualFactory : IDisposable {
		readonly Visual actor;
		readonly Visual puff;
		readonly Visual startWay;
		readonly Visual checkWay;
		readonly Visual goalWay;
		readonly Visual buttonInner;
		readonly Visual buttonOuter;
		readonly Visual @lock;
		readonly Visual key;
		readonly Visual emitter;
		readonly Visual anchor;
		readonly Visual junction;
		readonly Visual wall;

		readonly GraphicsDevice GraphicsDevice;
		readonly BasicEffect effect;
		readonly Texture2D texture;
		readonly Camera camera;

		readonly Visual[] visuals = new Visual[DollSettings.Parts.Length];

		const float s = 1 / 16.0f;

		public void Dispose() {
		}
		public VisualFactory(Factories factories,
			GraphicsDevice graphicsDevice, BasicEffect effect, Texture2D texture, Camera camera) {
			this.GraphicsDevice = graphicsDevice;
			this.effect = effect;
			this.texture = texture;
			this.camera = camera;

			actor = CreateActor();
			puff = CreatePuff();
			startWay = CreateStartWay();
			checkWay = CreateCheckWay();
			goalWay = CreateGoalWay();
			@lock = CreateLock();
			key = CreateKey();
			buttonInner = CreateButtonInner();
			buttonOuter = CreateButtonOuter();
			emitter = CreateEmitter();
			anchor = CreateAnchor();
			junction = CreateJunction();
			wall = CreateWall();

			for (int i = 0; i < DollSettings.Parts.Length; i++) {
				visuals[i] = new Visual(graphicsDevice, effect, texture, camera);
				visuals[i].CreateSprite(DollSettings.Parts[i].Min, DollSettings.Parts[i].Max);
				visuals[i].Z = DollSettings.Parts[i].Z;
			}
		}
		Visual CreateActor() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(0.125f, 3.125f) * s, new Vector2(0.875f, 3.875f) * s);
			return value;
		}
		Visual CreatePuff() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreatePuff(new Vector2(0.125f, 3.125f) * s, new Vector2(0.875f, 3.875f) * s, PuffGimmick.Corners);
			return value;
		}
		Visual CreateStartWay() {
			var startWay = new Visual(GraphicsDevice, effect, texture, camera);
			startWay.CreateSprite(new Vector2(0, 0) * s, new Vector2(1, 1) * s);
			return startWay;
		}
		Visual CreateCheckWay() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(1.0f, 0.0f) * s, new Vector2(2.0f, 1.0f) * s);
			return value;
		}
		Visual CreateGoalWay() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(2.0f, 0.0f) * s, new Vector2(3.0f, 1.0f) * s);
			return value;
		}
		Visual CreateButtonInner() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(1.125f, 1.125f) * s, new Vector2(1.875f, 1.875f) * s);
			return value;
		}
		Visual CreateButtonOuter() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(0.125f, 1.125f) * s, new Vector2(0.875f, 1.875f) * s);
			return value;
		}
		Visual CreateLock() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(2.125f, 1.125f) * s, new Vector2(2.875f, 1.875f) * s);
			return value;
		}
		Visual CreateKey() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(3.125f, 1.125f) * s, new Vector2(3.875f, 1.875f) * s);
			return value;
		}
		Visual CreateEmitter() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(0.125f, 2.125f) * s, new Vector2(0.875f, 2.875f) * s);
			return value;
		}
		Visual CreateAnchor() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(4.125f, 1.125f) * s, new Vector2(4.875f, 1.875f) * s);
			return value;
		}
		Visual CreateJunction() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(0.125f, 5.125f) * s, new Vector2(0.875f, 5.875f) * s);
			return value;
		}
		Visual CreateWall() {
			var value = new Visual(GraphicsDevice, effect, texture, camera);
			value.CreateSprite(new Vector2(8.000f, 0.000f) * s, new Vector2(9.000f, 1.000f) * s);
			return value;
		}

		public Visual Create(VisualKind Kind) {
			switch (Kind) {
			default: return null;
			case VisualKind.Actor: return actor;
			case VisualKind.Puff: return puff;
			case VisualKind.ButtonInner: return buttonInner;
			case VisualKind.ButtonOuter: return buttonOuter;
			case VisualKind.StartWay: return startWay;
			case VisualKind.CheckWay: return checkWay;
			case VisualKind.GoalWay: return goalWay;
			case VisualKind.Lock: return @lock;
			case VisualKind.Key: return key;
			case VisualKind.Emitter: return emitter;
			case VisualKind.Anchor: return anchor;
			case VisualKind.Junction: return junction;
			case VisualKind.Wall: return wall;
			case VisualKind.Head:
			case VisualKind.Neck:
			case VisualKind.Chest:
			case VisualKind.Waist:
			case VisualKind.Hip:
			case VisualKind.UArmL:
			case VisualKind.UArmR:
			case VisualKind.LArmL:
			case VisualKind.LArmR:
			case VisualKind.HandL:
			case VisualKind.HandR:
			case VisualKind.ULegL:
			case VisualKind.ULegR:
			case VisualKind.LLegL:
			case VisualKind.LLegR:
			case VisualKind.FootL:
			case VisualKind.FootR:
				return visuals[Kind - VisualKind.Head];
			}
		}
	}

	class Visual : IDisposable {
		readonly Camera camera;

		public Visual Clone() {
			return new Visual(GraphicsDevice, effect, texture, camera)
			{
				Indices = (int[])this.Indices.Clone(),
				Vertices = (VertexPositionColorTexture[])Vertices.Clone(),
				Mapping = this.Mapping,
				ShowInPlay = this.ShowInPlay,
				//texture = this.texture,
				mesh = (mesh != null) ? mesh.Clone() : null,
				Z = this.Z,
			};
		}
		public Mapping Mapping = Mapping.Fit;

		public bool ShowInPlay {
			get;
			set;
		}
		public void Dispose() {
			if (declaration != null) declaration.Dispose();
		}

		public float Z;

		readonly BasicEffect effect;
		readonly Texture2D texture;
		public VertexPositionColorTexture[] Vertices;
		public int[] Indices;
		VertexDeclaration declaration;

		public GraphicsDevice GraphicsDevice {
			get;
			protected set;
		}

		Mesh mesh;
		public Mesh BeginEdit() {
			if (mesh == null) mesh = new Mesh()
			{
				SourceTextureTransform = Matrix.Identity,
			};
			mesh.Mapping = Mapping;
			return mesh;
		}
		public void EndEdit() {
			Vertices = mesh.Vertices.ToArray();
			Indices = mesh.Indices.ToArray();
		}

		public Visual(GraphicsDevice graphicsDevice, BasicEffect effect, Texture2D texture, Camera camera) {
			GraphicsDevice = graphicsDevice;
			this.effect = effect;
			this.texture = texture;
			this.camera = camera;
			declaration = new VertexDeclaration(graphicsDevice, VertexPositionColorTexture.VertexElements);
		}

		public void Draw() {
			if (Indices.Length > 3) {

				bool wireframe = false;

				if (wireframe) {
					GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;
					effect.TextureEnabled = false;
				} else {
					effect.TextureEnabled = true;
				}

				effect.VertexColorEnabled = true;
				effect.LightingEnabled = false;
				effect.Texture = texture;

				effect.World = camera.World;
				effect.View = camera.View;
				effect.Projection = camera.Projection;

				GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
				GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
				GraphicsDevice.RenderState.CullMode = CullMode.None;
				GraphicsDevice.RenderState.DepthBufferEnable = false;

				effect.Begin(SaveStateMode.SaveState);
				effect.CurrentTechnique.Passes[0].Begin();

				GraphicsDevice.VertexDeclaration = declaration;
				GraphicsDevice.DrawUserIndexedPrimitives(
					PrimitiveType.TriangleList, Vertices, 0, Vertices.Length,
					Indices, 0, Indices.Length / 3);
				effect.CurrentTechnique.Passes[0].End();
				effect.End();

				if (wireframe) {
					GraphicsDevice.RenderState.FillMode = FillMode.Solid;
				}
			}
		}

		/// <summary>
		/// 矩形のテクスチャ座標をテクスチャマッピングにする。
		/// </summary>
		/// <param name="min"></param>
		/// <param name="max"></param>
		public void CreateSprite(Vector2 min, Vector2 max) {
			var mesh = BeginEdit();

			mesh.SourceTextureTransform
				= Matrix.CreateScale(1, -1, 1) * Matrix.CreateTranslation(0, 1, 0)
				* Matrix.CreateScale(new Vector3(max.X - min.X, max.Y - min.Y, 1))
				* Matrix.CreateTranslation(min.X, min.Y, 1);

			var t00 = new Vector2(0, 0);
			var t10 = new Vector2(1, 0);
			var t11 = new Vector2(1, 1);
			var t01 = new Vector2(0, 1);
			Vector2.Transform(ref t00, ref mesh.SourceTextureTransform, out t00);
			Vector2.Transform(ref t10, ref mesh.SourceTextureTransform, out t10);
			Vector2.Transform(ref t11, ref mesh.SourceTextureTransform, out t11);
			Vector2.Transform(ref t01, ref mesh.SourceTextureTransform, out t01);

			mesh.Vertices = new List<VertexPositionColorTexture>
			{
				new VertexPositionColorTexture(Vector3.Zero, Color.White, t01),
				new VertexPositionColorTexture(Vector3.Zero, Color.White, t11),
				new VertexPositionColorTexture(Vector3.Zero, Color.White, t10),
				new VertexPositionColorTexture(Vector3.Zero, Color.White, t00),
			};
			mesh.Indices = new List<int> { 0, 1, 2, 2, 3, 0 };

			EndEdit();
		}

		public void CreatePuff(Vector2 min, Vector2 max, int corners) {
			var mesh = BeginEdit();

			mesh.SourceTextureTransform
				= Matrix.CreateScale(1, -1, 1) * Matrix.CreateTranslation(0, 1, 0)
				* Matrix.CreateScale(new Vector3(max.X - min.X, max.Y - min.Y, 1))
				* Matrix.CreateTranslation(min.X, min.Y, 1);

			mesh.Vertices = new List<VertexPositionColorTexture>(corners + 1);
			mesh.Indices = new List<int>(corners * 3);

			var points = new Vector2[corners + 1];
			points[0].X = 0.5f;
			points[0].Y = 0.5f;
			for (int i = 0; i < corners; i++) {
				var a = i * ((Math.PI * 2) / corners);
				points[i + 1].X = (float)Math.Sin(a) * 0.5f + 0.5f;
				points[i + 1].Y = (float)Math.Cos(a) * 0.5f + 0.5f;
				mesh.Indices.Add(0);
				mesh.Indices.Add(i + 1);
				mesh.Indices.Add(((i + 1) % corners) + 1);
			}

			for (int i = 0; i < points.Length; i++) {
				Vector2 textureCoordinate;
				Vector2.Transform(ref points[i], ref mesh.SourceTextureTransform, out textureCoordinate);
				mesh.Vertices.Add(
					new VertexPositionColorTexture(new Vector3(points[i], 0), Color.White, textureCoordinate));
			}

			EndEdit();
		}
	}

}
