using Cryville.Common;
using Cryville.Common.Pdt;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;

namespace Cryville.Crtr {
	public enum TransitionType : byte {
		In = 0,
		Out = 1,
		InOut = 2,
		OutIn = 3,

		Ease = 0,
		Sine = 4,
		Expo = 8,
		Circ = 12,
		Elastic = 16,
		Back = 20,
		Bounce = 24,
	}

	public static class MotionLerper {
		public static void Lerp<T>(float time, float tt, T tv, float ft, T fv, TransitionType type, float rate, ref T result) where T : Vector {
			if (fv == null) fv = (T)ReflectionHelper.InvokeEmptyConstructor(tv.GetType());
			Lerp((time - ft) / (tt - ft), fv, tv, type, rate, ref result);
		}

		public static void Lerp<T>(float scaledTime, T from, T to, TransitionType type, float rate, ref T result) where T : Vector {
			if (scaledTime >= 1) { result = to; return; }
			if (scaledTime < 0) { result = from; return; }
			var r = (Vector)result;
			to.LerpWith(from, GetEaseTime(scaledTime, type, rate), ref r);
		}

		public static float Delerp<T>(T value, float tt, T tv, float ft, T fv, TransitionType type, float rate) where T : Vector {
			if (fv == null) fv = (T)ReflectionHelper.InvokeEmptyConstructor(tv.GetType());
			var t = Delerp(value, fv, tv, type, rate);
			return ft * (1 - t) + tt * t;
		}

		public static float Delerp<T>(T value, T from, T to, TransitionType type, float rate) where T : Vector {
			if (value.CompareTo(to) >= 0) return 1;
			if (value.CompareTo(from) < 0) return 0;
			float lerpedTime = to.DelerpWith(from, value);
			return GetUneaseTime(lerpedTime, type, rate);
		}

		public static float GetEaseTime(float time, TransitionType type, float rate) {
			switch ((byte)type & 3) {
				case (byte)TransitionType.In:
					return 1 - GetEaseOutTime(1 - time, type, rate);
				case (byte)TransitionType.Out:
					return GetEaseOutTime(time, type, rate);
				case (byte)TransitionType.InOut:
					time *= 2;
					if (time <= 1) return (1 - GetEaseOutTime(1 - time, type, rate)) / 2;
					else return (GetEaseOutTime(time - 1, type, rate) + 1) / 2;
				case (byte)TransitionType.OutIn:
					time *= 2;
					if (time <= 1) return GetEaseOutTime(time, type, rate) / 2;
					else return (2 - GetEaseOutTime(2 - time, type, rate)) / 2;
				default:
					throw new ArgumentException("Unknown transition");
			}
		}

		static float GetUneaseTime(float time, TransitionType type, float rate) {
			switch ((byte)type & 3) {
				case (byte)TransitionType.In:
					return 1 - GetUneaseOutTime(1 - time, type, rate);
				case (byte)TransitionType.Out:
					return GetUneaseOutTime(time, type, rate);
				case (byte)TransitionType.InOut:
					time *= 2;
					if (time <= 1) return (1 - GetUneaseOutTime(1 - time, type, rate)) / 2;
					else return (GetUneaseOutTime(time - 1, type, rate) + 1) / 2;
				case (byte)TransitionType.OutIn:
					time *= 2;
					if (time <= 1) return GetUneaseOutTime(time, type, rate) / 2;
					else return (2 - GetUneaseOutTime(2 - time, type, rate)) / 2;
				default:
					throw new ArgumentException("Unknown transition");
			}
		}

		static float GetEaseOutTime(float p, TransitionType type, float rate) {
			switch ((byte)type & 252) {
				case (byte)TransitionType.Ease:
					return 1 - Mathf.Pow(1 - p, rate);
				case (byte)TransitionType.Sine:
					return Mathf.Sin(p * (Mathf.PI / 2));
				case (byte)TransitionType.Expo:
					return 1 - Mathf.Pow(2, -10 * p);
				case (byte)TransitionType.Circ:
					p -= 1;
					return Mathf.Sqrt(1 - p * p);
				case (byte)TransitionType.Elastic:
					float s = rate / (2 * Mathf.PI) * Mathf.Asin(1);
					return Mathf.Pow(2, -10 * p) * Mathf.Sin((p - s) * (2 * Mathf.PI) / rate) + 1;
				case (byte)TransitionType.Back:
					p -= 1;
					return p * p * ((rate + 1) * p + rate) + 1;
				case (byte)TransitionType.Bounce:
					if (p < 1 / 2.75) {
						return 7.5625f * p * p;
					}
					else if (p < (2 / 2.75)) {
						p -= 1.5f / 2.75f;
						return 7.5625f * p * p + .75f;
					}
					else if (p < 2.5 / 2.75) {
						p -= 2.25f / 2.75f;
						return 7.5625f * p * p + .9375f;
					}
					else {
						p -= 2.625f / 2.75f;
						return 7.5625f * p * p + .984375f;
					}
				default:
					throw new ArgumentException("Unknown transition");
			}
		}

		static float GetUneaseOutTime(float p, TransitionType type, float rate) {
			switch ((byte)type & 252) {
				case (byte)TransitionType.Ease:
					return 1 - Mathf.Pow(1 - p, 1 / rate);
				case (byte)TransitionType.Sine:
					return Mathf.Asin(p) / (Mathf.PI / 2);
				case (byte)TransitionType.Expo:
					return -Mathf.Log(1 - p, 2) / 10;
				case (byte)TransitionType.Circ:
					return 1 - Mathf.Sqrt(1 - p * p);
				default:
					throw new ArgumentException("Unknown transition");
			}
		}
	}

	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)ReflectionHelper.InvokeEmptyConstructor(type)) { }

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

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

	public class RealtimeMotionValue {
		public Vector AbsoluteValue;
		List<MotionNode> RelativeNodes;

		public RealtimeMotionValue Init(Vector init) {
			RelativeNodes = new List<MotionNode> {
				new MotionNode() {
					Id = 0,
					Time = new Vec1(0),
					Rate = new Vec1(1),
					Value = init
				}
			};
			AbsoluteValue = (Vector)ReflectionHelper.InvokeEmptyConstructor(init.GetType());
			return this;
		}

		public RealtimeMotionValue Clone() {
			var r = new RealtimeMotionValue() {
				AbsoluteValue = AbsoluteValue.Clone()
			};

			var rel = new List<MotionNode>(RelativeNodes.Count);
			for (var i = 0; i < RelativeNodes.Count; i++) {
				rel.Add(RelativeNodes[i].Clone());
			}
			r.RelativeNodes = rel;

			return r;
		}

		public void CopyTo(RealtimeMotionValue dest) {
			AbsoluteValue.CopyTo(dest.AbsoluteValue);
			int tcount = RelativeNodes.Count;
			int dcount = dest.RelativeNodes.Count;
			int count = Math.Min(tcount, dcount);
			for (int i = 0; i < count; i++) {
				RelativeNodes[i].CopyTo(dest.RelativeNodes[i]);
			}
			if (tcount > dcount)
				for (int i = dcount; i < tcount; i++)
					dest.RelativeNodes.Add(RelativeNodes[i].Clone());
			else
				dest.RelativeNodes.RemoveRange(tcount, dcount - tcount);
		}

		public MotionNode GetRelativeNode(ushort id) {
			int i = RelativeNodes.FindIndex(n => n.Id == id);
			if (i == -1) {
				var r = GetRelativeNode((ushort)(id - 1));
				r.Time = null;
				r.Id = id;
				return r;
			}
			return RelativeNodes[i];
		}

		public MotionNode QueryRelativeNode(ushort id) {
			int i = RelativeNodes.FindIndex(n => n.Id == id);
			MotionNode cnode;
			if (i == -1) cnode = new MotionNode() { Id = id };
			else cnode = RelativeNodes[i];
			return cnode;
		}

		public void SetRelativeNode(MotionNode node) {
			int i = RelativeNodes.FindIndex(n => n.Id == node.Id);
			MotionNode cnode;
			if (i == -1) {
				cnode = new MotionNode() {
					Id = node.Id,
					Time = new Vec1(0),
					Rate = new Vec1(1),
					Value = (Vector)ReflectionHelper.InvokeEmptyConstructor(node.Value.GetType())
				};
			}
			else {
				cnode = RelativeNodes[i];
				RelativeNodes.RemoveAt(i);
			}
			if (node.Time != null) cnode.Time = node.Time;
			if (node.Transition.HasValue) cnode.Transition = node.Transition.Value;
			if (node.Rate != null) cnode.Rate = node.Rate;
			if (node.Value != null) cnode.Value.ReplaceFrom(node.Value);

			int i2 = RelativeNodes.BinarySearch(cnode);
			if (i2 < 0) i2 = ~i2;
			RelativeNodes.Insert(i2, cnode);
		}

		/// <summary>
		/// Computes the motion value at the given relative time.
		/// </summary>
		/// <typeparam name="T">The vector type of the value.</typeparam>
		/// <param name="reltime">The relative time.</param>
		/// <param name="result">The result.</param>
		/// <returns></returns>
		public void GetValue<T>(float reltime, ref T result) where T : Vector {
			int i = BinarySearch(reltime);
			if (i >= 0) RelativeNodes[i].Value.CopyTo(result);
			else {
				i = ~i;
				if (i >= RelativeNodes.Count) {
					RelativeNodes[i - 1].Value.CopyTo(result);
				}
				else if (i == 0) {
					RelativeNodes[0].Value.CopyTo(result);
				}
				else {
					MotionNode t = RelativeNodes[i], f = RelativeNodes[i - 1];
					MotionLerper.Lerp(reltime, t.Time.Value, (T)t.Value, f.Time.Value, (T)f.Value, t.Transition.Value, t.Rate.Value, ref result);
				}
			}
			result.ApplyFrom(AbsoluteValue);
		}

		// Adopted from System.Collections.Generic.ArraySortHelper<T>.InternalBinarySearch(T[] array, int index, int length, T value, IComparer<T> comparer)
		int BinarySearch(float value) {
			int num = 0;
			int num2 = RelativeNodes.Count - 1;
			while (num <= num2) {
				int num3 = num + (num2 - num >> 1);
				int num4 = RelativeNodes[num3].Time.Value.CompareTo(value);
				if (num4 == 0) return num3;
				if (num4 < 0) num = num3 + 1;
				else num2 = num3 - 1;
			}
			return ~num;
		}
	}

	public class MotionNode : IComparable<MotionNode> {
		public ushort Id;
		public Vec1 Time;
		float CmpTime { get { return Time != null ? Time.Value : 0; } }
		public TransitionType? Transition;
		public Vec1 Rate;
		public Vector Value;

		public MotionNode Clone() {
			return new MotionNode() {
				Id = Id,
				Time = (Vec1)Time.Clone(),
				Transition = Transition,
				Rate = (Vec1)Rate.Clone(),
				Value = Value.Clone()
			};
		}

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

		public int CompareTo(MotionNode other) {
			return CmpTime.CompareTo(other.CmpTime);
		}

		public void LerpWith(MotionNode start, float lerpedTime, ref MotionNode result) {
			result.Id = Id;
			if (Time == null) result.Time = null;
			else {
				var t = (Vector)result.Time;
				Time.LerpWith(start.Time, lerpedTime, ref t);
			}
			result.Transition = Transition;
			if (Rate == null) result.Rate = null;
			else {
				var t = (Vector)result.Rate;
				Rate.LerpWith(start.Rate, lerpedTime, ref t);
			}
			if (Value == null) result.Value = null;
			else Value.LerpWith(start.Value, lerpedTime, ref result.Value);
		}
	}

	public abstract class Vector : IComparable<Vector> {
		public Vector() { }

		public abstract int CompareTo(Vector other);

		public abstract void ApplyFrom(Vector parent);
		public abstract void ReplaceFrom(Vector parent);
		public abstract void LerpWith(Vector start, float lerpedTime, ref Vector result);
		public abstract float DelerpWith(Vector start, Vector value);
		public abstract bool IsZero();
		public override abstract string ToString();
		public abstract float[] ToArray();

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

		static readonly Type[] stringTypeArray = new Type[] { typeof(string) };
		static readonly Type[] floatArrayTypeArray = new Type[] { typeof(float[]) };
		public static Vector Construct(Type type, string s) {
			if (!typeof(Vector).IsAssignableFrom(type)) throw new ArgumentException(); // TODO
			return (Vector)type.GetConstructor(stringTypeArray).Invoke(new object[] { s });
		}
		public static Vector Construct(Type type, float[] values) {
			if (!typeof(Vector).IsAssignableFrom(type)) throw new ArgumentException(); // TODO
			return (Vector)type.GetConstructor(floatArrayTypeArray).Invoke(new object[] { values });
		}
	}

	public class Vec1 : Vector, IComparable<Vec1> {
		public float Value;

		public Vec1() { Value = 0; }
		public Vec1(string s) {
			Value = float.Parse(s);
		}
		public Vec1(float[] v) {
			Value = v[0];
		}
		public Vec1(float i) {
			Value = i;
		}

		public override int CompareTo(Vector other) {
			return CompareTo((Vec1)other);
		}
		public int CompareTo(Vec1 other) {
			return Value.CompareTo(other.Value);
		}

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

		public override void ReplaceFrom(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 float DelerpWith(Vector start, Vector value) {
			var s = (Vec1)start;
			var v = (Vec1)value;
			return (v.Value - s.Value) / (this.Value - s.Value);
		}

		public override bool IsZero() {
			throw new NotImplementedException();
		}

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

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

		public override float[] ToArray() {
			return new float[] { Value };
		}
	}

	public class VecI1 : Vector {
		public int Value;

		public VecI1() { Value = 0; }
		public VecI1(string s) {
			Value = int.Parse(s);
		}
		public VecI1(float[] v) {
			Value = (int)Math.Round(v[0]);
		}
		public VecI1(int i) {
			Value = i;
		}

		public override int CompareTo(Vector other) {
			throw new NotImplementedException();
		}

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

		public override void ReplaceFrom(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 float DelerpWith(Vector start, Vector value) {
			throw new NotImplementedException();
		}

		public override bool IsZero() {
			throw new NotImplementedException();
		}

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

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

		public override float[] ToArray() {
			return new float[] { Value };
		}
	}

	public class Vec1m : Vector {
		public float Value;

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

		public override int CompareTo(Vector other) {
			throw new NotImplementedException();
		}

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

		public override void ReplaceFrom(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 float DelerpWith(Vector start, Vector value) {
			throw new NotImplementedException();
		}

		public override bool IsZero() {
			throw new NotImplementedException();
		}

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

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

		public override float[] ToArray() {
			return new float[] { Value };
		}
	}

	public class VecPtComp : Vector {
		float? w, h;

		public VecPtComp() { w = h = 0; }
		public VecPtComp(string s) {
			Parse(s, out w, out h);
		}
		public VecPtComp(float[] v) {
			w = v[0]; h = v[1];
		}
		public VecPtComp(float w, float h) : base() {
			this.w = w;
			this.h = h;
		}

		public static void Parse(string s, out float? w, out float? h) {
			string[] c = s.Split('+');
			float rw = 0, rh = 0;
			bool aw = false, ah = false;
			if (s != "") {
				foreach (var i in c) {
					if (i == "0") {
						aw = true;
						ah = true;
						continue;
					}
					float n = float.Parse(i.Substring(0, i.Length - 1));
					if (i.EndsWith("w")) {
						rw += n;
						aw = true;
					}
					else if (i.EndsWith("h")) {
						rh += n;
						ah = true;
					}
				}
			}
			w = aw ? rw : (float?)null;
			h = ah ? rh : (float?)null;
		}

		public static VecPtComp Parse(string s) {
			var r = new VecPtComp();
			float? w, h;
			Parse(s, out w, out h);
			r.w = w; r.h = h;
			return r;
		}

		public override int CompareTo(Vector other) {
			throw new NotImplementedException();
		}

		public override void ApplyFrom(Vector parent) {
			var p = (VecPtComp)parent;
			w += p.w ?? 0; h += p.h ?? 0;
		}

		public override void ReplaceFrom(Vector parent) {
			var p = (VecPtComp)parent;
			if (p.w.HasValue) w = p.w;
			if (p.h.HasValue) h = p.h;
		}

		public override void LerpWith(Vector start, float lerpedTime, ref Vector result) {
			throw new NotImplementedException();
		}

		public override float DelerpWith(Vector start, Vector value) {
			throw new NotImplementedException();
		}

		public override bool IsZero() {
			throw new NotImplementedException();
		}

		public float ToFloat(Rect rect) {
			return (w ?? 0) * rect.width + (h ?? 0) * rect.height;
		}

		public override void CopyTo(Vector dest) {
			var d = (VecPtComp)dest;
			d.w = w; d.h = h;
		}

		public static string ToString(float? w, float? h) {
			List<string> list = new List<string>();
			if (w != null) list.Add(w.ToString() + "w");
			if (h != null) list.Add(h.ToString() + "h");
			return string.Join("+", list.ToArray());
		}

		public override string ToString() {
			return ToString(w, h);
		}

		public override float[] ToArray() {
			return new float[] { w.Value, h.Value };
		}
	}

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

		public Vec3() { x = y = z = 0; }
		public Vec3(string s) {
			Match m = Regex.Match(s, @"^(\^?)(.*?),(.*?),(.*?)$");
			if (m.Groups[1].Value != "") {
				float tx = 0, ty = 0, tz = 0;
				if (m.Groups[2].Value != "")
					tx = VecPtComp.Parse(m.Groups[2].Value).ToFloat(ChartPlayer.hitRect);
				if (m.Groups[3].Value != "")
					ty = VecPtComp.Parse(m.Groups[3].Value).ToFloat(ChartPlayer.hitRect);
				if (m.Groups[4].Value != "")
					tz = VecPtComp.Parse(m.Groups[4].Value).ToFloat(ChartPlayer.hitRect);
				Vector3 r = Quaternion.LookRotation(new Vector3(tx, ty, tz)).eulerAngles;
				x = r.x; y = r.y; z = r.z;
			}
			else {
				if (m.Groups[2].Value != "")
					x = float.Parse(m.Groups[2].Value);
				if (m.Groups[3].Value != "")
					y = float.Parse(m.Groups[3].Value);
				if (m.Groups[4].Value != "")
					z = float.Parse(m.Groups[4].Value);
			}
		}
		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 int CompareTo(Vector other) {
			throw new NotImplementedException();
		}

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

		public override void ReplaceFrom(Vector parent) {
			var p = (Vec3)parent;
			if (p.x.HasValue) x = p.x;
			if (p.y.HasValue) y = p.y;
			if (p.z.HasValue) 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 = x.HasValue ? (s.x.Value * (1 - lerpedTime) + x.Value * lerpedTime) : s.x.Value;
			r.y = y.HasValue ? (s.y.Value * (1 - lerpedTime) + y.Value * lerpedTime) : s.y.Value;
			r.z = z.HasValue ? (s.z.Value * (1 - lerpedTime) + z.Value * lerpedTime) : s.z.Value;
		}

		public override float DelerpWith(Vector start, Vector value) {
			throw new NotImplementedException();
		}

		public override bool IsZero() {
			throw new NotImplementedException();
		}

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

		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("{0},{1},{2}",
				x != null ? x.ToString() : "",
				y != null ? y.ToString() : "",
				z != null ? z.ToString() : ""
			);
		}

		public override float[] ToArray() {
			return new float[] { x.Value, y.Value, z.Value };
		}
	}

	public class VecPt : Vector {
		public float? xw, xh, yw, yh;

		public VecPt() { xw = xh = yw = yh = 0; }
		public VecPt(string s) {
			Match m = Regex.Match(s, "^(.*?),(.*?)$");
			VecPtComp.Parse(m.Groups[1].Value, out xw, out xh);
			VecPtComp.Parse(m.Groups[2].Value, out yw, out yh);
		}
		public VecPt(float[] v) {
			xw = v[0]; xh = v[1]; yw = v[2]; yh = v[3];
		}
		public VecPt(float xw, float xh, float yw, float yh) {
			this.xw = xw; this.xh = xh; this.yw = yw; this.yh = yh;
		}

		public override int CompareTo(Vector other) {
			throw new NotImplementedException();
		}

		public override void ApplyFrom(Vector parent) {
			var p = (VecPt)parent;
			xw += p.xw ?? 0; xh += p.xh ?? 0;
			yw += p.yw ?? 0; yh += p.yh ?? 0;
		}

		public override void ReplaceFrom(Vector parent) {
			var p = (VecPt)parent;
			if (p.xw.HasValue) xw = p.xw;
			if (p.xh.HasValue) xh = p.xh;
			if (p.yw.HasValue) yw = p.yw;
			if (p.yh.HasValue) yh = p.yh;
		}

		public override void LerpWith(Vector start, float lerpedTime, ref Vector result) {
			var r = (VecPt)result;
			if (start == null) {
				r.xw = xw;
				r.xh = xh;
				r.yw = yw;
				r.yh = yh;
				return;
			}
			var s = (VecPt)start;
			r.xw = xw.HasValue ? (s.xw.Value * (1 - lerpedTime) + xw.Value * lerpedTime) : s.xw.Value;
			r.xh = xh.HasValue ? (s.xh.Value * (1 - lerpedTime) + xh.Value * lerpedTime) : s.xh.Value;
			r.yw = yw.HasValue ? (s.yw.Value * (1 - lerpedTime) + yw.Value * lerpedTime) : s.yw.Value;
			r.yh = yh.HasValue ? (s.yh.Value * (1 - lerpedTime) + yh.Value * lerpedTime) : s.yh.Value;
		}

		public override float DelerpWith(Vector start, Vector value) {
			throw new NotImplementedException();
		}

		public override bool IsZero() {
			throw new NotImplementedException();
		}

		public Vector2 ToVector2(Rect rect) {
			return new Vector2(
				xw.Value * rect.width + xh.Value * rect.height,
				yw.Value * rect.width + yh.Value * rect.height
			);
		}

		public override void CopyTo(Vector dest) {
			var d = (VecPt)dest;
			d.xw = xw; d.xh = xh; d.yw = yw; d.yh = yh;
		}

		public override string ToString() {
			return VecPtComp.ToString(xw, xh) + "," + VecPtComp.ToString(yw, yh);
		}

		public override float[] ToArray() {
			return new float[] { xw.Value, xh.Value, yw.Value, yh.Value };
		}
	}

	public class VecCtrl : Vector {
		public float? xw, xh, yw, yh, z;
		public VecCtrl() { xw = xh = yw = yh = z = 0; }
		public VecCtrl(string s) {
			Match m = Regex.Match(s, "^(.*?),(.*?),(.*?)$");
			VecPtComp.Parse(m.Groups[1].Value, out xw, out xh);
			VecPtComp.Parse(m.Groups[2].Value, out yw, out yh);
			if (m.Groups[3].Value != "") z = float.Parse(m.Groups[3].Value);
		}
		public VecCtrl(float[] v) {
			xw = v[0]; xh = v[1]; yw = v[2]; yh = v[3]; z = v[4];
		}
		public VecCtrl(float xw, float xh, float yw, float yh, float z) {
			this.xw = xw; this.xh = xh; this.yw = yw; this.yh = yh; this.z = z;
		}

		public override int CompareTo(Vector other) {
			throw new NotImplementedException();
		}

		public override void ApplyFrom(Vector parent) {
			var p = (VecCtrl)parent;
			xw += p.xw ?? 0; xh += p.xh ?? 0;
			yw += p.yw ?? 0; yh += p.yh ?? 0;
			z += p.z ?? 0;
		}

		public override void ReplaceFrom(Vector parent) {
			var p = (VecCtrl)parent;
			if (p.xw.HasValue) xw = p.xw;
			if (p.xh.HasValue) xh = p.xh;
			if (p.yw.HasValue) yw = p.yw;
			if (p.yh.HasValue) yh = p.yh;
			if (p.z.HasValue) z = p.z;
		}

		public override void LerpWith(Vector start, float lerpedTime, ref Vector result) {
			var r = (VecCtrl)result;
			if (start == null) {
				r.xw = xw;
				r.xh = xh;
				r.yw = yw;
				r.yh = yh;
				r.z = z;
				return;
			}
			var s = (VecCtrl)start;
			r.xw = xw.HasValue ? (s.xw.Value * (1 - lerpedTime) + xw.Value * lerpedTime) : s.xw.Value;
			r.xh = xh.HasValue ? (s.xh.Value * (1 - lerpedTime) + xh.Value * lerpedTime) : s.xh.Value;
			r.yw = yw.HasValue ? (s.yw.Value * (1 - lerpedTime) + yw.Value * lerpedTime) : s.yw.Value;
			r.yh = yh.HasValue ? (s.yh.Value * (1 - lerpedTime) + yh.Value * lerpedTime) : s.yh.Value;
			r.z = z.HasValue ? (s.z.Value * (1 - lerpedTime) + z.Value * lerpedTime) : s.z.Value;
		}

		public override float DelerpWith(Vector start, Vector value) {
			throw new NotImplementedException();
		}

		public override bool IsZero() {
			if (!xw.Equals(0)) return false;
			if (!xh.Equals(0)) return false;
			if (!yw.Equals(0)) return false;
			if (!yh.Equals(0)) return false;
			if (!z.Equals(0)) return false;
			return true;
		}

		public Vector3 ToVector3(Rect rect, float deltaz) {
			return new Vector3(
				xw.Value * rect.width + xh.Value * rect.height,
				yw.Value * rect.width + yh.Value * rect.height,
				z.Value * deltaz
			);
		}

		public override void CopyTo(Vector dest) {
			var d = (VecCtrl)dest;
			d.xw = xw; d.xh = xh; d.yw = yw; d.yh = yh; d.z = z;
		}

		public override string ToString() {
			return VecPtComp.ToString(xw, xh) + "," + VecPtComp.ToString(yw, yh) + "," + (z != null ? z.ToString() : "");
		}

		public override float[] ToArray() {
			return new float[] { xw.Value, xh.Value, yw.Value, yh.Value, z.Value };
		}
	}

	public class VectorSrc : PropSrc {
		readonly Func<Vector> _cb;
		public VectorSrc(Func<Vector> cb) { _cb = cb; }
		protected override unsafe void InternalGet(out int type, out byte[] value) {
			var arr = _cb().ToArray();
			if (arr.Length == 1) {
				type = PdtInternalType.Number;
				value = new byte[sizeof(float)];
				fixed (byte* ptr = value) {
					*(float*)ptr = arr[0];
				}
			}
			else {
				type = PdtInternalType.Vector;
				value = new byte[sizeof(float) * arr.Length + sizeof(int)];
				fixed (byte* rptr = value) {
					var ptr = (float*)rptr;
					for (int i = 0; i < arr.Length; i++, ptr++) {
						*ptr = arr[i];
					}
					*(int*)ptr = PdtInternalType.Number;
				}
			}
		}
	}

	public class VectorOp : PropOp {
		readonly Action<float[]> _cb;
		public VectorOp(Action<float[]> cb) { _cb = cb; }
		protected unsafe override 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)];
				fixed (float* ptr = values) {
					op.CopyTo((byte*)ptr, 0, op.Length - sizeof(int));
				}
			}
			else throw new InvalidOperationException("Invalid operand");
			_cb(values);
		}
	}
}
