using Cryville.Common;
using Cryville.Common.Buffers;
using Cryville.Common.Collections.Specialized;
using Cryville.Common.Pdt;
using Cryville.Crtr.Ruleset;
using Cryville.Crtr.Skin;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace Cryville.Crtr.Event {
	public class ContainerState {
		#region Struct
		public EventBus Bus;
		public EventContainer Container;
		public StampedEvent StampedContainer;
		public ContainerState Parent = null;
		public ushort Depth;

		public Dictionary<EventContainer, ContainerState> Children
			= new Dictionary<EventContainer, ContainerState>();
		HashSet<EventContainer> ActiveChildren
			= new HashSet<EventContainer>();
		public Dictionary<Type, List<ContainerState>> TypedChildren
			= new Dictionary<Type, List<ContainerState>>();

		public ContainerState GetChild(int index, Type handlerType) {
			var list = TypedChildren[handlerType];
			if (index < 0 || index >= list.Count)
				throw new ArgumentOutOfRangeException("index", string.Format("{0}#{1} does not exist.", list[0].Handler.TypeName, index)); 
			return list[index];
		}

		private bool m_active;
		public bool Active {
			get { return m_active; }
			private set {
				if (m_active == value) return;
				m_active = value;
				if (!m_active) {
					if (CloneType == 1) Dispose();
					else if (CloneType >= 16) ReleasePools();
				}
				if (Parent != null) {
					if (m_active) Parent.ActiveChildren.Add(Container);
					else Parent.ActiveChildren.Remove(Container);
				}
				Bus.NotifyActiveChanged(this);
			}
		}
		private bool m_lActive;
		public bool LogicalActive {
			get { return m_lActive; }
			set {
				if (m_lActive == value) return;
				m_lActive = value;
				UpdateActive();
				if (m_lActive) Handler.StartLogicalUpdate(this);
				else Handler.EndLogicalUpdate(this);
			}
		}
		private bool m_pActive;
		public bool PhysicalActive {
			get { return m_pActive; }
			set {
				if (m_pActive == value) return;
				m_pActive = value;
				UpdateActive();
				if (m_pActive) Handler.StartPhysicalUpdate(this);
				else Handler.EndPhysicalUpdate(this);
			}
		}
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		void UpdateActive() { Active = m_lActive || m_pActive; }

		public byte CloneType;
		public ContainerState rootPrototype = null;

		public ContainerHandler Handler {
			get;
			private set;
		}

		public double Time {
			get {
				return Bus.Time;
			}
		}

		public ContainerState(EventContainer _ev, ContainerState parent = null) {
			Container = _ev;

			if (parent != null) {
				AddChild(_ev, parent);
				Parent = parent;
			}

			_rmvpa = new CategorizedPoolAccessor<int, RealtimeMotionValue>(RMVPool.Shared);
			_mcpa = new CategorizedPoolAccessor<int, MotionCache>(MotionCachePool.Shared);

			Values = new IntKeyedDictionary<RealtimeMotionValue>(ChartPlayer.motionRegistry.Count);
			CachedValues = new IntKeyedDictionary<MotionCache>(ChartPlayer.motionRegistry.Count);
			foreach (var m in ChartPlayer.motionRegistry)
				Values.Add(m.Key.Key, new RealtimeMotionValue().Init(Parent == null ? m.Value.GlobalInitValue : m.Value.InitValue));

			rootPrototype = this;
		}

		void AddChild(EventContainer c, ContainerState parent) {
			parent.Children.Add(c, this);
			Type t = c.GetType();
			List<ContainerState> tc;
			if (!parent.TypedChildren.TryGetValue(t, out tc))
				parent.TypedChildren.Add(t, tc = new List<ContainerState>());
			tc.Add(this);
		}

		public ContainerState Clone(byte ct) {
			var r = (ContainerState)MemberwiseClone();

			var mvs = new IntKeyedDictionary<RealtimeMotionValue>(ChartPlayer.motionRegistry.Count);
			foreach (var mv in Values) {
				mvs.Add(mv.Key, mv.Value.Clone());
			}
			r.Values = mvs;

			var cvs = new IntKeyedDictionary<MotionCache>(ChartPlayer.motionRegistry.Count);
			r.CachedValues = cvs;

			r.Children = new Dictionary<EventContainer, ContainerState>();
			r.TypedChildren = new Dictionary<Type, List<ContainerState>>();
			foreach (var child in Children) {
				var cc = child.Value.Clone(ct);
				cc.Parent = r;
				cc.AddChild(child.Key, r);
			}

			r.ActiveChildren = new HashSet<EventContainer>();

			var pms = new Dictionary<StampedEvent, RealtimeMotionValue>(Math.Max(4, PlayingMotions.Count));
			foreach (var m in PlayingMotions)
				pms.Add(m.Key, m.Value);
			r.PlayingMotions = pms;

			if (ct == 1) Handler.bs = r;
			else if (ct == 2) Handler.ts = r;
			else if (ct == 3) Handler.ns = r;
			else if (ct >= 16) Handler.ps = r;
			else throw new InvalidOperationException("Invalid clone type");
			r.CloneType = ct;
			return r;
		}

		public void CopyTo(ContainerState dest) {
			dest.m_lActive = m_lActive;
			dest.m_pActive = m_pActive;
			dest.m_active = m_active;
			if (dest.m_active) dest.Bus.NotifyActiveChanged(dest);

			foreach (var mv in Values) {
				RealtimeMotionValue dv;
				if (dest.Values.TryGetValue(mv.Key, out dv)) mv.Value.CopyTo(dv, false);
				else dest.Values.Add(mv.Key, mv.Value.Clone());
			}

			foreach (var cv in dest.CachedValues) cv.Value.Valid = false;
			foreach (var cv in CachedValues) {
				MotionCache dv;
				if (!dest.CachedValues.TryGetValue(cv.Key, out dv)) {
					dest.CachedValues.Add(cv.Key, dv = dest._mcpa.Rent(cv.Key));
				}
				cv.Value.CopyTo(dv);
			}

			foreach (var cev in dest.ActiveChildren) {
				if (!ActiveChildren.Contains(cev))
					Children[cev].CopyTo(dest.Children[cev]);
			}
			dest.ActiveChildren.Clear();
			foreach (var cev in ActiveChildren) {
				dest.ActiveChildren.Add(cev);
				Children[cev].CopyTo(dest.Children[cev]);
			}

			foreach (var m in dest.PlayingMotions) {
				if (m.Value.CloneTypeFlag == dest.CloneType) dest._rmvpa.Return(m.Value);
			}
			dest.PlayingMotions.Clear();
			foreach (var m in PlayingMotions) {
				dest.PlayingMotions.Add(m.Key, m.Value);
			}
		}

		public bool Disposed {
			get;
			private set;
		}
		public void Dispose() {
			if (Disposed) return;
			Disposed = true;
			if (CloneType == 1) Handler.Dispose();
			ReleasePools();
			foreach (var s in Children)
				s.Value.Dispose();
		}
		public void DisposeAll() {
			foreach (var s in Children)
				s.Value.DisposeAll();
			Handler.DisposeAll();
		}
		public void ReleasePools() {
			_rmvpa.ReturnAll();
			_mcpa.ReturnAll();
		}

		public void AttachHandler(ContainerHandler h) {
			if (Handler != null)
				throw new InvalidOperationException("Handler attached twice");
			Handler = h;
			h.cs = this;
		}

		public void AttachSystems(PdtSkin skin, Judge judge) {
			Handler.AttachSystems(skin, judge);
		}
		#endregion

		#region Motion
		readonly CategorizedPoolAccessor<int, RealtimeMotionValue> _rmvpa;
		readonly CategorizedPoolAccessor<int, MotionCache> _mcpa;
		Dictionary<StampedEvent, RealtimeMotionValue> PlayingMotions = new Dictionary<StampedEvent, RealtimeMotionValue>(4);
		IntKeyedDictionary<RealtimeMotionValue> Values;
		IntKeyedDictionary<MotionCache> CachedValues;

		void InvalidateMotion(int name) {
			MotionCache cache;
			if (!CachedValues.TryGetValue(name, out cache))
				CachedValues.Add(name, cache = _mcpa.Rent(name));
			cache.Valid = false;
			foreach (var c in ActiveChildren)
				Children[c].InvalidateMotion(name);
		}

		public Vector GetComputedValue(int key) {
			MotionCache tr;
			if (!CachedValues.TryGetValue(key, out tr))
				CachedValues.Add(key, tr = _mcpa.Rent(key));
			Vector r = tr.Value;
			if (tr.Valid) return r;
			Values[key].Compute(ref r);
			if (Parent != null) r.ApplyFrom(Parent.GetComputedValue(key));
			tr.Valid = true;
			return r;
		}

		public T GetComputedValue<T>(int key) where T : Vector {
			return (T)GetComputedValue(key);
		}

		static readonly int n_pt = IdentifierManager.Shared.Request("pt");
		public Vector2 ScreenPoint {
			get {
				var mv = GetComputedValue<Vec2>(n_pt);
				return mv.ToVector2();
			}
		}

		static readonly int n_dir = IdentifierManager.Shared.Request("dir");
		public Vector3 Direction {
			get {
				Vec3 r = GetComputedValue<Vec3>(n_dir);
				return r.ToVector3();
			}
		}

		static readonly int n_normal = IdentifierManager.Shared.Request("normal");
		public Vector3 Normal {
			get {
				Vec3 r = GetComputedValue<Vec3>(n_normal);
				return r.ToVector3();
			}
		}

		public Quaternion QuatDir {
			get {
				return Quaternion.LookRotation(Quaternion.Euler(Direction) * Vector3.forward, Normal);
			}
		}

		static readonly int n_sv = IdentifierManager.Shared.Request("sv");
		static readonly int n_svm = IdentifierManager.Shared.Request("svm");
		public float ScrollVelocity {
			get {
				return GetComputedValue<Vec1>(n_sv).Value * GetComputedValue<Vec1m>(n_svm).Value;
			}
		}

		static readonly int n_dist = IdentifierManager.Shared.Request("dist");
		public float Distance {
			get {
				return GetComputedValue<Vec1>(n_dist).Value;
			}
		}

		static readonly int n_track = IdentifierManager.Shared.Request("track");
		public float Track {
			get {
				return GetComputedValue<Vec1>(n_track).Value;
			}
		}
		#endregion

		#region Update
		bool breakflag = false;

		public void Break() {
			// Handler.EndLogicalUpdate(this);
			breakflag = true;
			// LogicalActive = false;
		}

		public void Discard(StampedEvent ev) {
			Handler.Discard(this, ev);
			if (ev is StampedEvent.RelativeMotion) {
				ReturnRelativeMotionEvent((StampedEvent.RelativeMotion)ev);
			}
			else if (ev.Origin is StampedEvent.RelativeMotion) {
				ReturnEndRelativeMotionEvent((StampedEvent.Temporary)ev);
			}
		}

		public void Handle(StampedEvent ev) {
			if (breakflag) return;
			if (ev != null) {
				if (ev.Unstamped is Chart.Motion) {
					var tev = (Chart.Motion)ev.Unstamped;
					var mv = _rmvpa.Rent(tev.Name.Key);
					mv.CloneTypeFlag = CloneType;
					Values[tev.Name.Key].CopyTo(mv, true);
					PlayingMotions.Add(ev, mv);
					Update(ev);
					if (!ev.Unstamped.IsLong) {
						PlayingMotions.Remove(ev);
						_rmvpa.Return(mv);
					}
				}
				else if (ev is StampedEvent.RelativeMotion) {
					var tev = (StampedEvent.RelativeMotion)ev;
					var mv = _rmvpa.Rent(tev.Name);
					mv.CloneTypeFlag = CloneType;
					Values[tev.Name].CopyTo(mv, true);
					PlayingMotions.Add(ev, mv);
					Update(ev);
					if (ev.Duration == 0) {
						PlayingMotions.Remove(ev);
						_rmvpa.Return(mv);
						ReturnRelativeMotionEvent(tev);
					}
				}
				else if (ev.Unstamped is EventContainer) {
					var cev = (EventContainer)ev.Unstamped;
					var ccs = Children[cev];
					ccs.LogicalActive = true;
					UpdateMotions();
					if (!cev.IsLong) {
						ccs.LogicalActive = false;
					}
				}
				else if (ev.Origin != null) {
					var oev = ev.Origin;
					if (oev is StampedEvent.RelativeMotion) {
						Update(ev);
						var mv = PlayingMotions[oev];
						if (mv.CloneTypeFlag == CloneType) _rmvpa.Return(mv);
						PlayingMotions.Remove(oev);
						ReturnEndRelativeMotionEvent((StampedEvent.Temporary)ev);
						ReturnRelativeMotionEvent((StampedEvent.RelativeMotion)oev);
					}
					else {
						var nev = oev.Unstamped;
						if (nev is Chart.Motion) {
							Update(ev);
							var mv = PlayingMotions[oev];
							if (mv.CloneTypeFlag == CloneType) _rmvpa.Return(mv);
							PlayingMotions.Remove(oev);
						}
						else if (nev is EventContainer) {
							var cev = (EventContainer)nev;
							var ccs = Children[cev];
							UpdateMotions();
							ccs.LogicalActive = false;
						}
					}
				}
				Update(ev.Unstamped == null || ev.Unstamped.Priority >= 0 ? ev : null);
			}
			else Update(null);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		void Update(StampedEvent ev) {
			UpdateMotions();
			Handler.Update(this, ev);
		}

		private void UpdateMotions() {
			foreach (var m in PlayingMotions) {
				if (m.Key is StampedEvent.RelativeMotion) {
					var tev = (StampedEvent.RelativeMotion)m.Key;
					var value = Values[tev.Name];
					InvalidateMotion(tev.Name);
					if (tev.Duration == 0) {
						tev.Node.Value.CopyTo(value.RelativeValue);
					}
					else {
						var scaledTime = (float)((Time - tev.Time) / tev.Duration);
						var transition = GetTransition(scaledTime, tev.Node.Transition);
						tev.Node.Value.LerpWith(m.Value.RelativeValue, transition, ref value.RelativeValue);
					}
				}
				else {
					var tev = (Chart.Motion)m.Key.Unstamped;
					if (tev.Node.Id >= 0 && (CloneType == 2 || CloneType == 3)) continue;
					var value = Values[tev.Name.Key];
					InvalidateMotion(tev.Name.Key);
					if (m.Key.Duration == 0) {
						if (tev.Node.Id >= 0) {
							if (CloneType != 0) continue;
							value.SetRelativeNode(tev.Node);
						}
						else {
							tev.Node.Value.CopyTo(value.AbsoluteValue);
						}
					}
					else {
						var scaledTime = (float)((Time - m.Key.Time - ChartPlayer.actualRenderStep * tev.sumfix) / m.Key.Duration);
						var transition = GetTransition(scaledTime, tev.transition);
						if (tev.Node.Id >= 0) {
							if (CloneType != 0) continue;
							var start = m.Value.GetRelativeNode(tev.Node.Id);
							if (start == null) {
								value.SetRelativeNode(tev.Node);
							}
							else {
								var target = value.GetRelativeNode(tev.Node.Id);
								if (target == null) value.SetRelativeNode(tev.Node);
								else tev.Node.LerpWith(start, transition, ref target);
							}
						}
						else {
							tev.Node.Value.LerpWith(m.Value.AbsoluteValue, transition, ref value.AbsoluteValue);
						}
					}
				}
			}
		}
		static readonly PropStores.Float _ttimest = new PropStores.Float();
		static readonly PropStores.Vector4 _transst = new PropStores.Vector4();
		Vector4 GetTransition(float time, PdtExpression transition) {
			if (time >= 1) return Vector4.one;
			if (transition == null) return new Vector4(time, time, time, time);
			_ttimest.Value = time;
			PdtEvaluator.Instance.ContextSelfValue = _ttimest.Source;
			PdtEvaluator.Instance.Evaluate(_transst.Target, transition);
			PdtEvaluator.Instance.ContextSelfValue = null;
			return _transst.Value;
		}

		public void BroadcastPreInit() {
			Handler.PreInit();
			foreach (var c in Children) {
				c.Value.BroadcastPreInit();
			}
		}
		public void BroadcastPostInit() {
			Handler.PostInit();
			foreach (var c in Children) {
				c.Value.BroadcastPostInit();
			}
		}

		public void EndPreGraphicalUpdate() {
			Handler.SetPreGraphicalActive(false, this);
			foreach (var ls in ActiveChildren) {
				Children[ls].EndPreGraphicalUpdate();
			}
		}

		public void EndGraphicalUpdate() {
			Handler.SetGraphicalActive(false, this);
			foreach (var ls in ActiveChildren) {
				Children[ls].EndGraphicalUpdate();
			}
		}

		static readonly SimpleObjectPool<StampedEvent.RelativeMotion> relmEvPool
			= new SimpleObjectPool<StampedEvent.RelativeMotion>(1024);
		static readonly SimpleObjectPool<StampedEvent.Temporary> erelmEvPool
			= new SimpleObjectPool<StampedEvent.Temporary>(1024);
		public void PreAnchor() {
			PushRelativeMotions(Handler.ns.Bus);
			Handler.PreAnchor();
			foreach (var ls in Children) {
				if (ls.Value.Handler.Alive) ls.Value.PreAnchor();
			}
		}
		public void Anchor() {
			PushRelativeMotions(Handler.ts.Bus);
			Handler.Anchor();
			foreach (var ls in Children) {
				if (ls.Value.Handler.Alive) ls.Value.Anchor();
			}
		}
		void PushRelativeMotions(EventBus bus) {
			foreach (var m in Values) {
				foreach (var node in m.Value.RelativeNodes) {
					var ev = relmEvPool.Rent();
					ev.Time = rootPrototype.Time + node.Value.Time.Value;
					ev.Container = Container;
					ev.Name = m.Key;
					ev.Node = node.Value;
					bus.PushTempEvent(ev);
					if (node.Value.EndTime.Value > node.Value.Time.Value) {
						var eev = erelmEvPool.Rent();
						eev.Time = rootPrototype.Time + node.Value.EndTime.Value;
						eev.Container = Container;
						eev.Origin = ev;
						ev.ReleaseEvent = eev;
						bus.PushTempEvent(eev);
					}
				}
			}
		}
		void ReturnRelativeMotionEvent(StampedEvent.RelativeMotion ev) {
			ev.Container = null;
			ev.Node = null;
			ev.ReleaseEvent = null;
			relmEvPool.Return(ev);
		}
		void ReturnEndRelativeMotionEvent(StampedEvent.Temporary ev) {
			ev.Container = null;
			ev.Origin = null;
			erelmEvPool.Return(ev);
		}
		#endregion
	}
}
