using Cryville.Common;
using Cryville.Crtr.Ruleset;
using Cryville.Crtr.Skin.Components;
using System;
using System.Collections.Generic;
using UnityEngine;

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

		SectionalGameObject[] sgos;
		readonly Dictionary<Chart.Judge, JudgeState> judges = new Dictionary<Chart.Judge, JudgeState>();
		class JudgeState {
			static readonly int _var_judge_result = IdentifierManager.Shared.Request("judge_result");
			static readonly int _var_judge_time_absolute = IdentifierManager.Shared.Request("judge_time_absolute");
			static readonly int _var_judge_time_relative = IdentifierManager.Shared.Request("judge_time_relative");
			public Anchor StaticAnchor { get; private set; }
			readonly PropStores.Float _jtabsst = new PropStores.Float();
			readonly PropStores.Float _jtrelst = new PropStores.Float();
			readonly PropStores.Identifier _resultst = new PropStores.Identifier();
			public JudgeState(NoteHandler handler, int name) {
				StaticAnchor = handler.RegisterAnchor(handler.judge.judgeMap[name], false, 3);
			}
			public void MarkJudged(float abs, float rel, int result) {
				_jtabsst.Value = abs;
				_jtrelst.Value = rel;
				_resultst.Value = result;
			}
			public void InitPropSrcs() {
				StaticAnchor.PropSrcs.Add(_var_judge_result, _resultst.Source);
				StaticAnchor.PropSrcs.Add(_var_judge_time_absolute, _jtabsst.Source);
				StaticAnchor.PropSrcs.Add(_var_judge_time_relative, _jtrelst.Source);
			}
		}

		public override void PreInit() {
			base.PreInit();
			foreach (var j in _note.judges) {
				judges.Add(j, new JudgeState(this, j.Id.Key));
			}
		}
		public override void Init() {
			base.Init();
			sgos = RootTransform.GetComponentsInChildren<SectionalGameObject>();
			foreach (var judge in judges) judge.Value.InitPropSrcs();
		}

		public override void StartPhysicalUpdate(ContainerState s) {
			base.StartPhysicalUpdate(s);
			if (s.CloneType == 2) {
				TransformAwake(s);
			}
		}
		protected override void StartGraphicalUpdate(ContainerState s) {
			base.StartGraphicalUpdate(s);
			TransformAwake(s);
			if (RootTransform) {
				if (_note.IsLong) {
					foreach (var i in sgos) {
						i.Reset();
						i.AppendPoint(Position, Rotation);
					}
				}
				else {
#if UNITY_5_6_OR_NEWER
					RootTransform.SetPositionAndRotation(Position, Rotation);
#else
					RootTransform.position = Position;
					RootTransform.rotation = Rotation;
#endif
				}
			}
		}
		void TransformAwake(ContainerState s) {
			Position = GetFramePoint(s.Parent, s.Track);
			Rotation = GetFrameRotation(s.Parent, s.Track);
		}

		public override void Update(ContainerState s, StampedEvent ev) {
			if (s.CloneType <= 2) {
				Position = GetFramePoint(s.Parent, s.Track);
				Rotation = GetFrameRotation(s.Parent, s.Track);
				if (s.CloneType == 2 && RootTransform && _note.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) {
					judge._etor.ContextEvent = ev.Unstamped;
					judge._etor.ContextState = s;
					judge.Prepare(ev, this);
					judge._etor.ContextState = null;
					judge._etor.ContextEvent = null;
				}
			}
			if (s.CloneType == 2 && ev != null && ev.Unstamped is Chart.Judge) {
				var anchor = judges[(Chart.Judge)ev.Unstamped].StaticAnchor;
#if UNITY_5_6_OR_NEWER
				anchor.Transform.SetPositionAndRotation(Position, Rotation);
#else
				anchor.Transform.position = Position;
				anchor.Transform.rotation = Rotation;
#endif
				OpenAnchor(anchor);
				base.Update(s, ev);
				CloseAnchor();
			}
			else base.Update(s, ev);
		}

		protected override void EndGraphicalUpdate(ContainerState s) {
			if (RootTransform) {
				foreach (var i in sgos) i.Seal();
			}
			base.EndGraphicalUpdate(s);
		}
		
		Vector3 GetFramePoint(ContainerState state, float track) {
			return GetFrame(state, track, th => th.Handler.Position);
		}

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

		Vector3 GetFrame(ContainerState state, float track, Func<ContainerState, Vector3> func) {
			// TODO
			int id = Mathf.FloorToInt(track);
			if (track == id) {
				var ts0 = state.GetChild(id, typeof(Chart.Track));
				var p1 = func(ts0);
				return p1;
			}
			return gh.GetCurrentFrame(func, track);
		}

		internal void ReportJudge(JudgeEvent ev, float time, Identifier result) {
			JudgeState state;
			if (!judges.TryGetValue(ev.BaseEvent, out state)) return;
			state.MarkJudged(time, (float)(ev.StartTime - time), result.Key);
		}
	}
}
