module std.dynamic.core;

private static import std.algorithm;
private import core.exception, std.exception, std.typetuple, std.metastrings, std.traits, core.vararg, std.stdio;

abstract class Dynamic
{
	protected abstract Dynamic invoke(string member, ...);
	protected this() { assert(typeid(this) != typeid(typeof(this))); }
	public Dynamic opDispatch(string name, T...)(T args) { return this.invoke(name, args); }
	protected @property abstract void* pValue();
	public @property abstract TypeInfo typeId();
	public ref T as(T)() { assert(this.typeId == typeid(T)); return *cast(T*)this.pValue; }
}

static bool isDynamic(TypeInfo t) { auto asTIClass = cast(TypeInfo_Class)t; return asTIClass !is null && (asTIClass == typeid(Dynamic)); }
static bool isValidArgument(TypeInfo tArg, TypeInfo tParam) { return tArg == tParam; } //FIXME: Allow for implicit conversions (both widening conversions and static casts)

private template staticIMapInternal(size_t iStart, alias Mapper, T...) { static if (T.length > 0) { alias TypeTuple!(Mapper!(iStart, T[0]), staticIMapInternal!(iStart + 1, Mapper, T[1 .. $])) staticIMapInternal; } else { alias TypeTuple!() staticIMapInternal; } }
template staticIMap(alias Mapper, T...) { alias staticIMapInternal!(0, Mapper, T) staticIMap; }
template Joiner(string paramPrefix) { template Joiner(size_t i, T...) { enum Joiner = Format!(q{%s%s}, paramPrefix, i); } }
template staticJoin(string separator, T...) { static if (T.length >= 2) { enum staticJoin = T[0] ~ separator ~ staticJoin!(separator, T[1 .. $]); } else static if (T.length >= 1) { enum staticJoin = T[0]; } else { enum staticJoin = q{}; } }

template va_make_local_arg(string paramPrefix, alias args)
{
	template va_make_local_arg(size_t i, T)
	{
		enum va_make_local_arg = Format!(q{auto %s%s = isDynamic(_arguments[%s]) ? va_arg!(Dynamic)(%s).as!(%s) : va_arg!(%s)(%s); },
						paramPrefix, i, i, args.stringof, T.stringof, T.stringof, args.stringof);
	}
}

template isTemplate(T...) { pragma(msg, typeof(&T[0])); enum bool isTemplate = !is(T[0]) && !is(typeof(&T[0])); } //False negatives?

private class DynamicOf(T) if (!is(T : Dynamic)) : Dynamic
{
	T obj;
	this(T obj) { this.obj = obj; }

	alias Dynamic function(DynamicOf!(T) instance, TypeInfo[] _arguments, ref va_list args) Invoker;
	static immutable(Invoker[string]) members;
	enum PARAM_PREFIX = q{param};
	static this()
	{
		Invoker[string] members;
		auto temp = T.init;
		static if (__traits(compiles, typeof(__traits(allMembers, T))))
		{
			foreach (i, member; __traits(allMembers, T))
			{
				static const string SMEMBER_NAME = q{(T).} ~ member;
				static const string IMEMBER_NAME = q{T.init.} ~ member;
				static if (member == q{opUnary})
				{
					
				}
				else
				{
					static if (is(FunctionTypeOf!(mixin(SMEMBER_NAME)) TFn2))
					{
						alias TFn2 TFn;
					}
				}
				static if (is(TFn))
				{
					static if (!__traits(isStaticFunction, mixin(SMEMBER_NAME)))
					{
						//pragma(msg, "Adding member: ", member);
						auto fn = function Dynamic(DynamicOf!(T) instance, TypeInfo[] _arguments, ref va_list args)
						{
							mixin(Format!(q{%s
								auto result = instance.obj.%s(%s);
								assert(_arguments.length == ParameterTypeTuple!(TFn).length);
								foreach (i, type; ParameterTypeTuple!(TFn))
								{ assert(isDynamic(_arguments[i]) ? true : isValidArgument(_arguments[i], typeid(type))); }
								return new DynamicOf!(ReturnType!(TFn))(result);},
								staticJoin!(q{ }, staticIMap!(va_make_local_arg!(PARAM_PREFIX, args), ParameterTypeTuple!(TFn))),
								__traits(allMembers, T)[i],
								staticJoin!(q{, }, staticIMap!(Joiner!(PARAM_PREFIX), ParameterTypeTuple!(FunctionTypeOf!(mixin(SMEMBER_NAME)))))));
						};
						members[member] = fn;
					}
					else
					{
						//pragma(msg, "Ignoring non-instance member: ", member);
					}
				}
				else
				{
					//pragma(msg, "Ignoring non-function member: ", member);
				}
			}
		}
		DynamicOf!(T).members = members.assumeUnique();
	}

	protected override Dynamic invoke(string member, ...)
	{
		auto pMember = member in members;
		if (pMember == null) { throw new HiddenFuncError(typeid(DynamicOf!(T))); }
		va_list args;
		va_start(args, member);
		auto result = (*pMember)(this, _arguments, args);
		va_end(args);
		return result;
	}

	protected @property override TypeInfo typeId() { return typeid(this.obj); }
	protected @property override void* pValue() { return &this.obj; }
}

static Dynamic dynamic(T)(T value) { static if (!is(T : Dynamic)) { return new DynamicOf!(T)(value); } else { return value; } }