using Cryville.Common.Collections;
using Cryville.Common.Logging;
using Cryville.Common.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using CMath = System.Math;

namespace Cryville.Common.Pdt {
	/// <summary>
	/// Interpreter for Property Definition Tree (PDT) file format.
	/// </summary>
	public partial class PdtInterpreter {
		/// <summary>
		/// The character category map.
		/// </summary>
		/// <remarks>
		/// <list type="bullet">
		///		<item><term><c>0x0001</c></term><description>White Space</description></item>
		///		<item><term><c>0x0010</c></term><description>Identifier</description></item>
		///		<item><term><c>0x0020</c></term><description>Identifier Begin</description></item>
		///		<item><term><c>0x0040</c></term><description>Digit</description></item>
		///		<item><term><c>0x0080</c></term><description>Operator</description></item>
		///		<item><term><c>0x0100</c></term><description>String</description></item>
		///		<item><term><c>0x0200</c></term><description>Opening Bracket</description></item>
		///		<item><term><c>0x0400</c></term><description>Closing Bracket</description></item>
		///		<item><term><c>0x0800</c></term><description>End of Expression</description></item>
		///		<item><term><c>0x1000</c></term><description>End of Key</description></item>
		/// </list>
		/// </remarks>
		static readonly int[] cm = new int[] {
			//   0       1       2       3       4       5       6       7       8       9       A       B       C       D       E       F
			0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000,
			0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
			0x0001, 0x0080, 0x0100, 0x0000, 0x0030, 0x0080, 0x0080, 0x0000, 0x0200, 0x0400, 0x0080, 0x0080, 0x0080, 0x0080, 0x0040, 0x0080,
			0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x1000, 0x1800, 0x0080, 0x0080, 0x0080, 0x0030,
			0x0080, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
			0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0000, 0x0080, 0x0000, 0x0080, 0x0030,
			0x0000, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
			0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x1000, 0x0080, 0x1000, 0x0080, 0x0000,
		};

		/// <summary>
		/// Interprets a source string to an object of type <typeparamref name="T" />.
		/// </summary>
		/// <param name="src">The source string.</param>
		/// <returns>The interpreted object.</returns>
		public static T Interpret<T>(string src) {
			return Interpret<T>(src, BinderAttribute.CreateBinderOfType(typeof(T)));
		}
		/// <summary>
		/// Interprets a source string to an object of type <typeparamref name="T"/> with a binder.
		/// </summary>
		/// <param name="src">The source string.</param>
		/// <param name="binder">The binder.</param>
		/// <returns>The interpreted object.</returns>
		public static T Interpret<T>(string src, Binder binder) {
			return (T)new PdtInterpreter(src, binder).Interpret(typeof(T));
		}

		/// <summary>
		/// The source string.
		/// </summary>
		public string Source { get; protected set; }
		protected Binder _binder;
		/// <summary>
		/// The current position in the string being parsed by the interpreter.
		/// </summary>
		public int Position { get; protected set; }
#pragma warning disable IDE1006
		/// <summary>
		/// The character at the current position.
		/// </summary>
		/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
		protected char cc { get { return Source[Position]; } }
		/// <summary>
		/// The category of the character.
		/// </summary>
		/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
		protected int ct { get { return cm[cc]; } }
		/// <summary>
		/// Reads a token until a character of type <paramref name="flag" /> is met.
		/// </summary>
		/// <param name="flag">The type filter.</param>
		/// <returns>A token from the current position (inclusive) to the next character of type <paramref name="flag" /> (exclusive).</returns>
		/// <exception cref="IndexOutOfRangeException">No character of type <paramref name="flag" /> is met.</exception>
		protected string tokenb(int flag) {
			int sp = Position;
			while ((ct & flag) == 0) Position++;
			return Source.Substring(sp, Position - sp);
		}
		/// <summary>
		/// Reads a token until a character that is not of type <paramref name="flag" /> is met.
		/// </summary>
		/// <param name="flag">The type filter.</param>
		/// <returns>A token from the current position (inclusive) to the next character that is not of type <paramref name="flag" /> (exclusive).</returns>
		/// <exception cref="IndexOutOfRangeException">No character that is not of type <paramref name="flag" /> is met.</exception>
		protected string tokenw(int flag) {
			int sp = Position;
			while ((ct & flag) != 0) Position++;
			return Source.Substring(sp, Position - sp);
		}
		/// <summary>
		/// Skips over whitespaces.
		/// </summary>
		/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
		protected void ws() {
			while ((ct & 0x0001) != 0) Position++;
		}
#pragma warning restore IDE1006

		/// <summary>
		/// Reads the current character and increments the position.
		/// </summary>
		/// <returns>The current character.</returns>
		/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
		protected char GetChar() {
			char r = cc;
			Position++;
			return r;
		}
		/// <summary>
		/// Reads an identifier.
		/// </summary>
		/// <returns>An identifier.</returns>
		/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
		protected string GetIdentifier() {
			if ((ct & 0x0020) == 0) return "";
			return tokenw(0x0010);
		}
		/// <summary>
		/// Reads a number.
		/// </summary>
		/// <returns>A number.</returns>
		/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
		protected string GetNumber() {
			return tokenw(0x0040);
		}
		/// <summary>
		/// Reads a string.
		/// </summary>
		/// <returns>A string.</returns>
		/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
		protected string GetString() {
			int sp = Position;
			do {
				if (cc == '\\') Position++;
				Position++;
			} while (ct != 0x0100);
			Position++;
			return Regex.Replace(Source.Substring(sp + 1, Position - sp - 2), @"\\(.)", "$1");
		}
		/// <summary>
		/// Reads an expression.
		/// </summary>
		/// <returns>An expression.</returns>
		/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
		protected PdtExpression GetExp() {
			var ins = new LinkedList<PdtInstruction>();
			int _;
			InterpretExp(ins, -2, out _);
			return new PdtExpression(ins);
		}

		readonly Dictionary<string, PdtExpression> defs = new Dictionary<string, PdtExpression>();
		/// <summary>
		/// Creates an instance of the <see cref="PdtInterpreter" /> class.
		/// </summary>
		/// <param name="src">The source string.</param>
		/// <param name="binder">The binder. May be <c>null</c>.</param>
		public PdtInterpreter(string src, Binder binder) {
			Source = src;
			_binder = binder;
		}
		int[] m_formatVersion;
		public int[] GetFormatVersion() {
			if (m_formatVersion == null) InterpretDirectives();
			return m_formatVersion;
		}
		/// <summary>
		/// Interprets the source to an object.
		/// </summary>
		/// <param name="type">The output type.</param>
		/// <returns>The interpreted object.</returns>
		public object Interpret(Type type) {
			try {
				if (m_formatVersion == null) InterpretDirectives();
				if (_binder == null)
					_binder = BinderAttribute.CreateBinderOfType(type);
				return InterpretObject(type);
			}
			catch (Exception ex) {
				throw new PdtParsingException(this, ex);
			}
		}
		void InterpretDirectives() {
			bool flag = false;
			ws();
			while (cc == '#') {
				Position++;
				switch (GetIdentifier()) {
					case "ver":
						ws();
						Logger.Log("main", 3, "PDT", "Legacy PDT directive #ver={0} found. Ignoring.", GetNumber());
						break;
					case "format":
						ws();
						m_formatVersion = (from i in GetNumber().Split('.') select int.Parse(i)).ToArray();
						if (m_formatVersion.Length == 0)
							throw new FormatException("Invalid format version");
						if (m_formatVersion[0] != 1)
							throw new NotSupportedException("Format not supported");
						flag = true;
						break;
					case "define":
						if (!flag) throw new FormatException("Format directive not found");
						ws();
						string dname = GetIdentifier();
						ws();
						PdtExpression dexp = GetExp();
						defs.Add(dname, dexp);
						break;
					default:
						throw new NotSupportedException("Unsupported directive found");
				}
				ws();
			}
			if (!flag) throw new FormatException("Format directive not found");
		}
		object InterpretObject(Type type) {
			var result = Activator.CreateInstance(type);
			bool pcflag = PairCollection.IsPairCollection(type);
			while (true) {
				try { ws(); }
				catch (IndexOutOfRangeException) { return result; }
				if (cc == '}') {
					GetChar();
					return result;
				}
				object pkey = InterpretKey(type);
				char c = GetChar();
				switch (c) {
					case '{':
						InterpretObjectInternal<ElementListAttribute>(pcflag, type, pkey, result, type => InterpretObject(type));
						break;
					case ':':
					case ';':
						var exp = c == ';' ? PdtExpression.Empty : GetExp();
						InterpretObjectInternal<PropertyListAttribute>(pcflag, type, pkey, result, type => _binder.ChangeType(exp, type, null));
						break;
					default:
						throw new InvalidOperationException("Internal error: Invalid key interpretation");
				}
			}
		}
		void InterpretObjectInternal<T>(bool pcflag, Type type, object pkey, object result, Func<Type, object> vfunc) where T : Attribute {
			if (pcflag) {
				using (var collection = new PairCollection(result)) {
					var ktype = type.GetGenericArguments()[0];
					var ptype = type.GetGenericArguments()[1];
					object key = _binder.ChangeType(pkey, ktype, null);
					object value = vfunc(ptype);
					collection.Add(key, value);
				}
			}
			else {
				MemberInfo prop = null;
				bool flag = false;
				if (pkey is string) prop = FieldLikeHelper.GetMember(type, (string)pkey);
				if (prop == null) {
					prop = FieldLikeHelper.FindMemberWithAttribute<T>(type);
					flag = true;
				}
				if (prop == null) throw new MissingMemberException(string.Format("The property \"{0}\" is not found", pkey));
				Type ptype = FieldLikeHelper.GetMemberType(prop);
				if (flag) {
					var origCollection = FieldLikeHelper.GetValue(prop, result);
					if (origCollection == null) {
						FieldLikeHelper.SetValue(prop, result, origCollection = Activator.CreateInstance(ptype));
					}
					using (var collection = new PairCollection(origCollection)) {
						var ktype = ptype.GetGenericArguments()[0];
						var vtype = ptype.GetGenericArguments()[1];
						object key = _binder.ChangeType(pkey, ktype, null);
						object value = vfunc(vtype);
						collection.Add(key, value);
					}
				}
				else FieldLikeHelper.SetValue(prop, result, vfunc(ptype), _binder);
			}
		}
		/// <summary>
		/// Interprets a key from the current position.
		/// </summary>
		/// <returns>The interpreted key.</returns>
		protected virtual object InterpretKey(Type type) {
			return tokenb(0x1000).Trim();
		}
	}
	/// <summary>
	/// The exception that is thrown when the interpretation of a PDT fails.
	/// </summary>
	public class PdtParsingException : Exception {
		public PdtParsingException(PdtInterpreter interpreter) : this(interpreter, null) { }
		public PdtParsingException(PdtInterpreter interpreter, Exception innerException)
			: base(GenerateMessage(interpreter, innerException), innerException) { }
		static string GenerateMessage(PdtInterpreter interpreter, Exception innerException) {
			string src = interpreter.Source;
			int pos = interpreter.Position;
			if (pos >= src.Length) return "Failed to interpret the PDT: There are some missing or redundant tokens";
			int lineStartPos = src.LastIndexOf('\n', pos) + 1;
			int previewStartPos = src.LastIndexOf('\n', pos, CMath.Min(64, pos));
			if (previewStartPos == -1) {
				previewStartPos = pos - 64;
				if (previewStartPos < 0) previewStartPos = 0;
			}
			else previewStartPos++;
			int previewEndPos = src.IndexOf('\n', pos, CMath.Min(64, src.Length - pos));
			if (previewEndPos == -1) {
				previewEndPos = pos + 64;
				if (previewEndPos > src.Length) previewEndPos = src.Length;
			}
			return string.Format(
				"Failed to interpret the PDT at line {0}, position {1}: {2}\n{3}",
				src.Take(interpreter.Position).Count(c => c == '\n') + 1,
				pos - lineStartPos + 1,
				innerException == null ? "Unknown error" : innerException.Message,
				src.Substring(previewStartPos, previewEndPos - previewStartPos)
			);
		}
	}
}
