using Cryville.Common;
using Cryville.Common.Pdt;
using Cryville.Crtr.Event;
using System.Collections.Generic;

namespace Cryville.Crtr {
	public class Judge {
		readonly PdtEvaluator _etor;
		readonly PdtRuleset _rs;
		readonly Dictionary<Identifier, float> ct
			= new Dictionary<Identifier, float>();
		readonly Dictionary<Identifier, List<JudgeEvent>> evs
			= new Dictionary<Identifier, List<JudgeEvent>>();
		readonly Dictionary<Identifier, List<JudgeEvent>> activeEvs
			= new Dictionary<Identifier, List<JudgeEvent>>();
		struct JudgeEvent {
			public float StartTime { get; set; }
			public float EndTime { get; set; }
			public float StartClip { get; set; }
			public float EndClip { get; set; }
			public JudgeDefinition Definition { get; set; }
			public ContainerState State { get; set; }
		}
		static readonly IComparer<JudgeEvent> _stcmp = new JudgeEventStartTimeComparer();
		class JudgeEventStartTimeComparer : IComparer<JudgeEvent> {
			public int Compare(JudgeEvent x, JudgeEvent y) {
				return x.StartClip.CompareTo(y.StartClip);
			}
		}
		public Judge(PdtRuleset rs) {
			_etor = ChartPlayer.etor;
			_rs = rs;
			foreach (var s in rs.scores) {
				var key = s.Key;
				scoreSrcs.Add(key.Key, new PropSrc.Float(() => scores[key.Key]));
				scoreOps.Add(key.Key, new PropOp.Float(v => scores[key.Key] = v));
				scoreFmtKeys.Add(key.Key, IdentifierManager.SharedInstance.Request("_score_" + (string)key.Name));
				scoreDefs.Add(key.Key, s.Value);
				scores.Add(key.Key, s.Value.init);
			}
		}
		public void Prepare(float st, float et, Identifier input, JudgeDefinition def, ContainerState container) {
			List<JudgeEvent> list;
			if (!evs.TryGetValue(input, out list)) {
				ct.Add(input, 0);
				evs.Add(input, list = new List<JudgeEvent>());
				activeEvs.Add(input, new List<JudgeEvent>());
			}
			var ev = new JudgeEvent {
				StartTime = st,
				EndTime = et,
				StartClip = st + def.clip[0],
				EndClip = et + def.clip[1],
				Definition = def,
				State = container,
			};
			var index = list.BinarySearch(ev, _stcmp);
			if (index < 0) index = ~index;
			list.Insert(index, ev);
		}
		static bool _flag;
		static readonly PropOp.Boolean _flagop = new PropOp.Boolean(v => _flag = v);
		static readonly int _var_fn = IdentifierManager.SharedInstance.Request("fn");
		static readonly int _var_tn = IdentifierManager.SharedInstance.Request("tn");
		static readonly int _var_ft = IdentifierManager.SharedInstance.Request("ft");
		static readonly int _var_tt = IdentifierManager.SharedInstance.Request("tt");
		readonly byte[] _numbuf1 = new byte[sizeof(float)];
		readonly byte[] _numbuf2 = new byte[sizeof(float)];
		readonly byte[] _numbuf3 = new byte[sizeof(float)];
		readonly byte[] _numbuf4 = new byte[sizeof(float)];
		unsafe void LoadNum(byte[] buffer, float value) {
			fixed (byte* ptr = buffer) *(float*)ptr = value;
		}
		// Adopted from System.Collections.Generic.ArraySortHelper<T>.InternalBinarySearch(T[] array, int index, int length, T value, IComparer<T> comparer)
		int BinarySearch(List<JudgeEvent> list, float time, int stack) {
			int num = 0;
			int num2 = list.Count - 1;
			while (num <= num2) {
				int num3 = num + (num2 - num >> 1);
				int num4 = -list[num3].Definition.stack.CompareTo(stack);
				if (num4 == 0) num4 = list[num3].StartClip.CompareTo(time);
				if (num4 == 0) return num3;
				else if (num4 < 0) num = num3 + 1;
				else num2 = num3 - 1;
			}
			return ~num;
		}
		int BinarySearchFirst(List<JudgeEvent> list, float time, int stack) {
			if (list[0].Definition.stack == stack && list[0].StartClip == time) return 0;
			int num = 0;
			int num2 = list.Count - 1;
			while (num <= num2) {
				int num3 = num + (num2 - num >> 1);
				int num4 = -list[num3].Definition.stack.CompareTo(stack);
				if (num4 == 0) num4 = list[num3].StartClip.CompareTo(time);
				if (num4 >= 0) num2 = num3 - 1;
				else num = num3 + 1;
			}
			return num + 1;
		}
		public void Feed(Identifier target, float ft, float tt) {
			Forward(target, tt);
			var actlist = activeEvs[target];
			if (actlist.Count > 0) {
				LoadNum(_numbuf3, ft); _etor.ContextCascadeUpdate(_var_ft, new PropSrc.Arbitrary(PdtInternalType.Number, _numbuf3));
				LoadNum(_numbuf4, tt); _etor.ContextCascadeUpdate(_var_tt, new PropSrc.Arbitrary(PdtInternalType.Number, _numbuf4));
				var index = 0;
				while (index >= 0 && index < actlist.Count) {
					var ev = actlist[index];
					LoadNum(_numbuf1, ev.StartTime); _etor.ContextCascadeUpdate(_var_fn, new PropSrc.Arbitrary(PdtInternalType.Number, _numbuf1));
					LoadNum(_numbuf2, ev.EndTime); _etor.ContextCascadeUpdate(_var_tn, new PropSrc.Arbitrary(PdtInternalType.Number, _numbuf2));
					var def = ev.Definition;
					if (def.hit != null) _etor.Evaluate(_flagop, def.hit);
					else _flag = true;
					if (_flag) {
						if (def.scores != null) UpdateScore(def.scores);
						if (def.pass != null) Pass(def.pass);
						actlist.RemoveAt(index);
						if (def.stack != def.prop && actlist.Count > 0) {
							index = BinarySearchFirst(actlist, ev.StartClip, def.prop);
							if (index < 0) index = ~index;
						}
						else index++;
					}
					else index++;
				}
			}
		}
		bool Pass(Identifier[] ids) {
			foreach (var i in ids) {
				var def = _rs.judges[i];
				if (def.hit != null) _etor.Evaluate(_flagop, def.hit);
				else _flag = true;
				if (_flag) {
					if (def.scores != null) UpdateScore(def.scores);
					if (def.pass != null) Pass(def.pass);
					return true;
				}
			}
			return false;
		}
		public void Cleanup(Identifier target, float ft, float tt) {
			Forward(target, tt);
			var actlist = activeEvs[target];
			for (int i = actlist.Count - 1; i >= 0; i--) {
				JudgeEvent ev = actlist[i];
				if (tt > ev.EndClip) {
					actlist.RemoveAt(i);
					if (ev.Definition.miss != null) Pass(ev.Definition.miss);
				}
			}
		}
		void Forward(Identifier target, float tt) {
			var list = evs[target];
			var actlist = activeEvs[target];
			JudgeEvent ev;
			while (list.Count > 0 && (ev = list[0]).StartClip <= tt) {
				list.RemoveAt(0);
				var index = BinarySearch(actlist, ev.StartClip, ev.Definition.stack);
				if (index < 0) index = ~index;
				actlist.Insert(index, ev);
			}
		}
		void UpdateScore(Dictionary<ScoreOperation, PdtExpression> scoreops) {
			foreach (var scoreop in scoreops) {
				var key = scoreop.Key;
				_etor.ContextSelfValue = scoreSrcs[key.name.Key];
				_etor.Evaluate(scoreOps[key.name.Key], scoreop.Value);
				scoreSrcs[key.name.Key].Invalidate();
				foreach (var s in _rs.scores) {
					if (s.Value.value != null) {
						_etor.ContextSelfValue = scoreSrcs[s.Key.Key];
						_etor.Evaluate(scoreOps[s.Key.Key], s.Value.value);
						scoreSrcs[s.Key.Key].Invalidate();
					}
				}
			}
			ScoreCache.Clear();
		}
		readonly Dictionary<int, int> scoreFmtKeys = new Dictionary<int, int>();
		readonly Dictionary<int, PropSrc> scoreSrcs = new Dictionary<int, PropSrc>();
		readonly Dictionary<int, PropOp> scoreOps = new Dictionary<int, PropOp>();
		readonly Dictionary<int, ScoreDefinition> scoreDefs = new Dictionary<int, ScoreDefinition>();
		public readonly Dictionary<int, float> scores = new Dictionary<int, float>();
		readonly Dictionary<int, string> ScoreCache = new Dictionary<int, string>();
		readonly object _lock = new object();
		public Dictionary<int, string> GetFormattedScoreStrings() {
			lock (_lock) {
				if (ScoreCache.Count == 0) {
					foreach (var s in scores)
						ScoreCache.Add(scoreFmtKeys[s.Key], s.Value.ToString(scoreDefs[s.Key].format));
				}
				return ScoreCache;
			}
		}
		public string GetFullFormattedScoreString() {
			bool flag = false;
			string result = "";
			foreach (var s in GetFormattedScoreStrings()) {
				result += string.Format(flag ? "\n{0}: {1}" : "{0}: {1}", IdentifierManager.SharedInstance.Retrieve(s.Key), s.Value);
				flag = true;
			}
			return result;
		}
	}
	public class InputDefinition {
		public int dim;
		public bool notnull;
		public Dictionary<Identifier, PdtExpression> pass;
	}
	public class JudgeDefinition {
		public float[] clip;
		public PdtExpression input;
		public PdtExpression hit;
		public Identifier[] pass;
		public Identifier[] miss;
		public Dictionary<ScoreOperation, PdtExpression> scores;
		public int stack;
		public int prop = -1;
	}
	public class ScoreOperation {
		public Identifier name;
		public Identifier op;
		public override string ToString() {
			if (op == default(Identifier)) return name.ToString();
			else return string.Format("{0} {1}", name, op);
		}
	}
	public class ScoreDefinition {
		public PdtExpression value;
		public float init = 0;
		public string format = "";
	}
}
