using Cryville.Common.Collections.Specialized;
using Cryville.Common.Pdt;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;

namespace Cryville.Crtr.Skin {
	public class SkinContainer {
		readonly ISkinnableGroup _group;
		readonly SkinElement _rootElement;
		readonly DynamicStack[] _stacks = new DynamicStack[3];
		readonly HashSet<SkinPropertyKey> _once = new HashSet<SkinPropertyKey>();
		public readonly IntKeyedDictionary<PropStores.Float> Variables = new IntKeyedDictionary<PropStores.Float>();

		class DynamicStack {
			public readonly List<DynamicProperty> Properties = new List<DynamicProperty>();
			public readonly List<DynamicElement> Elements = new List<DynamicElement>();
			public void Clear() {
				Properties.Clear();
				Elements.Clear();
			}
		}
		struct DynamicProperty {
			public RuntimeSkinContext Context { get; set; }
			public SkinPropertyKey Key { get; set; }
			public PdtExpression Value { get; set; }
		}
		struct DynamicElement {
			public RuntimeSkinContext Context { get; set; }
			public SkinSelectors Selectors { get; set; }
			public SkinElement Element { get; set; }
		}
		public SkinContainer(ISkinnableGroup group, SkinElement rootElement) {
			_group = group;
			_rootElement = rootElement;
			for (int i = 0; i < _stacks.Length; i++) _stacks[i] = new DynamicStack();
		}
		public void MatchStatic() {
			var stack = _stacks[0];
			stack.Clear();
			MatchStatic(_rootElement, stack, new RuntimeSkinContext(_group.SkinContext));
		}
		void MatchStatic(SkinElement rel, DynamicStack stack, RuntimeSkinContext ctx) {
			var rc = ctx.ReadContext;
			PdtEvaluator.Instance.ContextTransform = rc.Transform;
			if (rc.PropSrcs != null) PdtEvaluator.Instance.ContextCascadeInsert(rc.PropSrcs);
			foreach (var p in rel.properties) {
				try {
					p.Key.ExecuteStatic(_group, ctx, p.Value, Variables); 
				}
				catch (EvaluationFailureException) { }
				if (p.Key.IsValueRequired && !p.Value.IsConstant) stack.Properties.Add(
					new DynamicProperty { Context = ctx, Key = p.Key, Value = p.Value }
				);
			}
			PdtEvaluator.Instance.ContextTransform = null;
			foreach (var e in rel.elements) {
				try {
					var nctxs = e.Key.MatchStatic(_group, rc);
					if (nctxs != null) {
						var roflag = e.Key.annotations.Contains("if");
						var woflag = e.Key.annotations.Contains("then");
						foreach (var nctx in nctxs) {
							var nrctx = new RuntimeSkinContext(nctx, ctx, roflag, woflag);
							MatchStatic(e.Value, stack, nrctx);
						}
					}
				}
				catch (SelectorNotAvailableException) {
					stack.Elements.Add(
						new DynamicElement { Context = ctx, Selectors = e.Key, Element = e.Value }
					);
				}
			}
			if (rc.PropSrcs != null) PdtEvaluator.Instance.ContextCascadeDiscard();
		}
		public void MatchDynamic(int dl, bool recursive = false) {
			var stack = _stacks[dl];
			if (stack.Properties.Count == 0 && stack.Elements.Count == 0) return;
			var nstack = dl + 1 < _stacks.Length ? _stacks[dl + 1] : null;
			if (nstack != null) nstack.Clear();
			Profiler.BeginSample("SkinContainer.MatchDynamic");
			if (!recursive) PdtEvaluator.Instance.ContextSkinContainer = this;
			for (int i = 0; i < stack.Properties.Count; i++) {
				DynamicProperty p = stack.Properties[i];
				p.Key.ExecuteDynamic(_group, p.Context, p.Value, Variables, dl);
				if (p.Key.annotations.Contains("once")) {
					stack.Properties.RemoveAt(i--);
				}
				if (p.Key.IsValueRequired && !p.Value.IsConstant && dl < 1) nstack.Properties.Add(p);
			}
			for (int i = 0; i < stack.Elements.Count; i++) {
				DynamicElement e = stack.Elements[i];
				var psrcs = e.Context.ReadContext.PropSrcs;
				if (psrcs != null) PdtEvaluator.Instance.ContextCascadeInsert(psrcs);
				if (e.Selectors.IsUpdatable(_group, dl)) {
					try {
						var nctx = e.Selectors.MatchDynamic(_group, e.Context.ReadContext);
						if (nctx != null) {
							MatchDynamic(e.Element, dl, nstack, new RuntimeSkinContext(
								nctx, e.Context, e.Selectors.annotations.Contains("if"), e.Selectors.annotations.Contains("then")
							));
							if (e.Selectors.annotations.Contains("once")) {
								stack.Elements.RemoveAt(i--);
							}
						}
					}
					catch (SelectorNotAvailableException) {
						if (nstack == null) throw;
						nstack.Elements.Add(e);
					}
				}
				else {
					if (nstack == null) throw new SelectorNotAvailableException();
					nstack.Elements.Add(e);
				}
				if (psrcs != null) PdtEvaluator.Instance.ContextCascadeDiscard();
			}
			if (!recursive) PdtEvaluator.Instance.ContextSkinContainer = null;
			Profiler.EndSample();
		}
		void MatchDynamic(SkinElement rel, int dl, DynamicStack stack, RuntimeSkinContext ctx) {
			var rc = ctx.ReadContext;
			PdtEvaluator.Instance.ContextTransform = rc.Transform;
			if (rc.PropSrcs != null) PdtEvaluator.Instance.ContextCascadeInsert(rc.PropSrcs);
			foreach (var p in rel.properties) {
				if (p.Key.annotations.Contains("once")) {
					if (_once.Contains(p.Key)) continue;
					p.Key.ExecuteDynamic(_group, ctx, p.Value, Variables, dl);
					_once.Add(p.Key);
				}
				else {
					p.Key.ExecuteDynamic(_group, ctx, p.Value, Variables, dl);
				}
			}
			PdtEvaluator.Instance.ContextTransform = null;
			foreach (var e in rel.elements) {
				if (e.Key.IsUpdatable(_group, dl)) {
					SkinContext nctx = e.Key.MatchDynamic(_group, rc);
					if (nctx != null) MatchDynamic(e.Value, dl, stack, new RuntimeSkinContext(
						nctx, ctx, e.Key.annotations.Contains("if"), e.Key.annotations.Contains("then")
					));
				}
				else {
					if (stack == null) throw new SelectorNotAvailableException();
					stack.Elements.Add(
						new DynamicElement { Context = ctx, Selectors = e.Key, Element = e.Value }
					);
				}
			}
			if (rc.PropSrcs != null) PdtEvaluator.Instance.ContextCascadeDiscard();
		}
		readonly PropStores.Float _rtimest = new PropStores.Float(); 
		public void MatchAnimation(AnimationSpan span, float rtime, RuntimeSkinContext ctx) {
			PdtEvaluator.Instance.ContextSkinContainer = this;
			PdtEvaluator.Instance.ContextSelfValue = _rtimest.Source;
			MatchAnimationInternal(span, rtime, ctx);
			PdtEvaluator.Instance.ContextSelfValue = null;
			PdtEvaluator.Instance.ContextSkinContainer = null;
		}
		void MatchAnimationInternal(AnimationSpan span, float rtime, RuntimeSkinContext ctx) {
			_rtimest.Value = rtime;
			foreach (var p in span.properties) {
				p.Key.ExecuteDynamic(_group, ctx, p.Value, Variables, 0);
			}
			foreach (var s in span.spans) {
				if (rtime < s.Key.Behind || rtime >= s.Key.Ahead) continue;
				MatchAnimationInternal(s.Value, (rtime - s.Key.Behind) / (s.Key.Ahead - s.Key.Behind), ctx);
			}
		}
	}
	public class SkinContext {
		public Transform Transform { get; private set; }
		public IntKeyedDictionary<PropSrc> PropSrcs { get; private set; }
		public SkinContext(Transform transform, IntKeyedDictionary<PropSrc> propSrcs = null) {
			Transform = transform;
			PropSrcs = propSrcs;
		}
	}
	public struct RuntimeSkinContext {
		public SkinContext ReadContext { get; private set; }
		public Transform WriteTransform { get; private set; }
		public RuntimeSkinContext(SkinContext rw) {
			ReadContext = rw;
			WriteTransform = rw.Transform;
		}
		public RuntimeSkinContext(SkinContext r, Transform w) {
			ReadContext = r;
			WriteTransform = w;
		}
		public RuntimeSkinContext(SkinContext newctx, RuntimeSkinContext oldctx, bool roflag, bool woflag) {
			if (roflag) {
				ReadContext = newctx;
				WriteTransform = oldctx.WriteTransform;
			}
			else if (woflag) {
				ReadContext = oldctx.ReadContext;
				WriteTransform = newctx.Transform;
			}
			else {
				ReadContext = newctx;
				WriteTransform = newctx.Transform;
			}
		}
	}
	public interface ISkinnableGroup {
		string TypeName { get; }
		SkinContext SkinContext { get; }
		int AtAnchorDynamicLevel { get; }
		int OpenedAnchorName { get; }
		bool TryGetAnchorsByName(int name, out IReadOnlyCollection<Anchor> result);
		void RegisterAnchor(int name);
		void PushAnchorEvent(double time, int name);
	}
}
