using Cryville.Common;
using Cryville.Common.Collections.Generic;
using Cryville.Common.Pdt;
using Cryville.Crtr.Extension;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Cryville.Crtr.Skin {
	public class SkinDefinition : MetaInfo {
		public const long CURRENT_FORMAT = 2;

		[JsonRequired]
		public long format;

		public string @base;

		[JsonRequired]
		public string ruleset;

		public List<string> frames = new List<string>();

		[JsonIgnore]
		public PdtSkin Root { get; private set; }

		public void LoadPdt(DirectoryInfo dir) {
			using (StreamReader pdtreader = new StreamReader(dir.FullName + "/" + data + ".pdt", Encoding.UTF8)) {
				var src = pdtreader.ReadToEnd();
				var interpreter = new SkinInterpreter(src, null);
				var format = interpreter.GetFormatVersion();
				if (format.Length == 1) {
					Root = new PdtSkin();
					Root.elements = (SkinElement)new SkinInterpreter(src, new PdtBinder()).Interpret(typeof(SkinElement));
				}
				else {
					switch (format[1]) {
						case 1:
							Root = (PdtSkin)new SkinInterpreter(src, new PdtBinder()).Interpret(typeof(PdtSkin));
							break;
						default: throw new NotSupportedException("Unsupported skin format");
					}
				}
			}
		}
	}

	public class PdtSkin {
		public Dictionary<Identifier, AnimationSpan> animations
			= new Dictionary<Identifier, AnimationSpan>();
		public Dictionary<Identifier, EffectDefinition> effects
			= new Dictionary<Identifier, EffectDefinition>();
		public SkinElement elements;

		public void Optimize(PdtEvaluator etor) {
			foreach (var a in animations) {
				a.Value.Optimize(etor);
			}
			foreach (var e in effects) {
				var effect = e.Value;
				etor.ContextCascadeInsert();
				etor.ContextCascadeUpdate(EffectInstance._VAR_EFFECT_INDEX, PropSrc.Error);
				foreach(var s in effect.states) {
					etor.Optimize(s.Value.duration);
				}
				effect.elements.Optimize(etor);
				etor.ContextCascadeDiscard();
			}
			elements.Optimize(etor);
		}
	}

	public class SkinElement {
		[ElementList]
		public PairList<SkinSelectors, SkinElement> elements
			= new PairList<SkinSelectors, SkinElement>();

		[PropertyList]
		public PairList<SkinPropertyKey, PdtExpression> properties
			= new PairList<SkinPropertyKey, PdtExpression>();

		public bool IsDynamic {
			get;
			private set;
		}

		public void Optimize(PdtEvaluatorBase etor) {
			IsDynamic = true;
			foreach (var e in properties) {
				etor.Optimize(e.Value);
				if (!e.Value.IsConstant)
					IsDynamic = true;
			}
			foreach (var e in elements) {
				e.Key.Optimize(etor);
				e.Value.Optimize(etor);
				if (e.Value.IsDynamic)
					IsDynamic = true;
			}
		}
	}

	public class EffectDefinition {
		static readonly Identifier _ident_init = new Identifier("<init>");
#pragma warning disable IDE1006
		public PdtExpression duration {
			set {
				EffectState s;
				if (!states.TryGetValue(_ident_init, out s))
					throw new InvalidOperationException("Cannot set duration and states at the same time");
				s.duration = value;
			}
		}
#pragma warning restore IDE1006
		public Identifier init = _ident_init;
		public Dictionary<Identifier, EffectState> states = new Dictionary<Identifier, EffectState> {
			{ _ident_init, new EffectState() { rewind = _ident_init } }
		};
		public SkinElement elements;
	}

	public class EffectState {
		public PdtExpression duration;
		public Identifier rewind;
		public Identifier next;
	}

	public class AnimationSpan {
		[ElementList]
		public PairList<Clip, AnimationSpan> spans
			= new PairList<Clip, AnimationSpan>();

		[PropertyList]
		public PairList<SkinPropertyKey, PdtExpression> properties
			= new PairList<SkinPropertyKey, PdtExpression>();

		public void Optimize(PdtEvaluator etor) {
			foreach (var p in properties) {
				etor.Optimize(p.Value);
			}
			foreach (var e in spans) {
				e.Value.Optimize(etor);
			}
		}
	}
}
