using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;

namespace Cryville.Common.Pdt {
	/// <summary>
	/// Interpreter for Property Definition Tree (PDT) file format.
	/// </summary>
	/// <typeparam name="T">The object type represented by the PDT.</typeparam>
	public partial class PdtInterpreter<T> {
		/// <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>
		readonly static 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, 0x0000,
			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, 0x0000, 0x0000, 0x0000, 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, 0x0000, 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(string src) {
			return Interpret(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(string src, Binder binder) {
			return new PdtInterpreter<T>(src, binder).Interpret();
		}

		readonly string _src;
		readonly Binder _binder;
		protected int Position { get; private set; }
#pragma warning disable IDE1006
		protected char cc { get { return _src[Position]; } }
		protected int ct { get { return cm[cc]; } }
		protected string tokenb(int flag) { // Token Whitelist
			int sp = Position;
			while ((ct & flag) == 0) Position++;
			return _src.Substring(sp, Position - sp);
		}
		protected string tokenw(int flag) { // Token Whitelist
			int sp = Position;
			while ((ct & flag) != 0) Position++;
			return _src.Substring(sp, Position - sp);
		}
		protected void ws() {
			while ((ct & 0x0001) != 0) Position++;
		}
#pragma warning restore IDE1006

		protected char GetChar() {
			char r = cc;
			Position++;
			return r;
		}
		protected string GetIdentifier() {
			if ((ct & 0x0020) == 0) return "";
			return tokenw(0x0010);
		}
		protected string GetNumber() {
			return tokenw(0x0040);
		}
		protected string GetString() {
			int sp = Position;
			do {
				if (cc == '\\') Position++;
				Position++;
			} while (ct != 0x0100);
			Position++;
			return Regex.Replace(_src.Substring(sp + 1, Position - sp - 2), @"\\(.)", "$1");
		}
		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{T}" /> 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) {
			_src = src;
			_binder = binder;
			if (_binder == null)
				_binder = BinderAttribute.CreateBinderOfType(typeof(T));
		}
		/// <summary>
		/// Interprets the source to an object of type <typeparamref name="T" />.
		/// </summary>
		/// <returns>The interpreted object.</returns>
		public T Interpret() {
			InterpretDirectives();
			return (T)InterpretObject(typeof(T));
		}
		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();
						if (GetNumber() != "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 = ReflectionHelper.InvokeEmptyConstructor(type);
			bool dictflag = ReflectionHelper.IsGenericDictionary(type);
			while (true) {
				try { ws(); }
				catch (IndexOutOfRangeException) { return result; }
				object pkey = InterpretKey(type);
				char c = GetChar();
				switch (c) {
					case '{':
						if (dictflag) {
							var ktype = type.GetGenericArguments()[0];
							var ptype = type.GetGenericArguments()[1];
							object key = _binder.ChangeType(pkey, ktype, null);
							object value = InterpretObject(ptype);
							((IDictionary)result).Add(key, value);
						}
						else {
							MemberInfo prop;
							bool flag = ReflectionHelper.TryFindMemberWithAttribute<ElementListAttribute>(type, out prop);
							if (!flag && pkey is string) prop = ReflectionHelper.GetMember(type, (string)pkey);
							Type ptype = ReflectionHelper.GetMemberType(prop);
							if (ReflectionHelper.IsGenericDictionary(ptype)) {
								var ktype = ptype.GetGenericArguments()[0];
								var vtype = ptype.GetGenericArguments()[1];
								if (flag) {
									object key = _binder.ChangeType(pkey, ktype, null);
									object value = InterpretObject(vtype);
									((IDictionary)ReflectionHelper.GetValue(prop, result)).Add(key, value);
								}
								else ReflectionHelper.SetValue(prop, result, InterpretObject(ptype));
							}
							else ReflectionHelper.SetValue(prop, result, InterpretObject(ptype));
						}
						break;
					case ':':
					case ';':
						var exp = c == ';' ? _emptyexp : GetExp();
						if (dictflag) {
							var ktype = type.GetGenericArguments()[0];
							var vtype = type.GetGenericArguments()[1];
							object key = _binder.ChangeType(pkey, ktype, null);
							object value = _binder.ChangeType(exp, vtype, null);
							((IDictionary)result).Add(key, value);
						}
						else {
							MemberInfo prop;
							bool flag = ReflectionHelper.TryFindMemberWithAttribute<PropertyListAttribute>(type, out prop);
							if (!flag && pkey is string) prop = ReflectionHelper.GetMember(type, (string)pkey);
							var ptype = ReflectionHelper.GetMemberType(prop);
							if (!typeof(IDictionary).IsAssignableFrom(ptype)) {
								object value = _binder.ChangeType(exp, ptype, null);
								ReflectionHelper.SetValue(prop, result, value, _binder);
							}
							else {
								var ktype = ptype.GetGenericArguments()[0];
								var vtype = ptype.GetGenericArguments()[1];
								object key = _binder.ChangeType(pkey, ktype, null);
								object value = _binder.ChangeType(exp, vtype, null);
								((IDictionary)ReflectionHelper.GetValue(prop, result)).Add(key, value);
							}
						}
						break;
					case '}':
						return result;
				}
			}
		}
		protected virtual object InterpretKey(Type type) {
			return tokenb(0x1000).Trim();
		}
	}
}
