#include "XCDotNet.h"
/*#include "mscorlib.h"*/

#include <dispex.h>

const IID IID__Object = {0x65074F7F,0x63C0,0x304E,{0xAF,0x0A,0xD5,0x17,0x41,0xCB,0x4A,0x8D}};

enum BindingsFlags {
	InvokeMethod   = 256,
	CreateInstance = 512,
	GetField       = 1024,
	SetField       = 2048,
	GetProperty    = 4096,
	SetProperty    = 8192,
};

void __declspec(dllexport) xcdotnet_fire_event(IDispatch *sender, IDispatch *target, IDispatch *event_args)
{
	HRESULT hres;
	DISPPARAMS dispParams = { NULL, NULL, 0, 0 };
	VARIANT retval, args[2];
	XCDotNet *dotnet_sender = NULL, *dotnet_args = NULL;

	args[1].vt = VT_DISPATCH;
	dotnet_sender = make_XCDotNet(sender);
	IDispatch_QueryInterface(&dotnet_sender->iface, &IID_IDispatch, (void**)&args[1].pdispVal);
	args[0].vt = VT_DISPATCH;
	dotnet_args = make_XCDotNet(event_args);
	IDispatch_QueryInterface(&dotnet_args->iface, &IID_IDispatch, (void**)&args[0].pdispVal);
	dispParams.rgvarg = args;
	dispParams.cArgs = 2;
	hres = IDispatch_Invoke(target, 0, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispParams, &retval, NULL, NULL);
	IDispatch_Release(&dotnet_sender->iface);
	IDispatch_Release(&dotnet_args->iface);
	printf("Event result=%p\n", hres);
}

STDMETHODIMP xcdotnet_queryinterface(IDispatch *This, REFIID iid, void **ppvObject)
{
	if (InlineIsEqualGUID(iid, &IID_IUnknown) ||
	    InlineIsEqualGUID(iid, &IID_IDispatch) ||
	    InlineIsEqualGUID(iid, &IID_IDispatchEx))
		*ppvObject = This;
	else
		return E_NOINTERFACE;

	IDispatch_AddRef(This);

	return S_OK;
}

STDMETHODIMP_(ULONG) xcdotnet_addref(IDispatch *This)
{
	((XCDotNet*)This)->m_refCount++;
	return S_OK;
}

STDMETHODIMP_(ULONG) xcdotnet_release(IDispatch *This)
{
	XCDotNet *dotnet = (XCDotNet*)This;
	if (--(dotnet->m_refCount) == 0) {
		if (dotnet->m_objectproxy)
			IDispatch_Release(dotnet->m_objectproxy);
		LocalFree(This);
	}
	return S_OK;
}

STDMETHODIMP xcdotnet_gettypeinfocount(IDispatch *This, UINT *pctinfo)
{
	return E_NOTIMPL;
}

STDMETHODIMP xcdotnet_gettypeinfo(IDispatch *This, UINT itinfo, LCID lcid, ITypeInfo **pptinfo)
{
	return DISP_E_BADINDEX;
}

STDMETHODIMP xcdotnet_getidsofnames(IDispatch *This, REFIID iid, LPOLESTR *rgszNames, UINT cNames,
		LCID lcid, DISPID *rgDispId)
{
	if (cNames != 1)
		return DISP_E_UNKNOWNNAME;
	else {
		XCDotNet *dotnet = (XCDotNet*)This;
		HRESULT hres;
		LPOLESTR memberName = L"GetMemberID";
		DISPPARAMS dispParams = { NULL, NULL, 0, 0 };
		VARIANT retval, arg;
		BSTR argName = SysAllocString(rgszNames[0]);
		DISPID dispID;

		hres = IDispatch_GetIDsOfNames(dotnet->m_objectproxy, &IID_NULL, &memberName, 1, lcid, &dispID);
		arg.vt = VT_BSTR;
		arg.bstrVal = argName;
		dispParams.rgvarg = &arg;
		dispParams.cArgs = 1;
		hres = IDispatch_Invoke(dotnet->m_objectproxy, dispID, &IID_NULL, lcid, DISPATCH_METHOD,
				&dispParams, &retval, NULL, NULL);
		if (retval.vt == VT_I4 && retval.lVal >= 0) {
			*rgDispId = retval.lVal;
			return S_OK;
		} else
			return DISP_E_UNKNOWNNAME;
	}
}

STDMETHODIMP xcdotnet_invoke(IDispatch *This, DISPID dispIdMember, REFIID iid, LCID lcid, WORD wFlags,
		DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
	HRESULT hres;
	DISPID dispID;
	DISPPARAMS dispParams = { NULL, NULL, 0, 0 };
	LPOLESTR memberName = L"InvokeProxy";
	VARIANT args[3];
	XCDotNet *dotnet = (XCDotNet*)This;
	SAFEARRAY *argArray;
	unsigned int i;

	hres = IDispatch_GetIDsOfNames(dotnet->m_objectproxy, &IID_NULL, &memberName, 1, lcid, &dispID);
	args[2].vt = VT_I4;
	args[2].lVal = dispIdMember;
	args[1].vt = VT_I4;
	args[1].lVal = 0;
	if (wFlags & DISPATCH_METHOD)
		args[1].lVal |= InvokeMethod;
	if (wFlags & DISPATCH_PROPERTYGET)
		args[1].lVal |= (GetProperty | GetField);
	if (wFlags & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
		args[1].lVal |= (SetProperty | SetField);
	if (wFlags & DISPATCH_CONSTRUCT)
		args[1].lVal |= CreateInstance;
	args[0].vt = VT_ARRAY;
	argArray = SafeArrayCreateVector(VT_VARIANT, 0, pDispParams->cArgs);
	for (i=0; i<pDispParams->cArgs; i++) {
		if (pDispParams->rgvarg[i].vt == VT_DISPATCH && 
				pDispParams->rgvarg[i].pdispVal->lpVtbl->Invoke == xcdotnet_invoke) {
			pDispParams->rgvarg[i].pdispVal = ((XCDotNet*)pDispParams->rgvarg[i].pdispVal)->m_objectproxy;
		}
		SafeArrayPutElement(argArray, &i, &pDispParams->rgvarg[i]);
	}
	args[0].parray = argArray;
	dispParams.rgvarg = args;
	dispParams.cArgs = 3;
	hres = IDispatch_Invoke(dotnet->m_objectproxy, dispID, &IID_NULL, lcid, DISPATCH_METHOD,
			&dispParams, pVarResult, pExcepInfo, NULL);
	if (pVarResult && (pVarResult->vt == VT_UNKNOWN || pVarResult->vt == VT_DISPATCH)) {
		IDispatch *disp = NULL;
		XCDotNet *dotnet;

		if (pVarResult->vt == VT_UNKNOWN) {
			IUnknown_QueryInterface(pVarResult->punkVal, &IID__Object, &disp);
			IUnknown_Release(pVarResult->punkVal);
		} else
			disp = pVarResult->pdispVal;
		dotnet = make_XCDotNet(disp);
		pVarResult->vt = VT_DISPATCH;
		IDispatch_QueryInterface(&dotnet->iface, &IID_IDispatch, (void**)&pVarResult->pdispVal);
	}

	return hres;
}

STDMETHODIMP xcdotnet_getdispid(IDispatchEx *This, BSTR name, DWORD flags, DISPID *dispID)
{
	return IDispatchEx_GetIDsOfNames(This, &IID_NULL, &name, 1, LOCALE_USER_DEFAULT, dispID);
}

STDMETHODIMP xcdotnet_invokeex(IDispatchEx *This, DISPID id, LCID lcid, WORD flags, DISPPARAMS *dispParams, VARIANT *retval, EXCEPINFO *einfo, IServiceProvider *spCaller)
{
	printf("InvokeEx: %p\n", id);
	return IDispatchEx_Invoke(This, id, &IID_NULL, lcid, flags, dispParams, retval, einfo, NULL);
}

STDMETHODIMP xcdotnet_deletememberbyname(IDispatchEx *This, BSTR name, DWORD flags)
{
	return E_NOTIMPL;
}

STDMETHODIMP xcdotnet_deletememberbydispid(IDispatchEx *This, DISPID id)
{
	return E_NOTIMPL;
}

STDMETHODIMP xcdotnet_getmemberproperties(IDispatchEx *This, DISPID id, DWORD flags, DWORD *pFlags)
{
	return E_NOTIMPL;
}

STDMETHODIMP xcdotnet_getmembername(IDispatchEx *This, DISPID id, BSTR *pName)
{
	return E_NOTIMPL;
}

STDMETHODIMP xcdotnet_getnextdispid(IDispatchEx *This, DWORD flags, DISPID id, DISPID *pid)
{
	return E_NOTIMPL;
}

STDMETHODIMP xcdotnet_getnamespaceparent(IDispatchEx *This, IUnknown **punk)
{
	return E_NOTIMPL;
}

static IDispatchExVtbl vtbl = {
	xcdotnet_queryinterface,
	xcdotnet_addref,
	xcdotnet_release,
	xcdotnet_gettypeinfocount,
	xcdotnet_gettypeinfo,
	xcdotnet_getidsofnames,
	xcdotnet_invoke,
	xcdotnet_getdispid,
	xcdotnet_invokeex,
	xcdotnet_deletememberbyname,
	xcdotnet_deletememberbydispid,
	xcdotnet_getmemberproperties,
	xcdotnet_getmembername,
	xcdotnet_getnextdispid,
	xcdotnet_getnamespaceparent
};

XCDotNet* make_XCDotNet(IDispatch *punk)
{
	XCDotNet *dotnet = (XCDotNet*)LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, sizeof(XCDotNet));
	dotnet->iface.lpVtbl = (IDispatchVtbl*)&vtbl;
	dotnet->m_objectproxy = punk;
	return dotnet;
}
