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

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;
			InitScores();
		}
		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 if (num4 < 0) num = num3 + 1;
				else if (num != num3) num2 = num3;
				else return num;
			}
			return ~num;
		}
		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.prop != 0 && actlist.Count > 0) {
							index = BinarySearchFirst(actlist, ev.StartClip, def.stack - 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 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);
				InvalidateScore(key.name.Key);
				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);
						InvalidateScore(s.Key.Key);
					}
				}
			}
		}
		readonly Dictionary<int, int> scoreStringKeys = new Dictionary<int, int>();
		readonly Dictionary<int, int> scoreStringKeysRev = 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>();
		readonly Dictionary<int, float> scores = new Dictionary<int, float>();
		readonly Dictionary<int, string> scoreStringCache = new Dictionary<int, string>();
		readonly Dictionary<int, PropSrc> scoreStringSrcs = new Dictionary<int, PropSrc>();
		readonly ArrayPool<byte> scoreStringPool = new ArrayPool<byte>();
		void InitScores() {
			foreach (var s in _rs.scores) {
				var key = s.Key.Key;
				var strkey = IdentifierManager.SharedInstance.Request("_score_" + (string)s.Key.Name);
				scoreStringKeys.Add(key, strkey);
				scoreStringKeysRev.Add(strkey, key);
				scoreSrcs.Add(key, new PropSrc.Float(() => scores[key]));
				scoreOps.Add(key, new PropOp.Float(v => scores[key] = v));
				scoreDefs.Add(key, s.Value);
				scores.Add(key, s.Value.init);
				scoreStringCache.Add(scoreStringKeys[key], null);
				scoreStringSrcs.Add(scoreStringKeys[key], new ScoreStringSrc(scoreStringPool, () => GetScoreString(strkey)));
			}
		}
		void InvalidateScore(int key) {
			scoreSrcs[key].Invalidate();
			scoreStringCache[scoreStringKeys[key]] = null;
			scoreStringSrcs[scoreStringKeys[key]].Invalidate();
		}
		string GetScoreString(int key) {
			var result = scoreStringCache[key];
			if (result == null) {
				var rkey = scoreStringKeysRev[key];
				return scoreStringCache[key] = scores[rkey].ToString(scoreDefs[rkey].format, CultureInfo.InvariantCulture);
			}
			else return result;
		}
		public bool TryGetScoreSrc(int key, out PropSrc value) {
			return scoreSrcs.TryGetValue(key, out value);
		}
		public bool TryGetScoreStringSrc(int key, out PropSrc value) {
			return scoreStringSrcs.TryGetValue(key, out value);
		}
		public string GetFullFormattedScoreString() {
			bool flag = false;
			string result = "";
			foreach (var s in scores.Keys) {
				result += string.Format(flag ? "\n{0}: {1}" : "{0}: {1}", IdentifierManager.SharedInstance.Retrieve(s), GetScoreString(scoreStringKeys[s]));
				flag = true;
			}
			return result;
		}
		class ScoreStringSrc : PropSrc {
			readonly Func<string> _cb;
			readonly ArrayPool<byte> _pool;
			byte[] _buf;
			public ScoreStringSrc(ArrayPool<byte> pool, Func<string> cb) {
				_pool = pool;
				_cb = cb;
			}
			public override void Invalidate() {
				base.Invalidate();
				if (_buf != null) {
					_pool.Return(_buf);
					_buf = null;
				}
			}
			protected override unsafe void InternalGet(out int type, out byte[] value) {
				var src = _cb();
				int strlen = src.Length;
				type = PdtInternalType.String;
				value = _pool.Rent(sizeof(int) + strlen * sizeof(char));
				fixed (byte* _ptr = value) {
					char* ptr = (char*)(_ptr + sizeof(int));
					*(int*)_ptr = strlen;
					int i = 0;
					foreach (var c in src) ptr[i++] = c;
				}
			}
		}
	}
	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 = "";
	}
}
