using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Cryville.Common.Pdt {
	/// <summary>
	/// Base of evaluator for PDT expressions.
	/// </summary>
	public abstract class PdtEvaluatorBase {
		private struct StackFrame {
			public int Offset;
			public int Length;
			public int Type;
		}
		int _framecount = 0;
		int _goffset = 0;
		readonly StackFrame[] _stack = new StackFrame[256];
		readonly byte[] _mem = new byte[0x100000];
		bool _revokepttconst;
		/// <summary>
		/// Evaluates an expression and passes the result to a target operator.
		/// </summary>
		/// <param name="target">The target operator.</param>
		/// <param name="exp">The expression to evaluate.</param>
		/// <returns>Whether the evaluaton succeeded.</returns>
		public bool Evaluate(PdtOperator target, PdtExpression exp) {
			var prevFrameCount = _framecount;
			try {
				_revokepttconst = false;
				for (var ip = exp.Instructions.First; ip != null; ip = ip.Next)
					ip.Value.Execute(this, ref ip);
				if (exp.IsPotentialConstant) {
					exp.IsConstant = exp.IsPotentialConstant = !_revokepttconst;
				}
				var ret = Operate(target, _framecount - prevFrameCount, true);
				return ret;
			}
			catch (Exception ex) {
				throw new EvaluationFailureException(exp, ex);
			}
			finally {
				for (var i = prevFrameCount; i < _framecount; i++) DiscardStack();
			}
		}
		/// <summary>
		/// Optimizes an expression by merging its instructions.
		/// </summary>
		/// <param name="exp">The expression to optimize.</param>
		public void Optimize(PdtExpression exp) {
			_framecount = 0;
			_goffset = 0;
			List<PdtInstruction.Collapse> ct;
			var cols = new Dictionary<LinkedListNode<PdtInstruction>, List<PdtInstruction.Collapse>>();
			var il = exp.Instructions;
			var ip = il.First;
			while (ip != null) {
				bool nextFlag = false;
				var i = ip.Value;
				if (i is PdtInstruction.Operate) {
					int fc0 = _framecount;
					int fc1 = ((PdtInstruction.Operate)i).Signature.ParamCount;
					try { i.Execute(this, ref ip); } catch (Exception) { }
					if (fc0 - _framecount == fc1) {
						unsafe {
							fixed (StackFrame* frame = &_stack[_framecount++]) {
								frame->Type = PdtInternalType.Error;
								frame->Offset = -1;
								frame->Length = 0;
							}
						}
					}
					else {
						var frame = _stack[_framecount - 1];
						if (frame.Type != PdtInternalType.Error) {
							ReplaceIP(il, ref ip, new PdtInstruction.PushConstant(frame.Type, _mem, frame.Offset, frame.Length), cols);
							for (var j = 0; j < fc1; j++) il.Remove(ip.Previous);
						}
					}
				}
				else if (i is PdtInstruction.Collapse) {
					var t = (PdtInstruction.Collapse)i;
					try {
						var pins = ip;
						i.Execute(this, ref ip);
						if (_stack[_framecount - 1].Type == PdtInternalType.Error) {
							throw new EvaluationFailureException();
						}
						if (ip == pins) {
							ip = ip.Next;
							il.Remove(ip.Previous);
							il.Remove(ip.Previous);
							ip = ip.Previous;
							if (ip == null) {
								ip = il.First;
								nextFlag = true;
							}
						}
						else {
							ip = pins.Previous;
							while (ip.Next != t.Target) il.Remove(ip.Next);
							il.Remove(ip.Next);
							if (cols.TryGetValue(t.Target, out ct)) {
								foreach (var u in ct) u.Target = ip;
								cols.Remove(t.Target);
								cols.Add(ip, ct);
							}
						}
					}
					catch (Exception) {
						if (cols.TryGetValue(t.Target, out ct)) ct.Add(t);
						else cols.Add(t.Target, new List<PdtInstruction.Collapse> { t });
					}
				}
				else if (i is PdtInstruction.PushVariable) {
					i.Execute(this, ref ip);
					var frame = _stack[_framecount - 1];
					if (frame.Type != PdtInternalType.Undefined && frame.Type != PdtInternalType.Error) {
						ReplaceIP(il, ref ip, new PdtInstruction.PushConstant(frame.Type, _mem, frame.Offset, frame.Length), cols);
					}
				}
				else i.Execute(this, ref ip);
				if (ip != null && cols.TryGetValue(ip, out ct)) {
					unsafe {
						fixed (StackFrame* frame = &_stack[_framecount - 1]) {
							frame->Type = PdtInternalType.Error;
							frame->Offset = -1;
							frame->Length = 0;
						}
					}
				}
				if (!nextFlag) ip = ip.Next;
			}
			exp.IsConstant = true;
			exp.IsPotentialConstant = true;
			for (var ins = il.First; ins != null; ins = ins.Next) {
				if (!(ins.Value is PdtInstruction.PushConstant)) {
					exp.IsConstant = false;
					break;
				}
			}
		}
		void ReplaceIP(LinkedList<PdtInstruction> il, ref LinkedListNode<PdtInstruction> ip, PdtInstruction ins, Dictionary<LinkedListNode<PdtInstruction>, List<PdtInstruction.Collapse>> cols) {
			List<PdtInstruction.Collapse> cins;
			if (cols.TryGetValue(ip, out cins)) cols.Remove(ip);
			ip = il.AddAfter(ip, ins);
			il.Remove(ip.Previous);
			if (cins != null) cols.Add(ip, cins);
		}
		/// <summary>
		/// Revokes the potential constant mark of the current expression.
		/// </summary>
		protected void RevokePotentialConstant() {
			_revokepttconst = true;
		}
		internal unsafe void PushConstant(int type, byte[] value) {
			fixed (StackFrame* frame = &_stack[_framecount++]) {
				frame->Type = type;
				frame->Offset = _goffset;
				frame->Length = value.Length;
				Array.Copy(value, 0, _mem, _goffset, value.Length);
				_goffset += value.Length;
			}
		}
		internal unsafe void PushVariable(int name, bool forced) {
			fixed (StackFrame* frame = &_stack[_framecount++]) {
				byte[] value;
				GetVariable(name, forced, out frame->Type, out value);
				frame->Offset = _goffset;
				frame->Length = value.Length;
				Array.Copy(value, 0, _mem, _goffset, value.Length);
				_goffset += value.Length;
			}
		}
		/// <summary>
		/// Gets a variable of the specified name.
		/// </summary>
		/// <param name="name">The name of the variable.</param>
		/// <param name="forced">Whether to produce an error stack instead of an identifier stack if the variable is not found.</param>
		/// <param name="type">The type of the variable.</param>
		/// <param name="value">The value of the variable.</param>
		protected abstract void GetVariable(int name, bool forced, out int type, out byte[] value);
		internal void Operate(PdtOperatorSignature sig) {
			PdtOperator op;
			try { op = GetOperator(sig); }
			catch (Exception ex) {
				for (int i = 0; i < sig.ParamCount; i++)
					DiscardStack();
				throw new EvaluationFailureException(string.Format("Failed to get operator {0}", sig), ex);
			}
			Operate(op, sig.ParamCount);
		}
		/// <summary>
		/// Gets an operator of the specified name and the suggested parameter count.
		/// </summary>
		/// <param name="name">The name of the operator.</param>
		/// <param name="pc">Suggested parameter count.</param>
		/// <returns>An operator of the specific name.</returns>
		/// <remarks>The parameter count of the returned operator does not necessarily equal to <paramref name="pc" />.</remarks>
		protected abstract PdtOperator GetOperator(PdtOperatorSignature sig);
		unsafe bool Operate(PdtOperator op, int pc, bool noset = false) {
			fixed (byte* pmem = _mem) {
				op.Begin(this, pc);
				for (int i = 0; i < pc; i++) {
					var frame = _stack[--_framecount];
					if (frame.Type == PdtInternalType.Error) {
						_framecount -= pc - i - 1;
						_stack[_framecount++] = new StackFrame { Type = PdtInternalType.Error, Offset = -1, Length = 0 };
						return false;
					}
					op.LoadOperand(new PdtVariableMemory(frame.Type, pmem + frame.Offset, frame.Length));
					_goffset -= frame.Length;
				}
				op.Call(pmem + _goffset, noset);
				return true;
			}
		}
		internal unsafe void Collapse(int name, ref LinkedListNode<PdtInstruction> self, LinkedListNode<PdtInstruction> target) {
			fixed (byte* pmem = _mem) {
				var frame = _stack[--_framecount];
				_goffset -= frame.Length;
				if (frame.Type == PdtInternalType.Error) {
					_stack[_framecount++] = new StackFrame { Type = PdtInternalType.Error, Offset = -1, Length = 0 };
					self = target;
					return;
				}
				if (Collapse(name, new PdtVariableMemory(frame.Type, pmem + frame.Offset, frame.Length))) {
					_framecount++;
					_goffset += frame.Length;
					self = target;
				}
			}
		}
		/// <summary>
		/// Gets whether to jump to the target of a collapse instruction.
		/// </summary>
		/// <param name="name">The name of the collapse operator.</param>
		/// <param name="param">The top frame in the stack as the parameter.</param>
		/// <returns>Whether to jump to the target of the collapse instruction.</returns>
		protected abstract bool Collapse(int name, PdtVariableMemory param);
		internal unsafe PdtVariableMemory StackAlloc(int type, byte* ptr, int len) {
			fixed (StackFrame* frame = &_stack[_framecount++]) {
				frame->Type = type;
				frame->Offset = _goffset;
				frame->Length = len;
				_goffset += len;
				return new PdtVariableMemory(type, ptr, len);
			}
		}
		internal void DiscardStack() {
			_goffset -= _stack[--_framecount].Length;
		}
	}

	/// <summary>
	/// The exception that is thrown when the evaluation of a <see cref="PdtExpression" /> fails.
	/// </summary>
	public class EvaluationFailureException : Exception {
		/// <inheritdoc />
		public EvaluationFailureException() : base("Evaluation failed") { }
		/// <inheritdoc />
		public EvaluationFailureException(string message) : base(message) { }
		/// <inheritdoc />
		public EvaluationFailureException(string message, Exception innerException) : base(message, innerException) { }
		/// <inheritdoc />
		protected EvaluationFailureException(SerializationInfo info, StreamingContext context) : base(info, context) { }
		/// <summary>
		/// Creates an instance of the <see cref="EvaluationFailureException" /> class with the failing expression.
		/// </summary>
		/// <param name="exp">The failing expression.</param>
		public EvaluationFailureException(PdtExpression exp) : base("Evaluation failed for the expression: " + exp.ToString()) { }
		/// <summary>
		/// Creates an instance of the <see cref="EvaluationFailureException" /> class with the failing expression and the inner exception.
		/// </summary>
		/// <param name="exp">The failing expression.</param>
		/// <param name="innerException">The inner exception.</param>
		public EvaluationFailureException(PdtExpression exp, Exception innerException) : base("Evaluation failed for the expression: " + exp.ToString(), innerException) { }
	}
}
