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;
		readonly Judge judge;
		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
				}
			}
		}

		readonly Dictionary<Chart.Motion, Vec1> phMotions = new Dictionary<Chart.Motion, Vec1>();
		bool invalidated = false;
		readonly List<StampedEvent.Judge> patchedJudgeEvents = new List<StampedEvent.Judge>();

		public override void StartUpdate(ContainerState s) {
			base.StartUpdate(s);
			/*if (s.CloneType == 16 && Event.judge != null) {
				// var etor = new Evaluator();
				//CompiledRuleset.PatchJudge(Event, ChartPlayer.cruleset.primary_judges[Event.judge], s.Time, etor, patchedJudgeEvents, !Event.@long);
			}*/
		}

		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;
				}
				else if (ev.Unstamped is Chart.Motion) {
					/*var tev = (Chart.Motion)ev.Unstamped;
					if (tev.Name != "judge") return;
					phMotions.Add(tev, (Vec1)s.GetRawValue<Vec1>(tev.Name).Clone());*/
				}
				else if (ev.Unstamped is InstantEvent) {
					/*var oev = ((InstantEvent)ev.Unstamped).Original;
					if (oev is Chart.Motion) {
						var tev = (Chart.Motion)oev;
						if (tev.Name != "judge") return;
						var v0 = phMotions[tev];
						var v1 = s.GetRawValue<Vec1>(tev.Name);
						// var etor = new Evaluator();
						for (var vi = Mathf.Ceil(v0.Value); vi < v1.Value; vi++) {
							var v = new Vec1(vi);
							var t = MotionLerper.Delerp(v, ev.Time, v1, ev.Origin.Time, v0, tev.transition, tev.rate);
							CompiledRuleset.PatchJudge(
								Event, ChartPlayer.cruleset.primary_judges[tev.Name.SubName],
								t, etor, patchedJudgeEvents
							);
						}
						phMotions.Remove(tev);
					}*/
				}
			}
		}
		
		public override void ExUpdate(ContainerState s, StampedEvent ev) {
			base.ExUpdate(s, ev);
			if (ev is StampedEvent.Judge) {
				var tev = (StampedEvent.Judge)ev;
				if (tev.TargetJudge != null && s.CloneType == 0 && !invalidated) {
					//judge.Issue(tev, this);
				}
				else if (tev.TargetJudge == null && s.CloneType == 17) {
					//judge.RecordPos(tev.StartEvent, GetFramePoint(s.Parent, s.Track));
				}
				else
					return;
				patchedJudgeEvents.Remove(tev);
				// Logger.Log("main", 0, "Judge", "ir {0}", patchedJudgeEvents.Count);
			}
		}

		public override void MotionUpdate(byte ct, Chart.Motion ev) {
			base.MotionUpdate(ct, ev);
			if (ct == 0) {
				/*if (ev.Name == "judge") {
					if (invalidated) return;
					if (ev.Name == null)
						throw new InvalidOperationException();
					judge.IssueImmediate(this, ev.Name.SubName, GetFramePoint(cs.Parent, cs.Track));
				}*/
			}
			else if (ct == 16) {
				/*var etor = new EvalImpl();
				if (ev.MotionName == "judge") {
					if (ev.MotionSubName == null)
						throw new InvalidOperationException();
					var l = new List<StampedEvent>();
					float t = ps.Time;
					CompiledRuleset.PatchJudge(
						Event, ChartPlayer.cruleset.primary_judges[ev.MotionSubName],
						t, etor, ref l
					);
					cs.Bus.IssueEventPatch(l);
				}*/
			}
		}

		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
			}
			else if (s.CloneType == 16) {
				/*if (Event.endjudge != null) {
					//var etor = new Evaluator();
					//CompiledRuleset.PatchJudge(Event, ChartPlayer.cruleset.primary_judges[Event.endjudge], ps.Time, etor, patchedJudgeEvents, true);
				}
				cs.Bus.IssuePatch(patchedJudgeEvents.Cast<StampedEvent>());*/
			}
			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;
				}
			}
		}

		public List<StampedEvent.Judge> Invalidate() {
			invalidated = true;
			return patchedJudgeEvents;
		}
	}
}
