using System;

namespace Cryville.Common.Pdt {
	/// <summary>
	/// PDT operator.
	/// </summary>
	public unsafe abstract class PdtOperator {
		byte* _prmem;
		int _loadindex;
		readonly PdtVariableMemory[] _operands;
		/// <summary>
		/// The count of the operands loaded.
		/// </summary>
		protected int LoadedOperandCount { get { return ParamCount - _loadindex; } }
		/// <summary>
		/// Gets the operand at the specified index.
		/// </summary>
		/// <param name="index">The index.</param>
		/// <returns>The operand at the specified index.</returns>
		/// <exception cref="IndexOutOfRangeException"><paramref name="index" /> is not less than <see cref="LoadedOperandCount" /> or less than 0.</exception>
		protected PdtVariableMemory GetOperand(int index) {
			if (index >= LoadedOperandCount || index < 0) throw new IndexOutOfRangeException();
			int i = index + _loadindex;
			return _operands[i];
		}
		internal int ParamCount { get; private set; }
		/// <summary>
		/// Creates an instance of the <see cref="PdtOperator" /> class.
		/// </summary>
		/// <param name="pc">The suggested parameter count.</param>
		protected PdtOperator(int pc) {
			ParamCount = pc;
			_operands = new PdtVariableMemory[pc];
		}
		PdtEvaluatorBase _etor;
		bool _failure = false;
		bool _rfreq = true;
		internal void Begin(PdtEvaluatorBase etor) {
			_etor = etor;
			_failure = false;
			_loadindex = ParamCount;
		}
		internal void LoadOperand(PdtVariableMemory mem) {
			if (_loadindex == 0) return;
			_operands[--_loadindex] = mem;
		}
		internal void Call(byte* prmem, bool noset) {
			_prmem = prmem;
			_rfreq = false;
			try { Execute(); } catch (Exception ex) {
				if (_rfreq) _etor.DiscardStack();
				throw new InvalidOperationException("Evaluation failed", ex);
			}
			if (_failure) {
				if (_rfreq) _etor.DiscardStack();
				throw new InvalidOperationException("Evaluation failed");
			}
			if (!_rfreq && !noset) throw new InvalidOperationException("Return frame not set");
		}
		/// <summary>
		/// Executes the operator.
		/// </summary>
		protected abstract void Execute();
		/// <summary>
		/// Gets a return frame.
		/// </summary>
		/// <param name="type">The type of the frame.</param>
		/// <param name="len">The length of the frame.</param>
		/// <returns>The return frame.</returns>
		/// <exception cref="InvalidOperationException">The return frame has already been requested.</exception>
		protected PdtVariableMemory GetReturnFrame(int type, int len) {
			if (_rfreq) {
				_failure = true;
				throw new InvalidOperationException("Return frame already requested");
			}
			_rfreq = true;
			return _etor.StackAlloc(type, _prmem, len);
		}
	}
	/// <summary>
	/// The signature of a <see cref="PdtOperator" />.
	/// </summary>
	public struct PdtOperatorSignature : IEquatable<PdtOperatorSignature> {
		/// <summary>
		/// The name of the operator.
		/// </summary>
		public int Name { get; private set; }
		/// <summary>
		/// The parameter count.
		/// </summary>
		public int ParamCount { get; private set; }
		readonly int _hash;
		/// <summary>
		/// Creates an operator signature.
		/// </summary>
		/// <param name="name">The name of the operator.</param>
		/// <param name="paramCount">The parameter count.</param>
		public PdtOperatorSignature(string name, int paramCount)
			: this(IdentifierManager.SharedInstance.Request(name), paramCount) { }
		/// <summary>
		/// Creates an operator signature.
		/// </summary>
		/// <param name="name">The identifier of the operator.</param>
		/// <param name="paramCount">The parameter count.</param>
		public PdtOperatorSignature(int name, int paramCount) {
			Name = name;
			ParamCount = paramCount;
			_hash = Name ^ ((ParamCount << 16) | (ParamCount >> 16));
		}
		public override bool Equals(object obj) {
			if (!(obj is PdtOperatorSignature)) return false;
			return Equals((PdtOperatorSignature)obj);
		}
		public bool Equals(PdtOperatorSignature other) {
			return Name == other.Name && ParamCount == other.ParamCount;
		}
		public override int GetHashCode() {
			return _hash;
		}
		public override string ToString() {
			return string.Format("{0}({1})", IdentifierManager.SharedInstance.Retrieve(Name), ParamCount);
		}
	}
}