using System;

namespace Cryville.Common.Pdt {
	/// <summary>
	/// Span on the memory of a <see cref="PdtEvaluatorBase" />.
	/// </summary>
	public unsafe struct PdtVariableMemory {
		readonly byte* _ptr;
		/// <summary>
		/// The length of the span.
		/// </summary>
		public int Length { get; private set; }
		/// <summary>
		/// The type of the span.
		/// </summary>
		public int Type { get; private set; }
		internal PdtVariableMemory(int type, byte* ptr, int len) {
			Type = type;
			_ptr = ptr;
			Length = len;
		}
		/// <summary>
		/// Copies the memory in the span to another span.
		/// </summary>
		/// <param name="dest">The destination span.</param>
		public void CopyTo(PdtVariableMemory dest) {
			CopyTo(dest._ptr, 0, Length);
		}
		/// <summary>
		/// Copies the memory in the span to a buffer.
		/// </summary>
		/// <param name="dest">The destination buffer.</param>
		/// <param name="destOffset">The offset on the destination buffer to start copying to.</param>
		public void CopyTo(byte[] dest, int destOffset) {
			fixed (byte* ptr = dest) {
				CopyTo(ptr, destOffset, Length);
			}
		}
		/// <summary>
		/// Copies the memory in the span to a buffer.
		/// </summary>
		/// <param name="dest">The destination buffer.</param>
		/// <param name="destOffset">The offset on the destination buffer to start copying to.</param>
		/// <param name="length">The length to copy.</param>
		public void CopyTo(byte* dest, int destOffset, int length) {
			for (int i = 0; i < length; i++)
				dest[destOffset + i] = _ptr[i];
		}
		/// <summary>
		/// Gets the memory of the span as a number.
		/// </summary>
		/// <param name="offset">The offset on the span to start reading from.</param>
		/// <returns>A number.</returns>
		/// <exception cref="InvalidCastException">The span at the offset does not represent a number.</exception>
		public float AsNumber(int offset = 0) {
			if (Type != PdtInternalType.Number && Type != PdtInternalType.Vector)
				throw new InvalidCastException("Not a number");
			float value;
			byte* ptr = (byte*)&value;
			for (int i = 0; i < sizeof(float); i++)
				ptr[i] = _ptr[i + offset];
			return value;
		}
		/// <summary>
		/// Sets the memory of the span to a number.
		/// </summary>
		/// <param name="value">The number.</param>
		/// <param name="offset">The offset from the start of the span.</param>
		/// <exception cref="InvalidCastException">The span at the offset does not represent a number.</exception>
		/// <exception cref="InvalidOperationException">The length of the span is not sufficient.</exception>
		public void SetNumber(float value, int offset = 0) {
			if (Type != PdtInternalType.Number && Type != PdtInternalType.Vector)
				throw new InvalidCastException("Not a number");
			if (Length < sizeof(float) + offset)
				throw new InvalidOperationException("Frame length not sufficient");
			byte* ptr = (byte*)&value;
			for (int i = 0; i < sizeof(float); i++)
				_ptr[i + offset] = ptr[i];
		}
		/// <summary>
		/// Gets the memory of the span as a string.
		/// </summary>
		/// <param name="offset">The offset on the span to start reading from.</param>
		/// <returns>A string.</returns>
		/// <exception cref="InvalidCastException">The span at the offset does not represent a string.</exception>
		public string AsString(int offset = 0) {
			if (Type != PdtInternalType.String && Type != PdtInternalType.Array)
				throw new InvalidCastException("Not a string");
			var len = *(int*)(_ptr + offset);
			return new string((char*)(_ptr + offset + sizeof(int)), 0, len);
		}
		/// <summary>
		/// Sets the memory of the span to a string.
		/// </summary>
		/// <param name="value">The string.</param>
		/// <param name="offset">The offset from the start of the span.</param>
		/// <exception cref="InvalidCastException">The span at the offset does not represent a string.</exception>
		/// <exception cref="InvalidOperationException">The length of the span is not sufficient.</exception>
		public void SetString(string value, int offset = 0) {
			if (Type != PdtInternalType.String && Type != PdtInternalType.Array)
				throw new InvalidCastException("Not a string");
			int strlen = value.Length;
			if (Length < strlen * sizeof(char) + sizeof(int) + offset)
				throw new InvalidOperationException("Frame length not sufficient");
			char* ptr = (char*)(_ptr + offset + sizeof(int));
			*(int*)(_ptr + offset) = strlen;
			int i = 0;
			foreach (var c in value) ptr[i++] = c;
		}
		/// <summary>
		/// Gets the memory of the span as an undefined identifier.
		/// </summary>
		/// <param name="offset">The offset on the span to start reading from.</param>
		/// <returns>The name of an undefined identifier.</returns>
		/// <exception cref="InvalidCastException">The span does not represent an undefined identifier.</exception>
		public int AsIdentifier(int offset = 0) {
			if (Type != PdtInternalType.Undefined && Type != PdtInternalType.Array)
				throw new InvalidCastException("Not an identifier");
			return *(int*)(_ptr + offset);
		}
		internal void* TrustedAsOfLength(int len) {
			if (Length < len)
				throw new InvalidCastException("Type not matched");
			return _ptr;
		}
		/// <summary>
		/// Gets the array suffix.
		/// </summary>
		/// <param name="arrtype">The type of the array.</param>
		/// <param name="pc">The item count of the array.</param>
		/// <exception cref="InvalidCastException">The span does not represent an array.</exception>
		public void GetArraySuffix(out int arrtype, out int pc) {
			if (Type != PdtInternalType.Vector && Type != PdtInternalType.Array)
				throw new InvalidCastException("Not an array or vector");
			arrtype = *(int*)(_ptr + Length - sizeof(int));
			if (Type == PdtInternalType.Array) pc = *(int*)(_ptr + Length - 2 * sizeof(int));
			else pc = -1;
		}
		/// <summary>
		/// Sets the array suffix.
		/// </summary>
		/// <param name="arrtype">The type of the array.</param>
		/// <param name="pc">The item count of the array.</param>
		/// <exception cref="InvalidCastException">The span does not represent an array.</exception>
		public void SetArraySuffix(int arrtype, int pc = 0) {
			if (Type != PdtInternalType.Vector && Type != PdtInternalType.Array)
				throw new InvalidCastException("Not an array or vector");
			*(int*)(_ptr + Length - sizeof(int)) = arrtype;
			if (Type == PdtInternalType.Array) *(int*)(_ptr + Length - 2 * sizeof(int)) = pc;
		}
	}
}
