using Cryville.Common.Buffers;
using Cryville.Common.Pdt;
using System;
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;

namespace Cryville.Crtr {
	public struct MotionRegistry {
		readonly Type m_Type;
		public Type Type {
			get { return m_Type; }
		}

		readonly Vector m_InitValue;
		public Vector InitValue {
			get { return m_InitValue.Clone(); }
		}

		readonly Vector m_GlobalInitValue;
		public Vector GlobalInitValue {
			get { return m_GlobalInitValue.Clone(); }
		}

		public MotionRegistry(Type type) : this((Vector)Activator.CreateInstance(type)) { }

		public MotionRegistry(Vector init) : this(init, init) { }

		public MotionRegistry(Vector init, Vector globalInit) {
			if (!init.GetType().Equals(globalInit.GetType())) throw new ArgumentException();
			m_Type = init.GetType();
			m_InitValue = init;
			m_GlobalInitValue = globalInit;
		}
	}

	public class MotionNode {
		public short Id = -1;
		public bool Reset;
		public Vec1 Time;
		public Vec1 EndTime;
		public PdtExpression Transition;
		public Vector Value;

		public void Init(Type type) {
			if (Time == null) Time = new Vec1(float.NegativeInfinity);
			if (EndTime == null) EndTime = new Vec1(float.NegativeInfinity);
			if (Value == null) Value = (Vector)Activator.CreateInstance(type);
		}

		public void CopyTo(MotionNode dest) {
			dest.Id = Id;
			Time.CopyTo(dest.Time);
			EndTime.CopyTo(dest.EndTime);
			dest.Transition = Transition;
			Value.CopyTo(dest.Value);
		}

		public void LerpWith(MotionNode start, Vector4 trans, ref MotionNode result) {
			result.Id = Id;
			if (Time == null) start.Time.CopyTo(result.Time);
			else {
				var t = (Vector)result.Time;
				Time.LerpWith(start.Time, trans, ref t);
			}
			if (EndTime == null) start.EndTime.CopyTo(result.EndTime);
			else {
				var t = (Vector)result.EndTime;
				EndTime.LerpWith(start.EndTime, trans, ref t);
			}
			if (Value == null) start.Value.CopyTo(result.Value);
			else Value.LerpWith(start.Value, trans, ref result.Value);
		}
	}

	internal class MotionNodePool : CategorizedPool<Type, MotionNode> {
		public static MotionNodePool Shared;
		private class Bucket : ObjectPool<MotionNode> {
			readonly Type _type;
			public Bucket(Type type, int capacity) : base(capacity) {
				_type = type;
			}
			protected override MotionNode Construct() {
				var result = new MotionNode();
				result.Init(_type);
				return result;
			}
		}
		readonly Dictionary<Type, ObjectPool<MotionNode>> m_buckets;
		protected override IDictionary<Type, ObjectPool<MotionNode>> Buckets { get { return m_buckets; } }
		public MotionNodePool() {
			m_buckets = new Dictionary<Type, ObjectPool<MotionNode>>();
			foreach (var reg in ChartPlayer.motionRegistry) {
				var type = reg.Value.Type;
				if (!m_buckets.ContainsKey(type))
					m_buckets.Add(type, new Bucket(type, 4096));
			}
			m_buckets.TrimExcess();
		}
	}

	public abstract class Vector {
		public Vector() { }

		public abstract byte Dimension { get; }

		public abstract void ApplyFrom(Vector parent);
		public abstract void LerpWith(Vector start, float lerpedTime, ref Vector result);
		public abstract void LerpWith(Vector start, Vector4 trans, ref Vector result);
		public abstract override string ToString();
		public abstract unsafe void ToArray(float* arr);

		public Vector Clone() {
			return (Vector)MemberwiseClone();
		}
		public abstract void CopyTo(Vector dest);

		static readonly Type[] floatArrayTypeArray = new Type[] { typeof(float[]) };
		public static Vector Construct(Type type, float[] values) {
			if (!typeof(Vector).IsAssignableFrom(type)) throw new ArgumentException("Type is not vector");
			return (Vector)type.GetConstructor(floatArrayTypeArray).Invoke(new object[] { values });
		}
	}

	public class Vec1 : Vector {
		public float Value;

		public Vec1() { Value = 0; }
		public Vec1(float[] v) { Value = v[0]; }
		public Vec1(float i) { Value = i; }

		public override byte Dimension { get { return 1; } }

		public override void ApplyFrom(Vector parent) {
			var p = (Vec1)parent;
			Value += p.Value;
		}

		public override void LerpWith(Vector start, float lerpedTime, ref Vector result) {
			var r = (Vec1)result;
			if (start == null) {
				r.Value = Value;
				return;
			}
			var s = (Vec1)start;
			r.Value = s.Value * (1 - lerpedTime) + Value * lerpedTime;
		}
		public override void LerpWith(Vector start, Vector4 trans, ref Vector result) {
			var r = (Vec1)result;
			if (start == null) {
				r.Value = Value;
				return;
			}
			var s = (Vec1)start;
			r.Value = s.Value * (1 - trans.x) + Value * trans.x;
		}

		public override void CopyTo(Vector dest) {
			var d = (Vec1)dest;
			d.Value = Value;
		}

		public override string ToString() {
			return Value.ToString(CultureInfo.InvariantCulture);
		}

		public override unsafe void ToArray(float* arr) {
			arr[0] = Value;
		}
	}

	public class VecI1 : Vector {
		public int Value;

		public VecI1() { Value = 0; }
		public VecI1(float[] v) { Value = (int)v[0]; }
		public VecI1(int i) { Value = i; }

		public override byte Dimension { get { return 1; } }

		public override void ApplyFrom(Vector parent) {
			var p = (VecI1)parent;
			Value += p.Value;
		}

		public override void LerpWith(Vector start, float lerpedTime, ref Vector result) {
			var r = (VecI1)result;
			if (start == null) {
				r.Value = Value;
				return;
			}
			var s = (VecI1)start;
			r.Value = (int)(s.Value * (1 - lerpedTime) + Value * lerpedTime);
		}
		public override void LerpWith(Vector start, Vector4 trans, ref Vector result) {
			var r = (VecI1)result;
			if (start == null) {
				r.Value = Value;
				return;
			}
			var s = (VecI1)start;
			r.Value = (int)(s.Value * (1 - trans.x) + Value * trans.x);
		}

		public override void CopyTo(Vector dest) {
			var d = (VecI1)dest;
			d.Value = Value;
		}

		public override string ToString() {
			return Value.ToString(CultureInfo.InvariantCulture);
		}

		public override unsafe void ToArray(float* arr) {
			arr[0] = Value;
		}
	}

	public class Vec1m : Vector {
		public float Value;

		public Vec1m() { Value = 1; }
		public Vec1m(float[] v) { Value = v[0]; }
		public Vec1m(float i) : base() { Value = i; }

		public override byte Dimension { get { return 1; } }

		public override void ApplyFrom(Vector parent) {
			var p = (Vec1m)parent;
			Value *= p.Value;
		}

		public override void LerpWith(Vector start, float lerpedTime, ref Vector result) {
			var r = (Vec1m)result;
			if (start == null) {
				r.Value = Value;
				return;
			}
			var s = (Vec1m)start;
			r.Value = s.Value * (1 - lerpedTime) + Value * lerpedTime;
		}
		public override void LerpWith(Vector start, Vector4 trans, ref Vector result) {
			var r = (Vec1m)result;
			if (start == null) {
				r.Value = Value;
				return;
			}
			var s = (Vec1m)start;
			r.Value = s.Value * (1 - trans.x) + Value * trans.x;
		}

		public override void CopyTo(Vector dest) {
			var d = (Vec1m)dest;
			d.Value = Value;
		}

		public override string ToString() {
			return Value.ToString(CultureInfo.InvariantCulture);
		}

		public override unsafe void ToArray(float* arr) {
			arr[0] = Value;
		}
	}

	public class Vec2 : Vector {
		float x, y;

		public Vec2() { x = y = 0; }
		public Vec2(float[] v) { x = v[0]; y = v[1]; }
		public Vec2(float x, float y) { this.x = x; this.y = y; }

		public override byte Dimension { get { return 2; } }

		public override void ApplyFrom(Vector parent) {
			var p = (Vec2)parent;
			x += p.x; y += p.y;
		}

		public override void LerpWith(Vector start, float lerpedTime, ref Vector result) {
			var r = (Vec2)result;
			if (start == null) {
				r.x = x;
				r.y = y;
				return;
			}
			var s = (Vec2)start;
			r.x = s.x * (1 - lerpedTime) + x * lerpedTime;
			r.y = s.y * (1 - lerpedTime) + y * lerpedTime;
		}
		public override void LerpWith(Vector start, Vector4 trans, ref Vector result) {
			var r = (Vec2)result;
			if (start == null) {
				r.x = x;
				r.y = y;
				return;
			}
			var s = (Vec2)start;
			r.x = s.x * (1 - trans.x) + x * trans.x;
			r.y = s.y * (1 - trans.y) + y * trans.y;
		}

		public Vector3 ToVector2() {
			return new Vector3(x, y);
		}

		public override void CopyTo(Vector dest) {
			var d = (Vec2)dest;
			d.x = x; d.y = y;
		}

		public override string ToString() {
			return string.Format(CultureInfo.InvariantCulture, "{0},{1}", x, y);
		}

		public override unsafe void ToArray(float* arr) {
			arr[0] = x;
			arr[1] = y;
		}
	}

	public class Vec3 : Vector {
		float x, y, z;

		public Vec3() { x = y = z = 0; }
		public Vec3(float[] v) { x = v[0]; y = v[1]; z = v[2]; }
		public Vec3(float x, float y, float z) { this.x = x; this.y = y; this.z = z; }

		public override byte Dimension { get { return 3; } }

		public override void ApplyFrom(Vector parent) {
			var p = (Vec3)parent;
			x += p.x; y += p.y; z += p.z;
		}

		public override void LerpWith(Vector start, float lerpedTime, ref Vector result) {
			var r = (Vec3)result;
			if (start == null) {
				r.x = x;
				r.y = y;
				r.z = z;
				return;
			}
			var s = (Vec3)start;
			r.x = s.x * (1 - lerpedTime) + x * lerpedTime;
			r.y = s.y * (1 - lerpedTime) + y * lerpedTime;
			r.z = s.z * (1 - lerpedTime) + z * lerpedTime;
		}
		public override void LerpWith(Vector start, Vector4 trans, ref Vector result) {
			var r = (Vec3)result;
			if (start == null) {
				r.x = x;
				r.y = y;
				r.z = z;
				return;
			}
			var s = (Vec3)start;
			r.x = s.x * (1 - trans.x) + x * trans.x;
			r.y = s.y * (1 - trans.y) + y * trans.y;
			r.z = s.z * (1 - trans.z) + z * trans.z;
		}

		public Vector3 ToVector3() {
			return new Vector3(x, y, z);
		}

		public override void CopyTo(Vector dest) {
			var d = (Vec3)dest;
			d.x = x; d.y = y; d.z = z;
		}

		public override string ToString() {
			return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2}", x, y, z);
		}

		public override unsafe void ToArray(float* arr) {
			arr[0] = x;
			arr[1] = y;
			arr[2] = z;
		}
	}

	public unsafe class VectorSrc : PropSrc.FixedBuffer<Vector> {
		const int MAX_DIMENSION = 4;
		public VectorSrc(Func<Vector> cb) : base(PdtInternalType.Vector, MAX_DIMENSION * sizeof(float) + sizeof(int), cb) {
			fixed (byte* rptr = buf) {
				var ptr = (float*)rptr;
				*(int*)(ptr + MAX_DIMENSION) = PdtInternalType.Number;
			}
		}
		protected override void InternalGet() {
			var v = _cb();
			if (v.Dimension > MAX_DIMENSION) throw new NotSupportedException("Vector dimension too large");
			fixed (byte* rptr = buf) {
				var ptr = (float*)rptr;
				v.ToArray(ptr);
			}
		}
	}

	public class VectorOp : PropOp.Fixed<float[]> {
		public VectorOp(Action<float[]> cb) : base(cb) { }
		protected override unsafe void Execute() {
			var op = GetOperand(0);
			float[] values;
			if (op.Type == PdtInternalType.Number) {
				values = new float[] { op.AsNumber() };
			}
			else if (op.Type == PdtInternalType.Vector) {
				int type;
				op.GetArraySuffix(out type, out _);
				if (type != PdtInternalType.Number)
					throw new InvalidOperationException("Not a vector of numbers");
				values = new float[(op.Length - sizeof(int)) / sizeof(float)];
				fixed (float* ptr = values) {
					op.CopyTo((byte*)ptr, 0, op.Length - sizeof(int));
				}
			}
			else throw new InvalidOperationException("Invalid operand");
			_cb(values);
		}
	}
}
