module DynamicCall;

import tango.core.Traits;

template isPointer(T)
{
	const bool isPointer = isPointerType!(T);
}

enum ParamType
{
	// No return value
	Void,

	// ST0
	Float,
	Double,
	Real,

	// EAX
	Byte,
	Word,
	DWord,
	Pointer,		// could probably be removed. Resolve to DWord/QWord instead
	// AArray, 		// isn't tested (merge with Pointer?)

	//EDX,EAX
	QWord,
	DArray,		 // isn't tested

	// Hidden pointer
	Hidden_Pointer,	// for returning large (>8 bytes) structs, not tested yet
}

struct Param
{
	ParamType type;	// type of value
	void* ptr;		// pointer to value
}

// This struct describes everything needed to make a call
struct Call
{
	Param[] input;	// set of input arguments
	Param output;	// result info
	void* funcptr;	// function pointer
}

// makes a call, stores result in call.ouput
void makeDynamicCall(Call* call)
{
	switch (call.output.type) {
		case ParamType.Void:
			_makeCall!(void)(call);
			break;

		case ParamType.Pointer:
			makeCall!(void*)(call);
			break;

		case ParamType.Byte:
			makeCall!(byte)(call);
			break;
			
		case ParamType.Word:
			makeCall!(short)(call);
			break;
			
		case ParamType.DWord:
			makeCall!(int)(call);
			break;
			
		case ParamType.QWord:
			makeCall!(long)(call);
			break;

		case ParamType.Float:
			makeCall!(float)(call);
			break;

		case ParamType.Double:
			makeCall!(double)(call);
			break;
			
		case ParamType.Real:
			makeCall!(real)(call);
			break;
	}
}

// helper function to save some typing
void makeCall(T)(Call* call)
{
	*cast(T*)call.output.ptr = _makeCall!(T)(call);
}

T _makeCall(T)(Call* call)
{
	void* funcptr = call.funcptr;
	void* argptr;

	int numArgs = call.input.length;
	
	if (numArgs != 0) {	// this check is needed because last parameter is passed in EAX (if possible)
		Param* param = call.input.ptr;
		
		// iterate over first numArgs-1 arguments
		for ( ; --numArgs; ++param) {
			argptr = param.ptr;
			switch (param.type) {
				case ParamType.Byte:	// push byte
					arg(*cast(byte*)argptr);
					break;
					
				case ParamType.Word:	// push word
					arg(*cast(short*)argptr);
					break;

				case ParamType.Pointer:
				case ParamType.DWord:	// push dword
					arg(*cast(int*)argptr);
					break;

				case ParamType.QWord:	// push qword
					arg(*cast(long*)argptr);
					break;

				case ParamType.Float:	// push float
					arg(*cast(float*)argptr);
					break;

				case ParamType.Double:	// push double
					arg(*cast(double*)argptr);
					break;
					
				case ParamType.Real:	// push real
					arg(*cast(real*)argptr);
					break;
			}
		}

		// same as above but passes in EAX if possible

		argptr = param.ptr;
		switch (param.type) {
			case ParamType.Byte:
				lastArg(*cast(byte*)argptr);
				break;
				
			case ParamType.Word:
				lastArg(*cast(short*)argptr);
				break;
			
			version(X86_64) {
			} else version (X86) {
				case ParamType.Pointer:
			}
			case ParamType.DWord:
				lastArg(*cast(int*)argptr);
				break;

			version(X86_64) {
				case ParamType.Pointer:
			}
			case ParamType.QWord:
				lastArg(*cast(long*)argptr);
				break;

			case ParamType.Float:
				lastArg(*cast(float*)argptr);
				break;

			case ParamType.Double:
				lastArg(*cast(double*)argptr);
				
			case ParamType.Real:
				lastArg(*cast(real*)argptr);
		}
	}

	asm {
		// call it!
		call funcptr;
	}
}

// A helper function that pushes an argument to stack in a type-safe manner
// extern (System) is used so that argument isn't passed via EAX
// does it work the same way in Linux? Or Linux uses __cdecl?
// There must be other way to pass all the arguments on stack, but this one works well so far
// Beautiful, isn't it?
extern (System) void arg(T)(T arg)
{
	asm {
		naked;
		ret;
	}
}

// A helper function that pushes an argument to stack in a type-safe manner
// Allowed to pass argumet via EAX (that's why it's extern (D))
void lastArg(T)(T arg)
{
	asm {
		naked;
		ret;
	}
}

// Convenient templates to map from type T to corresponding ParamType enum element

template isStructSize(T, int size)
{
	const int isStructSize = is (T == struct) && T.sizeof == size;
}

template ParamTypeFromT(T)
{
	static if (is (T == byte) || is (T == ubyte) || is (T == char) || isStructSize!(T, 1)) {
		const ParamType ParamTypeFromT = ParamType.Byte;
	} else static if (is (T == short) || is (T == ushort) || is (T == wchar) || isStructSize!(T, 2)) {
		const ParamType ParamTypeFromT = ParamType.Word;
	} else static if (is (T == int) || is (T == uint) || is (T == dchar) || isStructSize!(T, 4)) {
		const ParamType ParamTypeFromT = ParamType.DWord;
	} else static if (is (T == long) || is (T == ulong) || isStructSize!(T, 8) || is (T == delegate)) {
		const ParamType ParamTypeFromT = ParamType.QWord;
	} else static if (is (T == float)) {
		const ParamType ParamTypeFromT = ParamType.Float;
	} else static if (is (T == double)) {
		const ParamType ParamTypeFromT = ParamType.Double;
	} else static if (is (T == real)) {
		const ParamType ParamTypeFromT = ParamType.Real;
	} else static if (is (T == void)) {
		const ParamType ParamTypeFromT = ParamType.Void;
	} else static if (isPointer!(T)) {
		const ParamType ParamTypeFromT = ParamType.Pointer;
	}
}