using Cryville.Crtr.Skin.Components;
using System.Linq;
using UnityEngine;

namespace Cryville.Crtr.Event {
	public abstract class TransformHandler : ContainerHandler {
		protected abstract TransformHandler Parent { get; }
		public override void Init() {
			base.Init();
			sgos = Components.Where(c => c is SectionalGameObject).Cast<SectionalGameObject>().ToArray();
		}

		SectionalGameObject[] sgos;

		bool sflag; // Did pre-graphical update
		Vector3 spos; // Start position offset
		Vector3 ppt = Vector3.zero; // Previous screen point
		Vector3 plp = Vector3.zero; // Previous local point
		Vector3 pwp = Vector3.zero; // Previous world point
		Vector3 ppwp = Vector3.zero; // Previous parent world point
		double ptime; // Previous time
		float length;

		public override void StartPhysicalUpdate(ContainerState s) {
			base.StartPhysicalUpdate(s);
			if (CanDoGraphicalUpdate(s)) {
				TransformAwake(s);
			}
		}
		protected override void StartPreGraphicalUpdate(ContainerState s) {
			base.StartPreGraphicalUpdate(s);
			spos = Vector3.zero;
			sflag = true;
			pwp = Parent != null ? Parent.Position : Vector3.zero;
			TransformAwake(s);
		}
		protected override void StartGraphicalUpdate(ContainerState s) {
			base.StartGraphicalUpdate(s);
			if (RootTransform) {
				if (!sflag && Parent != null) { // No pre-graphical update; inherit from parent
					spos = Parent.Position + (Vector3)s.ScreenPoint - (Vector3)s.Parent.ScreenPoint;
				}
				TransformAwake(s);
				var p = Position;
				foreach (var i in sgos) {
					i.Reset();
					i.AppendPoint(p, Rotation);
				}
			}
		}
		void TransformAwake(ContainerState s) {
			plp = s.ScreenPoint;
			pwp = Vector3.zero;
			ptime = s.Time;
			Position = spos;
			Rotation = s.QuatDir;
		}

		public override void Update(ContainerState s, StampedEvent ev) {
			base.Update(s, ev);
			if (CanDoGraphicalUpdate(s)) {
				ppt = s.ScreenPoint;
				var tsv = s.ScrollVelocity;

				Vector3 dpt = ppt - plp; // Delta 2D point
				dpt.z = (float)((s.Time - ptime) * ChartPlayer.sv * tsv); // Delta Z
				Quaternion rotq = Quaternion.Euler(s.Direction); // Rotation
				var dwp = rotq * dpt; // Delta world point
				var nl = length + dwp.magnitude; // New length
				var tdist = s.Distance;
				if (nl >= tdist) { // TODO Fix dist
					s.Break();
					return;
				}
				length = nl;
				var wp = pwp + dwp; // World point
				pwp = wp;
				if (Parent != null) ppwp = Parent.Position;
				plp += dpt;

				ptime = s.Time;
				Position = pwp + spos;
				Rotation = s.QuatDir;

				if (!RootTransform || s.CloneType == 3) return;
				foreach (var i in sgos)
					i.AppendPoint(Position, Rotation);
			}
			else UpdatePosition(s);
		}

		void UpdatePosition(ContainerState s) {
			plp = s.ScreenPoint;
			pwp = Vector3.zero;
			ptime = s.Time;
			length = 0;
			Position = pwp + spos;
			Rotation = s.QuatDir;
		}

		// TODO Fix anchor rotation
		public override void Anchor() {
			base.Anchor();
			spos = ppt - Position;
			if (ptime < cs.Time) { // Pre-graphical update ends before anchor, compensate from parent
				var dspos = ppwp - (Parent != null ? Parent.Position : Vector3.zero);
				spos += dspos;
				Position -= dspos;
			}
		}

		protected override void EndGraphicalUpdate(ContainerState s) {
			base.EndGraphicalUpdate(s);
			EndUpdatePosition(s);
			var p = Position;
			foreach (var i in sgos) {
				i.AppendPoint(p, Rotation);
				i.Seal();
			}
			sflag = false;
		}

		void EndUpdatePosition(ContainerState s) {
			ppt = s.ScreenPoint;
			var tsv = s.ScrollVelocity;

			Vector3 dpt = ppt - plp;
			dpt.z = (float)((s.Time - ptime) * ChartPlayer.sv * tsv);
			Quaternion rotq = Quaternion.Euler(s.Direction);
			var dwp = rotq * dpt; // Delta world point
			var nl = length + dwp.magnitude; // New length
			var tdist = s.Distance;
			if (nl >= tdist)
				dwp *= (tdist - length) / (nl - length);
			length = nl;
			var wp = pwp + dwp; // World point
			pwp = wp;
			plp += dpt;
			Position = pwp + spos;
			Rotation = s.QuatDir;
		}
	}
}
