#include "XCircuit.h"
#include "XCArc.h"
#include "XCPolygon.h"
#include "XCText.h"
#include "xccom-iface.h"

#include <ocidl.h>

extern int number_colors;
extern colorindex *colorlist;

#define CHECK(expr) if ((hres = expr) != S_OK) goto ERRTAG

#ifdef USE_DOTNET
#include "XCDotNet.h"
IDispatch *dotnet_mgr = NULL;
HANDLE dotnet_library = NULL;

typedef HRESULT(__stdcall *ClrCreateManagedInstance_t)(LPCWSTR pTypeName, REFIID riid, void **ppObject);

HRESULT dotnet_init(XCircuit *xc)
{
	HRESULT hres = S_OK;
	ClrCreateManagedInstance_t ClrCreateManagedInstance;

#define ERRTAG FAILED_dotnet_init

	if (dotnet_library == NULL)
		dotnet_library = LoadLibrary("mscoree.dll");
	ClrCreateManagedInstance = (ClrCreateManagedInstance_t)GetProcAddress(dotnet_library, "ClrCreateManagedInstance");
	CHECK(ClrCreateManagedInstance(L"XCircuit.Proxy.Manager, XCircuit", &IID__Object, (void**)&dotnet_mgr));

	if (dotnet_mgr != NULL) {
		VARIANT retval;
		DISPPARAMS dispParams = { NULL, NULL, 0, 0 };
		DISPID dispID;
		LPOLESTR str = L"get_top_namespace";

		CHECK(IDispatch_GetIDsOfNames(dotnet_mgr, &IID_NULL, &str, 1, LOCALE_USER_DEFAULT, &dispID));
		CHECK(IDispatch_Invoke(dotnet_mgr, dispID, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,
				       	&dispParams, &retval, NULL, NULL));
		if (retval.vt == VT_DISPATCH) {
			BSTR nsName = SysAllocString(L"XCircuitDotNetNS");
			XCDotNet *dotnet = make_XCDotNet(retval.pdispVal);
			IDispatch_QueryInterface(&dotnet->iface, &IID_IDispatch, (void**)&retval.pdispVal);
			hres = IScriptControl_AddObject(xc->engine, nsName, retval.pdispVal, TRUE);
			SysFreeString(nsName);
		}
	}

#undef ERRTAG

FAILED_dotnet_init:

	return hres;
}

void dotnet_finish()
{
	if (dotnet_mgr != NULL)
		IDispatch_Release(dotnet_mgr);
	if (dotnet_library != NULL)
		FreeLibrary(dotnet_library);
}

HRESULT create_dotnet_control(BSTR name, BOOL staticFlag, IDispatch **disp, XCircuit *xc)
{
	HRESULT hres;

	if (dotnet_mgr == NULL)
		dotnet_init(xc);

	if (dotnet_mgr != NULL) {
		VARIANT retval, args[3];
		DISPPARAMS dispParams = { NULL, NULL, 0, 0 };
		DISPID dispID;
		LPOLESTR memberName = L"new_dotnet";

#define ERRTAG FAILED_create
		CHECK(IDispatch_GetIDsOfNames(dotnet_mgr, &IID_NULL, &memberName, 1, LOCALE_USER_DEFAULT, &dispID));
		args[2].vt = VT_BSTR;
		args[2].bstrVal = name;
		args[1].vt = VT_ARRAY;
		args[1].parray = NULL;
		args[0].vt = VT_BOOL;
		args[0].boolVal = (staticFlag == FALSE ? VARIANT_FALSE : VARIANT_TRUE);
		dispParams.rgvarg = args;
		dispParams.cArgs = 3;
		CHECK(IDispatch_Invoke(dotnet_mgr, dispID, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispParams, &retval, NULL, NULL));
		if (retval.vt == VT_UNKNOWN) {
			CHECK(IUnknown_QueryInterface(retval.punkVal, &IID__Object, (void**)disp));
			IUnknown_Release(retval.punkVal);
		} else if (retval.vt == VT_DISPATCH) {
			*disp = retval.pdispVal;
		} else
			goto FAILED_create;
#undef ERRTAG
		{
			XCDotNet *dotnet = make_XCDotNet(*disp);
			IDispatch_QueryInterface(&dotnet->iface, &IID_IDispatch, (void**)disp);
		}

FAILED_create:
		disp;
	}

	return hres;
}
#endif

/*************************************************************************
 *************************************************************************
 *************************************************************************/

extern HWND topwin;
extern Clientdata areastruct;

#define TOXCIRCUIT(This) (XCircuit*)TOCOMOBJ(This)

STDMETHODIMP XCircuit_InitScript(IXCircuit *This)
{
	XCircuit *xc = TOXCIRCUIT(This);
	IDispatch *xcDisp = NULL;
	HRESULT hres = S_OK;
	IConnectionPointContainer *engineCPC = NULL;
	IConnectionPoint *engineCP = NULL;
	DWORD cookie;
	BSTR lang = SysAllocString(L"JScript"), xcName = SysAllocString(L"XCircuit");

#define ERRTAG err1
	CHECK(CoCreateInstance(&CLSID_ScriptControl, NULL, CLSCTX_INPROC_SERVER, &IID_IScriptControl, (void**)&xc->engine));
	CHECK(IScriptControl_put_Language(xc->engine, lang));

	CHECK(IXCircuit_QueryInterface(This, &IID_IDispatch, (void**)&xcDisp));
	CHECK(IScriptControl_AddObject(xc->engine, xcName, xcDisp, TRUE));

	CHECK(IScriptControl_QueryInterface(xc->engine, &IID_IConnectionPointContainer, &engineCPC));
	CHECK(IConnectionPointContainer_FindConnectionPoint(engineCPC, &DIID_DScriptControlSource, &engineCP));
	CHECK(IConnectionPoint_Advise(engineCP, &xc->com.unk_iface, &cookie));
#undef ERRTAG

err1:
	if (xcDisp != NULL)
		IDispatch_Release(xcDisp);
	if (engineCPC != NULL)
		IConnectionPointContainer_Release(engineCPC);
	if (engineCP != NULL)
		IConnectionPoint_Release(engineCP);
	SysFreeString(lang);
	SysFreeString(xcName);
	return hres;
}

STDMETHODIMP XCircuit_ExecuteStatement(IXCircuit *This, BSTR statement)
{
	XCircuit *xc = TOXCIRCUIT(This);
	HRESULT hres = IScriptControl_AddCode(xc->engine, statement);

	return hres;
}

STDMETHODIMP XCircuit_TermScript(IXCircuit *This)
{
	XCircuit *xc = TOXCIRCUIT(This);

	IScriptControl_Release(xc->engine);

	return S_OK;
}

STDMETHODIMP XCircuit_ScriptError(IXCircuit *This)
{
	XCircuit *xc = TOXCIRCUIT(This);
	IScriptError *err;
	BSTR src, desc, txt;
	long line, column, number;
	char buf[4096];

	if (IScriptControl_get_Error(xc->engine, &err) != S_OK)
		return S_OK;

	IScriptError_get_Number(err, &number);
	IScriptError_get_Line(err, &line);
	IScriptError_get_Column(err, &column);
	if (number >= XC_E_FIRST && number < XC_E_LAST) {
		src = L"XCircuit";
		desc = XC_ERROR_MSG(number);
	} else {
		IScriptError_get_Source(err, &src);
		IScriptError_get_Description(err, &desc);
	}
	IScriptError_get_Text(err, &txt);
	IScriptError_Clear(err);
	IScriptError_Release(err);

	_snprintf(buf, 4096, "XCircuit encountered a script error (line=%d, column=%d, error=%08x)\n"
			     "\n"
			     "Source: %ls\n"
			     "Description: %ls"
			     "%ls%ls", line, column, number, src, desc,
			     (txt != NULL ? L"\n\nText: " : L""),
			     (txt != NULL ? txt : L""));
	MessageBox(topwin, buf, "Script error", MB_OK|MB_ICONERROR);
	return S_OK;
}

STDMETHODIMP XCircuit_refresh(IXCircuit *This)
{
	refresh(NULL, NULL, NULL);
	return S_OK;
}

STDMETHODIMP XCircuit_quit(IXCircuit *This)
{
	quitcheck(NULL, NULL, NULL);
	return S_OK;
}

STDMETHODIMP XCircuit_zoomin(IXCircuit *This)
{
	xccom_put_zoom(1, 0.0);
	return S_OK;
}

STDMETHODIMP XCircuit_zoomout(IXCircuit *This)
{
	xccom_put_zoom(2, 0.0);
	return S_OK;
}

STDMETHODIMP XCircuit_put_zoom(IXCircuit *This, VARIANT val)
{
	char *cmds[] = { "auto" };
	int idx;

	if ((idx = get_string_index_from_variant(&val, cmds, 1)) >= 0) {
		xccom_put_zoom(0, 0.0);
	} else {
		HRESULT hres = VariantChangeType(&val, &val, 0, VT_R4);
		if (hres != S_OK)
			return hres;
		xccom_put_zoom(4, val.fltVal);
	}

	return S_OK;
}

STDMETHODIMP XCircuit_get_zoom(IXCircuit *This, VARIANT *val)
{
	val->vt = VT_R4;
	val->fltVal = xccom_get_zoom();
	return S_OK;
}

STDMETHODIMP XCircuit_arc(IXCircuit *This, int x, int y, int radius, float angle1, float angle2, IXCArc **pparc)
{
	XCArc *arcobj = (XCArc*)make_XCArc();
	arcptr arc;

	arc = new_arc(NULL, radius, x, y);
	arcobj->handle = (long)arc;
	arcobj->type = ARC;

	arc->width = 1.0;
	arc->style = NORMAL;
	arc->color = -1;
	if (angle2 > angle1) {
		arc->angle1 = angle1;
		arc->angle2 = angle2;
	}
	IXCObject_update(&arcobj->iface, 1);

	IXCArc_QueryInterface(&arcobj->iface, &IID_IXCArc, (void**)pparc);

	return S_OK;
}

STDMETHODIMP XCircuit_box(IXCircuit *This, int x, int y, int width, int height, IXCPolygon **pppoly)
{
	XCPolygon *polyobj = (XCPolygon*)make_XCPolygon();
	polyptr poly;
	XPoint *pts = (XPoint*)malloc(sizeof(XPoint)*4);
	
	pts[0].x = pts[3].x = x;
	pts[0].y = pts[1].y = y;
	pts[1].x = pts[2].x = x+width;
	pts[2].y = pts[3].y = y+height;
	poly = new_polygon(NULL, &pts, 4);
	poly->style = NORMAL;
	poly->width = 1.0;
	poly->color = -1;
	polyobj->handle = (long)poly;
	polyobj->type = POLYGON;
	IXCObject_update(&polyobj->iface, 1);

	IXCPolygon_QueryInterface(&polyobj->iface, &IID_IXCPolygon, (void**)pppoly);

	return S_OK;
}

STDMETHODIMP XCircuit_polygon(IXCircuit *This, SAFEARRAY *coords, IXCPolygon **pppoly)
{
	long lb, ub;
	long i;
	VARIANT elemX, elemY;
	XPoint *pts;
	HRESULT hres;
	polyptr poly;
	XCPolygon *polyobj;

	SafeArrayGetLBound(coords, 1, &lb);
	SafeArrayGetUBound(coords, 1, &ub);
	if ((ub-lb+1)%2)
		return ECODE(XC_E_NUMARGS);
	pts = (XPoint*)malloc(sizeof(XPoint)*((ub-lb+1)/2));
	for (i=lb; i<=ub; i++) {
		SafeArrayGetElement(coords, &i, &elemX);
		i++;
		SafeArrayGetElement(coords, &i, &elemY);
		if ((hres = VariantChangeType(&elemX, &elemX, 0, VT_I2)) != S_OK ||
		    (hres = VariantChangeType(&elemY, &elemY, 0, VT_I2)) != S_OK) {
			free(pts);
			if (hres = DISP_E_TYPEMISMATCH)
				return ECODE(XC_E_TYPEMISMATCH);
			else
				return hres;
		}
		pts[(i-1)/2].x = elemX.iVal;
		pts[(i-1)/2].y = elemY.iVal;
	}

	poly = new_polygon(NULL, &pts, (ub-lb+1)/2);
	poly->style |= UNCLOSED;
	poly->color = -1;
	poly->width = 1.0;
	polyobj = (XCPolygon*)make_XCPolygon();
	polyobj->handle = (long)poly;
	polyobj->type = POLYGON;
	IXCObject_update(&polyobj->iface, 1);

	IXCPolygon_QueryInterface(&polyobj->iface, &IID_IXCPolygon, (void**)pppoly);

	return S_OK;
}

STDMETHODIMP XCircuit_text(IXCircuit *This, BSTR txt, int x, int y, IXCText **pptext)
{
	XCText *labelobj = (XCText*)make_XCText();
	labelptr label;
	char *ctxt = convert_from_bstr(txt);
	stringpart *parts = string_to_parts(ctxt);

	label = new_label(NULL, parts, NORMAL, x, y);
	label->color = -1;
	label->scale = 1.0;
	label->justify = 0;
	free(ctxt);

	labelobj->handle = (long)label;
	labelobj->type = LABEL;
	IXCObject_update(&labelobj->iface, 1);

	IXCText_QueryInterface(&labelobj->iface, &IID_IXCText, (void**)pptext);

	return S_OK;
}

typedef struct script_timeout_data {
	IScriptControl *engine;
	BSTR code;
} script_timeout_data;

static void timeout_handler(void *_data, int *ID)
{
	script_timeout_data *data = (script_timeout_data*)_data;

	IScriptControl_AddCode(data->engine, data->code);
	free(data->code);
	free(data);
}

STDMETHODIMP XCircuit_setTimeout(IXCircuit *This, BSTR expr, int delay, int *ID)
{
	script_timeout_data *data = (script_timeout_data*)malloc(sizeof(script_timeout_data));

	data->engine = (TOXCIRCUIT(This))->engine;
	data->code = _wcsdup(expr);
	*ID = WinAddTimeOut(delay, (void(*)())timeout_handler, data);
	return S_OK;
}

STDMETHODIMP XCircuit_clearTimeout(IXCircuit *This, int ID)
{
	WinRemoveTimeOut(ID);
	return S_OK;
}

BOOL COM_autorefresh = 1;

STDMETHODIMP XCircuit_put_autorefresh(IXCircuit *This, VARIANT_BOOL newVal)
{
	COM_autorefresh = (newVal == VARIANT_TRUE ? TRUE : FALSE);
	return S_OK;
}

STDMETHODIMP XCircuit_get_autorefresh(IXCircuit *This, VARIANT_BOOL *pVal)
{
	*pVal = (COM_autorefresh ? VARIANT_TRUE : VARIANT_FALSE);
	return S_OK;
}

STDMETHODIMP XCircuit_filepopup(IXCircuit *This)
{
	getfile(NULL, NORMAL, NULL);
	return S_OK;
}

STDMETHODIMP XCircuit_init_dotnet(IXCircuit *This, BSTR assembly)
{
	HRESULT hres = E_NOTIMPL;
#ifdef USE_DOTNET
	if (dotnet_mgr == NULL)
		hres = dotnet_init(TOXCIRCUIT(This));
	else
		hres = S_OK;
	/* TODO: make use of assembly name */
#endif
	return hres;
}

STDMETHODIMP XCircuit_import(IXCircuit *This, IDispatch *ns)
{
	HRESULT hres = E_NOTIMPL;
#ifdef USE_DOTNET
	DISPPARAMS dispParams = { NULL, NULL, 0, 0 };
	VARIANT retval;
	DISPID dispID;
	LPOLESTR str = L"GetNamespace";
	XCDotNet *dotnet = (XCDotNet*)ns;
	IDispatch *code;

#define ERRTAG FAILED_import
	CHECK(IDispatch_GetIDsOfNames(dotnet->m_objectproxy, &IID_NULL, &str, 1, LOCALE_USER_DEFAULT, &dispID));
	CHECK(IDispatch_Invoke(dotnet->m_objectproxy, dispID, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,
			       	&dispParams, &retval, NULL, NULL));
	CHECK(IScriptControl_get_CodeObject((TOXCIRCUIT(This))->engine, &code));
	hres = IDispatch_GetIDsOfNames(code, &IID_NULL, &retval.bstrVal, 1, LOCALE_USER_DEFAULT, &dispID);
	if (hres != S_OK) {
		CHECK(IScriptControl_AddObject((TOXCIRCUIT(This))->engine, retval.bstrVal, ns, TRUE));
	} else
		printf("Namespace already imported\n");
#undef ERRTAG

FAILED_import:
#endif /* USE_DOTNET */

	return hres;
}

STDMETHODIMP XCircuit_load_dotnet_plugin(IXCircuit *This, BSTR filename)
{
	HRESULT hres = E_NOTIMPL;
#ifdef USE_DOTNET
	DISPPARAMS dispParams = { NULL, NULL, 0, 0 };
	DISPID dispID;
	LPOLESTR str = L"load_plugin";
	VARIANT arg;

#define ERRTAG FAILED_load_dotnet
	if (dotnet_mgr == NULL) {
		CHECK(dotnet_init(TOXCIRCUIT(This)));
	}
	CHECK(IDispatch_GetIDsOfNames(dotnet_mgr, &IID_NULL, &str, 1, LOCALE_USER_DEFAULT, &dispID));
	arg.vt = VT_BSTR;
	arg.bstrVal = filename;
	dispParams.rgvarg = &arg;
	dispParams.cArgs = 1;
	CHECK(IDispatch_Invoke(dotnet_mgr, dispID, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,
				&dispParams, NULL, NULL, NULL));
#undef ERRTAG

FAILED_load_dotnet:
#endif
	
	return hres;
}

STDMETHODIMP XCircuit_new_menu(IXCircuit *This, BSTR parent, BSTR name, VARIANT_BOOL is_popup, BSTR after, IXCMenuItem **ppItem)
{
	char *parent_cstr = convert_from_bstr(parent), *name_cstr = convert_from_bstr(name), *after_cstr = convert_from_bstr(after);
	Widget menu = WinGetMenu(parent_cstr), item = NULL;
	Arg args[1] = { -1, NULL };
	XCMenuItem *mitem;

	if (after_cstr != NULL && after_cstr[0] != '\0')
		XtSetArg(args[0], XtNattachTo, after_cstr);
	item = WinCreateWidget(name_cstr, (is_popup ? XwcascadeWidgetClass : XwmenubuttonWidgetClass), menu, args, 1, 0);
	free(parent_cstr);
	free(name_cstr);
	free(after_cstr);

	mitem = make_XCMenuItem(item);
	IUnknown_QueryInterface(&mitem->com.unk_iface, &IID_IXCMenuItem, (void**)ppItem);

	return S_OK;
}

STDMETHODIMP XCircuit_get_menu(IXCircuit *This, BSTR name, IXCMenuItem **ppItem)
{
	char *name_cstr = convert_from_bstr(name);
	Widget menu = WinGetMenu(name_cstr);
	XCMenuItem *item = NULL;

	if (menu != NULL) {
		item = make_XCMenuItem(menu);
		IUnknown_QueryInterface(&item->com.unk_iface, &IID_IXCMenuItem, (void**)ppItem);
	}
	free(name_cstr);

	return S_OK;
}

STDMETHODIMP XCircuit_add_color(IXCircuit *This, int red, int green, int blue)
{
	addnewcolorentry(RGB(red, green, blue));
	return S_OK;
}

STDMETHODIMP XCircuit_put_color(IXCircuit *This, int cval)
{
	int cindex;

	cval = cval & 0x00FFFFFF;
	addnewcolorentry(cval);
	for (cindex=0; cindex<number_colors; cindex++)
		if (colorlist[cindex].color.pixel == cval)
			break;
	unselect_all();
	setcolor(colorlist[cindex].cbutton, 0, NULL);
	return S_OK;
}

STDMETHODIMP XCircuit_get_color(IXCircuit *This, int *cval)
{
	*cval = areastruct.color;
	return S_OK;
}

static IXCircuitVtbl vtbl = {
	/* IUnknown */
	NULL,
	NULL,
	NULL,
	/* IXCircuit */
	XCircuit_InitScript,
	XCircuit_TermScript,
	XCircuit_ExecuteStatement,
	XCircuit_ScriptError,
	XCircuit_refresh,
	XCircuit_quit,
	XCircuit_zoomin,
	XCircuit_zoomout,
	XCircuit_put_zoom,
	XCircuit_get_zoom,
	XCircuit_arc,
	XCircuit_setTimeout,
	XCircuit_clearTimeout,
	XCircuit_put_autorefresh,
	XCircuit_get_autorefresh,
	XCircuit_filepopup,
	XCircuit_box,
	XCircuit_polygon,
	XCircuit_text,
	XCircuit_init_dotnet,
	XCircuit_import,
	XCircuit_load_dotnet_plugin,
	XCircuit_new_menu,
	XCircuit_get_menu,
	XCircuit_add_color,
	XCircuit_put_color,
	XCircuit_get_color,
};

/*************************************************************************
 *************************************************************************
 *************************************************************************/

/*
static ITypeInfo *ti = NULL;

HRESULT init_XCircuit(ITypeLib *tl)					
{									
	HRESULT hres;							
									
	hres = ITypeLib_GetTypeInfoOfGuid(tl, &IID_IXCircuit, &ti);	
	if (hres == S_OK)						
		ITypeInfo_AddRef(ti);					
	return hres;							
}									
									
HRESULT close_XCircuit()							
{									
	ITypeInfo_Release(ti);						
	return S_OK;							
}
*/

XCircuit* make_XCircuit()
{
	XCircuit *ptr = (XCircuit*)LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT,
				    sizeof(XCircuit));
	xccom_object_init(&ptr->com);
	xccom_object_add_interface(&ptr->com, &IID_IXCircuit, &vtbl);
	xccom_object_create_dispatch_interface(&ptr->com, &IID_IXCircuit, COM_get_typeinfo(&IID_IXCircuit));
	xccom_object_add_connectionpoint(&ptr->com, &DIID_DXCEvents);
	return ptr;
}
