using Cryville.Common;
using Cryville.Common.Math;
using Cryville.Crtr.Components;
using Cryville.Crtr.Event;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Cryville.Crtr {
	class NoteHandler : ContainerHandler {
		readonly GroupHandler gh;
		public readonly Chart.Note Event;
		readonly PdtRuleset ruleset;
		public NoteHandler(GroupHandler gh, Chart.Note ev, PdtRuleset rs, Judge j) : base() {
			this.gh = gh;
			Event = ev;
			ruleset = rs;
			judge = j;
		}
		
		public override string TypeName {
			get {
				return "note";
			}
		}

		SectionalGameObject[] sgos;

		public override void Init() {
			base.Init();
			sgos = gogroup.GetComponentsInChildren<SectionalGameObject>();
		}
		protected override void PreAwake(ContainerState s) {
			base.PreAwake(s);
#if UNITY_5_6_OR_NEWER
			a_head.SetPositionAndRotation(Position = GetFramePoint(s.Parent, s.Track), Rotation = GetFrameRotation(s.Parent, s.Track));
#else
			a_head.position = Position = GetFramePoint(s.Parent, s.Track);
			a_head.rotation = Rotation = GetFrameRotation(s.Parent, s.Track);
#endif
		}
		protected override void Awake(ContainerState s) {
			base.Awake(s);
			if (s.CloneType == 2) {
				if (!gogroup) return;
				Chart.Note tev = Event;
				if (tev.IsLong) {
					foreach (var i in sgos) {
						i.Reset();
						i.AppendPoint(Position, Rotation);
					}
				}
				else {
#if UNITY_5_6_OR_NEWER
					gogroup.SetPositionAndRotation(Position, Rotation);
#else
				gogroup.position = Position;
				gogroup.rotation = Rotation;
#endif
				}
			}
		}

		public override void Update(ContainerState s, StampedEvent ev) {
			base.Update(s, ev);
			if (s.CloneType == 1) {
				Position = GetFramePoint(bs.Parent, bs.Track);
				Rotation = GetFrameRotation(bs.Parent, bs.Track);
			}
			else if (s.CloneType == 2) {
				Position = GetFramePoint(ts.Parent, ts.Track);
				Rotation = GetFrameRotation(ts.Parent, ts.Track);
				if (!gogroup) return;
				Chart.Note tev = Event;
				if (tev.IsLong) {
					foreach (var i in sgos)
						i.AppendPoint(Position, Rotation);
				}
			}
			else if (s.CloneType == 16) {
				if (ev == null) { }
				else if (ev.Unstamped == null) { }
				else if (ev.Unstamped is Chart.Judge) {
					var tev = (Chart.Judge)ev.Unstamped;
					Identifier name = default(Identifier);
					ChartPlayer.etor.ContextEvent = tev;
					ChartPlayer.etor.ContextState = s;
					ChartPlayer.etor.Evaluate(new PropOp.Identifier(v => name = new Identifier(v)), ruleset.judges[tev.Id].input);
					judge.Prepare(ev.Time, ev.Time + ev.Duration, name, ruleset.judges[tev.Id], cs);
					ChartPlayer.etor.ContextState = null;
					ChartPlayer.etor.ContextEvent = null;
				}
			}
		}

		public override void EndUpdate(ContainerState s) {
			if (s.CloneType == 2 && gogroup) {
				foreach (var i in sgos) i.Seal();
#if UNITY_5_6_OR_NEWER
				a_tail.SetPositionAndRotation(GetFramePoint(ts.Parent, ts.Track), Quaternion.Euler(ts.Direction));
#else
				a_tail.position = GetFramePoint(ts.Parent, ts.Track);
				a_tail.rotation = Quaternion.Euler(ts.Direction);
#endif
			}
			OpenAnchor("tail");
			base.EndUpdate(s);
			CloseAnchor("tail");
		}
		
		Vector3 GetFramePoint(ContainerState state, float track) {
			return GetFrame(state, track, th => ((TrackHandler)th.Handler).GetCurrentWorldPoint());
		}

		Quaternion GetFrameRotation(ContainerState state, float track) {
			var r = GetFrame(state, track, th => Quaternion.Euler(th.Direction) * Vector3.forward);
			return Quaternion.LookRotation(r, state.Normal);
		}

		Vector3 GetFrame(ContainerState state, float track, Func<ContainerState, Vector3> func) {
			// TODO
			int id = Mathf.FloorToInt(track);
			var ts0 = state.GetChild(id, typeof(Chart.Track));
			var p1 = func(ts0);
			if (track == id)
				return p1;
			var ts1 = state.GetChild(id + 1, typeof(Chart.Track));
			var p2 = func(ts1);
			bool c0 = ts0.Corner;
			bool c1 = ts1.Corner;
			float t = track - id;
			float deltaz = p2.z - p1.z;
			float nt = 1 - t;
			if (c0 && c1)
				return (1 - t) * p1 + t * p2;
			else {
				var ctrl = new List<Vector3>(2);
				if (!c0) {
					var tp = ts0.GetControlPoint(true, deltaz);
					if (tp != Vector3.zero) ctrl.Add(tp);
				}
				if (!c1) {
					var tp = ts1.GetControlPoint(false, -deltaz);
					if (tp != Vector3.zero) ctrl.Add(tp);
				}
				if (ctrl.Count == 0) {
					var frame = gh.GetCurrentFrame(func);
					return frame.Dot(
						ColumnVector<float>.WithPolynomialCoefficients(
							frame.Size, track
						),
						new Vector3Operator()
					);
				}
				else if (ctrl.Count == 1) {
					return nt * (nt * p1 + t * (ctrl[0] + p1)) + t * t * p2;
				}
				else {
					var t2 = t * t;
					return nt * (nt * (nt * p1 + t * (ctrl[0] + p1)) + t2 * (ctrl[1] + p1)) + t2 * t * p2;
				}
			}
		}
	}
}
