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

namespace XELF.Sphynx {

	public interface ICamera {
		Matrix World { get; set; }
		Matrix View { get; set; }
		Matrix Projection { get; set; }
		Vector3 Position { get; set; }

		Vector3 CameraPosition { get; }
		float FieldOfView { get; set; }
		float AspectRatio { get; set; }
		float NearPlaneDistance { get; set; }
		float FarPlaneDistance { get; set; }
	}

	public class Camera : ICamera {
		public Camera() {
			World = Matrix.Identity;
			View = Matrix.Identity;
			Projection = Matrix.Identity;
		}
		public Matrix World { get; set; }
		public Matrix View { get; set; }
		public Matrix Projection { get; set; }

		public Vector3 Position {
			get { return World.Translation; }
			set {
				var world = World;
				world.Translation = value;
				World = world;
			}
		}

		Vector3 cameraPosition;
		public Vector3 CameraPosition {
			get { return cameraPosition; }
		}

		public void LookAt(Vector3 cameraPosition, Vector3 cameraTarget, Vector3 cameraUpVector) {
			View = Matrix.CreateLookAt(this.cameraPosition = cameraPosition, cameraTarget, cameraUpVector);
		}
		public void SetPerspectiveFieldOfView(float fieldOfView, float aspectRatio,
			float nearPlaneDistance, float farPlaneDisntace) {
			Projection = Matrix.CreatePerspectiveFieldOfView(
				FieldOfView = fieldOfView, AspectRatio = aspectRatio,
				NearPlaneDistance = nearPlaneDistance, FarPlaneDistance = farPlaneDisntace);
		}

		public float FieldOfView { get; set; }
		public float AspectRatio { get; set; }
		public float NearPlaneDistance { get; set; }
		public float FarPlaneDistance { get; set; }

		public virtual void Update() { }
	}

	public struct StrokeVertex {
		public Vector3 Position;
		public Color Color;
		public float Thickness;
	}

	class ActiveCamera : Camera {
		GraphicsDevice GraphicsDevice;
		public float Scale = 1.0f;

		public ActiveCamera(GraphicsDevice GraphicsDevice) {
			this.GraphicsDevice = GraphicsDevice;
		}

		public override void Update() {
			Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 10), Vector3.Zero, Vector3.Up);
			float viewBounds = 15.0f * Scale;
			float aspectRatio = GraphicsDevice.Viewport.AspectRatio;

			Matrix projection = Matrix.CreateOrthographicOffCenter(
				-viewBounds * aspectRatio, viewBounds * aspectRatio, -viewBounds, viewBounds, -10f, 10f);

			View = view;
			Projection = projection;

		}
	}

	public class VectorGraphics : IDisposable {
		BasicEffect effect;
		VertexDeclaration declaration;
		readonly List<VertexPositionColor> vertices = new List<VertexPositionColor>();
		SpriteBatch spriteBatch;
		public Texture2D IconTexture;
		Matrix iconMatrix;

		public ICamera Camera { get; set; }

		GraphicsDevice GraphicsDevice;

		public VectorGraphics(GraphicsDevice graphicsDevice) {
			this.GraphicsDevice = graphicsDevice;
			this.effect = new BasicEffect(GraphicsDevice, null);
			declaration = new VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);
			spriteBatch = new SpriteBatch(GraphicsDevice);
		}
		public void Dispose() {
			declaration.Dispose();
			effect.Dispose();
		}
		public void Begin() {
			var viewport = GraphicsDevice.Viewport;

			iconMatrix =
				Camera.World
				* Camera.View
				* Camera.Projection
				* Matrix.CreateTranslation(1, -1, 0)
				* Matrix.CreateScale(viewport.Width * 0.5f, viewport.Height * -0.5f, 1);

			spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Texture, SaveStateMode.SaveState);
		}
		public void DrawCurve(ref Vector2 p0, ref Vector2 p1, ref Vector2 p2, ref Vector2 p3, Color color) {
			var v0 = new Vector3(p0, 1);
			var v1 = new Vector3(p1, 1);
			var v2 = new Vector3(p2, 1);
			var v3 = new Vector3(p3, 1);
			DrawCurve(ref v0, ref v1, ref v2, ref v3, color);
		}
		public void DrawCurve(ref Vector3 p0, ref Vector3 p1, ref Vector3 p2, ref Vector3 p3, Color color) {
			Vector3 start = p1, end;

			int splits = 16;

			for (int i = 1; i <= splits; i++) {
				float amount = i * (1.0f / splits);
				Vector3.CatmullRom(ref p0, ref p1, ref p2, ref p3, amount, out end);
				DrawLine(start, color, end, color);
				start = end;
			}
		}
		public void DrawClosedCurve(Vector3[] points, Color color) {
			var length = points.Length;
			for (int i = 0; i < length; i++) {
				DrawCurve(ref points[i],
					ref points[(i + 1) % length],
					ref points[(i + 2) % length],
					ref points[(i + 3) % length], color);
			}
		}
		public void DrawClosedLine(Vector3[] points, Color color) {
			var length = points.Length;
			if (length <= 0) return;
			for (int i = 0; i < length; i++) {
				DrawLine(ref points[i], color, ref points[(i + 1) % length], color);
			}
		}

		public void DrawLine(
			Vector3 startPosition, Color startColor,
			Vector3 endPosition, Color endColor) {
			DrawLine(ref startPosition, startColor, ref endPosition, endColor);
		}
		public void DrawLine(
			ref Vector3 startPosition, Color startColor,
			ref Vector3 endPosition, Color endColor) {
			mode = Mode.Line;
			vertices.Add(new VertexPositionColor(startPosition, startColor));
			vertices.Add(new VertexPositionColor(endPosition, endColor));
		}
		public void DrawLine(
			Vector2 startPosition, Color startColor,
			Vector2 endPosition, Color endColor) {
			mode = Mode.Line;

			vertices.Add(new VertexPositionColor(new Vector3(startPosition, 1), startColor));
			vertices.Add(new VertexPositionColor(new Vector3(endPosition, 1), endColor));
		}
		public void DrawPoint(Vector3 position, Color color) {
			mode = Mode.Point;
			vertices.Add(new VertexPositionColor(position, color));
		}
		public void DrawPoint(
			Vector2 position, Color color) {
			mode = Mode.Point;
			vertices.Add(new VertexPositionColor(new Vector3(position, 1), color));
		}

		public static void MiterJoint(ref Vector3 a, ref Vector3 b, out Vector3 result) {
			a.Normalize();
			b.Normalize();
			Vector3.Subtract(ref b, ref a, out result);
			//Vector3.Add(ref a, ref b, out result);
			result.Normalize();
			result = new Vector3(-result.Y, result.X, result.Z);

			float cos;
			Vector3.Dot(ref a, ref result, out cos);
			result /= (float)Math.Sqrt(1 - cos * cos);
		}

		public void DrawPolygon(params StrokeVertex[] Points) {
			var corners = Points.Length;
			Vector3[] points = new Vector3[Points.Length * 2];

			switch (Points.Length) {
			case 2: {
					Vector3 d;
					Vector3.Subtract(ref Points[1].Position, ref Points[0].Position, out d);
					d.Normalize();
					var joint = new Vector3(-d.Y, d.X, d.Z);
					points[0] = Points[0].Position - joint * Points[0].Thickness;
					points[1] = Points[0].Position + joint * Points[0].Thickness;
					points[2] = Points[1].Position - joint * Points[1].Thickness;
					points[3] = Points[1].Position + joint * Points[1].Thickness;
				}
				break;
			default:
				for (int i = 0; i < corners; i++) {
					var h = (i + corners - 1) % corners;
					var j = (i + 1) % corners;

					Vector3 center = Points[i].Position;
					Vector3 a, b;
					Vector3.Subtract(ref Points[h].Position, ref center, out a);
					Vector3.Subtract(ref Points[j].Position, ref center, out b);
					Vector3 joint;
					MiterJoint(ref a, ref b, out joint);
					Vector3.Multiply(ref joint, Points[i].Thickness, out joint);
					Vector3.Subtract(ref center, ref joint, out points[i * 2]);
					Vector3.Add(ref center, ref joint, out points[i * 2 + 1]);
				}

				break;
			}

			mode = Mode.Polygon;

			for (int i = 0; i < corners; i++) {
				var j = (i + 1) % corners;
				vertices.Add(new VertexPositionColor(points[i * 2], Points[i].Color));
				vertices.Add(new VertexPositionColor(points[i * 2 + 1], Points[i].Color));
				vertices.Add(new VertexPositionColor(points[j * 2 + 1], Points[j].Color));
				vertices.Add(new VertexPositionColor(points[j * 2 + 1], Points[j].Color));
				vertices.Add(new VertexPositionColor(points[j * 2], Points[j].Color));
				vertices.Add(new VertexPositionColor(points[i * 2], Points[i].Color));
			}
		}

		public void FillPolygon(Color color, params Vector3[] Points) {
			if (Points.Length < 3)
				return;
			mode = Mode.Polygon;
			for (int i = 2; i < Points.Length; i++) {
				vertices.Add(new VertexPositionColor(Points[0], color));
				vertices.Add(new VertexPositionColor(Points[i - 1], color));
				vertices.Add(new VertexPositionColor(Points[i], color));
			}
		}
		Mode _mode;
		Mode mode {
			get { return _mode; }
			set {
				if (_mode != value) {
					Flush();
					Begin();
					_mode = value;
				}
			}
		}

		enum Mode {
			None,
			Point,
			Line,
			Sprite,
			Polygon,
		}

		static readonly Vector2[] ellipseVertices;
		static readonly Vector2[] rectangleVertices =
		{
			new Vector2(-1, -1),
			new Vector2(+1, -1),
			new Vector2(+1, +1),
			new Vector2(-1, +1),
		};

		static VectorGraphics() {
			ellipseVertices = new Vector2[64];
			var a0 = (Math.PI * 2) / ellipseVertices.Length;
			for (int i = 0; i < ellipseVertices.Length; i++) {
				var a = a0 * i;
				ellipseVertices[i] = new Vector2((float)Math.Cos(a), (float)Math.Sin(a));
			}
		}

		/// <summary>
		/// 矩形を変換行列で変換して描画する。元の頂点は、(-1, -1), (+1, -1), (+1, +1), (-1, -1)。
		/// </summary>
		/// <param name="transform">変換行列</param>
		/// <param name="color">円の色</param>
		public void DrawRectangle(Matrix transform, Color color) {
			Vector2 p0 = rectangleVertices[rectangleVertices.Length - 1];
			Vector2.Transform(ref p0, ref transform, out p0);
			for (int i = 0; i < rectangleVertices.Length; i++) {
				Vector2 p1 = rectangleVertices[i];
				Vector2.Transform(ref p1, ref transform, out p1);
				DrawLine(p0, color, p1, color);
				p0 = p1;
			}
		}

		/// <summary>
		/// 原点を中心とする半径1の円を変換行列で変換して描画する。
		/// </summary>
		/// <param name="transform">変換行列</param>
		/// <param name="color">円の色</param>
		public void DrawEllipse(Matrix transform, Color color) {
			Vector3 p0 = new Vector3(ellipseVertices[ellipseVertices.Length - 1], 0);
			Vector3.Transform(ref p0, ref transform, out p0);
			for (int i = 0; i < ellipseVertices.Length; i++) {
				Vector3 p1 = new Vector3(ellipseVertices[i], 0);
				Vector3.Transform(ref p1, ref transform, out p1);
				DrawLine(p0, color, p1, color);
				p0 = p1;
			}
		}
		public void DrawEllipse(Vector2 center, float radius, Color color) {
			Vector2 size = new Vector2(radius);
			DrawEllipse(center - size, center + size, color);
		}
		public void DrawEllipse(Vector2 center, Vector2 size, float rotation, Color color) {
			var transform = Matrix.CreateScale(size.X, size.Y, 1)
				* Matrix.CreateRotationZ(rotation) * Matrix.CreateTranslation(center.X, center.Y, 1);
			DrawEllipse(transform, color);
		}
		public void DrawEllipse(Vector2 position0, Vector2 position1, Color color) {
			Vector2 center = position0 + position1;
			Vector2 size = position1 - position0;
			Vector2.Multiply(ref center, 0.5f, out center);
			Vector2.Multiply(ref size, 0.5f, out size);
			Vector2 p0 = ellipseVertices[ellipseVertices.Length - 1] * size + center;
			for (int i = 0; i < ellipseVertices.Length; i++) {
				Vector2 p1 = ellipseVertices[i] * size + center;
				DrawLine(p0, color, p1, color);
				p0 = p1;
			}
		}

		public void End() {
			Flush();
		}
		void Flush() {
			if (vertices.Count > 0) {
				effect.World = Camera.World;
				effect.View = Camera.View;
				effect.Projection = Camera.Projection;
				effect.VertexColorEnabled = true;
				effect.LightingEnabled = false;
				effect.Begin();
				effect.CurrentTechnique.Passes[0].Begin();
				GraphicsDevice.RenderState.CullMode = CullMode.None;
				GraphicsDevice.VertexDeclaration = declaration;

				switch (mode) {
				case Mode.Point:
					GraphicsDevice.RenderState.PointSize = 5;
					GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.PointList,
						vertices.ToArray(), 0, vertices.Count);
					break;
				case Mode.Line:
					GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList,
						vertices.ToArray(), 0, vertices.Count / 2);
					break;
				case Mode.Sprite:
					break;
				case Mode.Polygon:
					GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList,
						vertices.ToArray(), 0, vertices.Count / 3);
					break;
				}
				_mode = Mode.None;
				vertices.Clear();
				effect.CurrentTechnique.Passes[0].End();
				effect.End();
			}
			spriteBatch.End();
		}

		static Rectangle[] iconRectangles = {
			new Rectangle(0x080,0x100,0x080,0x080), // Fixed
			new Rectangle(0x100,0x100,0x080,0x080), // Cursor
		};
		public void DrawIcon(IconKind kind, Vector2 position, Color color, float scale) {
			Vector2 p = Vector2.Transform(position, iconMatrix);
			Rectangle rectangle = iconRectangles[(int)kind];
			spriteBatch.Draw(IconTexture, p, rectangle, color, 0,
				new Vector2(rectangle.Width, rectangle.Height) * 0.5f, scale,
				SpriteEffects.None, 0);
		}
		public void DrawIcon(IconKind kind, Vector3 position, Color color, float scale) {
			Vector3 p = Vector3.Transform(position, iconMatrix);
			Rectangle rectangle = iconRectangles[(int)kind];
			spriteBatch.Draw(IconTexture, new Vector2(p.X, p.Y), rectangle, color, 0,
				new Vector2(rectangle.Width, rectangle.Height) * 0.5f, scale,
				SpriteEffects.None, 0);
		}
		public void DrawSprite(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color) {
			spriteBatch.Draw(texture, position, sourceRectangle, color);
		}

	}

	/// <summary>
	/// 線・楕円・アイコンの描画を提供する。
	/// </summary>
	public class VectorGraphicsComponent : DrawableGameComponent {
		VectorGraphics batch;

		public VectorGraphicsComponent(Game game)
			: base(game) {
		}
		protected override void LoadContent() {
			batch = new VectorGraphics(GraphicsDevice);
			base.LoadContent();
		}

		protected override void UnloadContent() {
			batch.Dispose();
			base.UnloadContent();
		}

		public VectorGraphics Batch { get { return batch; } }

	}

	public enum IconKind {
		Fixed,
		Cursor,
	}

	/// <summary>
	/// グリッドの描画
	/// </summary>
	class GridDisplay {
		readonly VectorGraphics Graphics;
		readonly Camera camera;
		Color color = new Color(200, 200, 200, 50);
		static readonly float[] table = {
			0.95f, 0.125f, 0.25f, 0.125f,
			0.50f, 0.125f, 0.25f, 0.125f,
		};

		public GridDisplay(VectorGraphics Graphics, Camera camera) {
			this.Graphics = Graphics;
			this.camera = camera;
			Graphics.Camera = camera;
		}

		/// <summary>
		/// 描画する範囲
		/// </summary>
		public int Bounds = 40;

		public void Draw(Vector3 gridSize) {
			Graphics.Camera = camera;
			Graphics.Begin();

			Vector3 p0 = new Vector3(0, -Bounds, 1);
			Vector3 p1 = new Vector3(0, +Bounds, 1);

			int tableLength = table.Length;

			for (int x = -Bounds; x <= Bounds; x++) {
				p0.X = x;
				p1.X = x;
				Color c = new Color(color, table[Math.Abs(x) % tableLength]);
				Graphics.DrawLine(p0 * gridSize, c, p1 * gridSize, c);
			}

			p0.X = -Bounds;
			p1.X = +Bounds;
			for (int y = -Bounds; y <= Bounds; y++) {
				p0.Y = y;
				p1.Y = y;
				Color c = new Color(color, table[Math.Abs(y) % tableLength]);
				Graphics.DrawLine(p0 * gridSize, c, p1 * gridSize, c);
			}

			Graphics.End();
		}
	}

}
