/*

	win32 Notify Handler

	Created May 1995, Mark Hammond (MHammond@skippinet.com.au)

*/
#include "stdafx.h"
#include "win32win.h"

// Not very general purpose notify parser!
PyObject *PyNotifyMakeExtraTuple( NMHDR *ptr, char *fmt)
{
	char *pUse = (char *)(ptr+1);
	int argNo = 0;
	int tupleLen = 0;
	for (char *szTemp = fmt;*szTemp; ++szTemp) {
		if (*szTemp == '-')
			++szTemp; // skip next one.
		else if (isdigit(*szTemp))
			; // ignore it.
		else
			++tupleLen; // count it
	}
	PyObject *ret = PyTuple_New(tupleLen);
	PyObject *ob;
	BOOL bIgnore;
	while (*fmt) {
#ifdef _DEBUG
		ob = NULL;
#endif
		bIgnore = *fmt=='-';
		if (bIgnore) ++fmt;
		switch (*fmt) {
		case 'i':
			ob = bIgnore ? NULL : PyInt_FromLong( * ((int *)pUse) );
			pUse += (sizeof(int));
			break;
		case 'P': { // point
			LONG l1 = * ((LONG *)pUse);
			pUse += (sizeof(long));
			LONG l2 = * ((LONG *)pUse);
			pUse += (sizeof(long));
			ob = bIgnore ? NULL : Py_BuildValue("ll", l1, l2);
			break;
			}
		case 'z': // string pointer
		case 's': // string buffer - same for this parse
			{
			char *use = (*fmt=='z') ? * (char **) pUse : pUse;
			ob = bIgnore ? NULL : PyString_FromString(""); // HACK HACK - FIX ME FIX ME
			if (*fmt=='s') { // followed by buffer size;
				int val = 0;
				while (fmt[1] && isdigit(fmt[1])) {
					val = val * 10 + (fmt[1]-'0');
					fmt++;
				}
				pUse += sizeof(char) * val;
			} else {
				pUse += sizeof(char *);
			}
			break;
			}
		case 'Z': // Unicode string pointer
		case 'S': // Unicode buffer - same for this parse
			{
			char *use = (*fmt=='Z') ? * (char **) pUse : pUse;
			ob = bIgnore ? NULL : PyString_FromString(""); // HACK HACK - FIX ME FIX ME
			if (*fmt=='S') { // followed by buffer size;
				int val = 0;
				while (fmt[1] && isdigit(fmt[1])) {
					val = val * 10 + (fmt[1]-'0');
					fmt++;
				}
				pUse += sizeof(wchar_t) * val;
			} else {
				pUse += sizeof(wchar_t *);
			}
			break;
			}

		case 'O': // object with no reference count maintained
			ob = bIgnore ? NULL : (PyObject *)pUse;
			Py_INCREF(ob);
			pUse += (sizeof(PyObject *));
			break;
		case 'T': {// TV_ITEM structure
			TV_ITEM *ptv = (TV_ITEM *)pUse;
			ob = bIgnore ? NULL : PyWinObject_FromTV_ITEM(ptv);
			pUse += (sizeof(TV_ITEM));
			break;
			}
		case 'L': {// LV_ITEM structure
			LV_ITEM *plv = (LV_ITEM *)pUse;
			ob = bIgnore ? NULL : PyWinObject_FromLV_ITEM(plv);
			pUse += (sizeof(LV_ITEM));
			break;
			}
			/*
		case 'H': {// HD_ITEM structure
			HD_ITEM *phd = (HD_ITEM *)pUse;
			ob = bIgnore ? NULL : MakeHD_ITEMTuple(phd);
			pUse += (sizeof(HD_ITEM));
			break;
			}
			*/
		case 'V':{	// Pointer-sized number, also used for HANDLE's
			ob = bIgnore ? NULL : PyWinLong_FromVoidPtr(*(void **)pUse);
			pUse += (sizeof(void *));
			break;
			}
		default:
			ASSERT(FALSE);
			Py_DECREF(ret);
			RETURN_ERR("Bad format char in internal WM_NOTIFY tuple conversion");
		}
		if (!bIgnore) {
			PyTuple_SET_ITEM(ret, argNo, ob);
			argNo++;
		}
		ASSERT(bIgnore==FALSE || ob==NULL); // check bIgnore logic
		fmt++;
	}
	return ret;
}

#define MY_RET_ERR(msg) { PyErr_SetString(ui_module_error, msg); return;}
// Not very general purpose notify parser!
void PyNotifyParseExtraTuple( NMHDR *ptr, PyObject *args,  char *fmt)
{
	char *pUse = (char *)(ptr+1);
	BOOL bIgnoreFormat;
	BOOL bIgnoreValue;
	int argNum = 0;
	if (fmt==NULL){
		PyErr_Format(PyExc_ValueError, "Notify code %d not expected to return data", ptr->code);
		return;
		}

	while (*fmt) {
		PyObject *ob = PyTuple_GetItem(args, argNum);
		if (ob==NULL) return;
		bIgnoreFormat = *fmt=='-';
		// The user can specify 'None' to say 'leave the value alone'
		bIgnoreValue = (ob == Py_None);
		BOOL bIgnore = bIgnoreFormat || bIgnoreValue;
		if (bIgnore) ++fmt;
		switch (*fmt) {
		case 'i':
			if (!bIgnore) {
				if (!PyInt_Check(ob)) MY_RET_ERR("Expected integer object")
				*((int *)pUse) = PyInt_AsLong(ob);
			}
			pUse += (sizeof(int));
			break;
		case 'P': { // point
			ASSERT(FALSE);
			break;
			}
		case 'T': { // TV_ITEM
			ASSERT(FALSE);
			break;
			}
		case 'V':{// Pointer-sized number, also used for HANDLEs, LPARAMS, etc
			if (!bIgnore) {
				if (!PyWinLong_AsVoidPtr(ob, (void **)pUse))
					return;
			}
			pUse += (sizeof(void *));
			break;
			}
		case 'z': // string pointer
			if (!bIgnore) {
				ASSERT(FALSE);
			}
			pUse += (sizeof(char *));
			break;
		case 'Z': // wide string pointer
			if (!bIgnore) {
				ASSERT(FALSE);
			}
			pUse += (sizeof(wchar_t *));
			break;
		case 's': // string buffer
			{
			int bufSize = 0;
			while (fmt[1] && isdigit(fmt[1])) {
				bufSize = bufSize * 10 + (fmt[1]-'0');
				fmt++;
			}
			ASSERT(bufSize);
			if (!bIgnore) {
				if (!PyString_Check(ob)) MY_RET_ERR("Expected string object")
				char *val = PyString_AsString(ob);
				SSIZE_T slen = strlen(val);
				SSIZE_T copylen = max(bufSize-1, slen);
				strncpy( pUse, val, copylen);
				pUse[copylen] = '\0';
			}
			pUse += bufSize;
			break;
			}
		case 'S': // Fixed size unicode buffer
			{
			DWORD bufSize = 0;
			while (fmt[1] && isdigit(fmt[1])) {
				bufSize = bufSize * 10 + (fmt[1]-'0');
				fmt++;
			}
			ASSERT(bufSize);
			if (!bIgnore) {
				WCHAR *wchar_buf=NULL;
				DWORD wchar_cnt;
				if (!PyWinObject_AsWCHAR(ob, &wchar_buf, FALSE, &wchar_cnt))
					return;
				ZeroMemory(pUse, bufSize * sizeof(wchar_t));
				wcsncpy((WCHAR *)pUse, wchar_buf, min(wchar_cnt, bufSize-1));
				PyWinObject_FreeWCHAR(wchar_buf);
			}
			pUse += bufSize * sizeof(wchar_t);
			break;
			}
		case 'O': // object with no reference count maintained
			ASSERT(FALSE);
			break;
		default:
			ASSERT(FALSE);
			MY_RET_ERR("Bad format char in internal WM_NOTIFY tuple conversion");
		}
		fmt++;
		if (!bIgnoreFormat)
			argNum ++;
	}
	return;
}

///////////////////////////////////////////////////////
// General notify handler for Python.
BOOL 
Python_OnNotify (CWnd *pFrom, WPARAM, LPARAM lParam, LRESULT *pResult)
{
	NMHDR *pHdr = (NMHDR *)lParam;
	if (pHdr==NULL)
		return FALSE; // bad data passed?
	UINT code = pHdr->code;
	CEnterLeavePython _celp;
	PyCCmdTarget *pPyWnd = (PyCCmdTarget *) ui_assoc_CObject::GetAssocObject(pFrom);

	if (pPyWnd==NULL) return FALSE; // no object.
	if (!pPyWnd->is_uiobject (&PyCWnd::type)) return FALSE; // unexpected object type.
	PyObject *method;

	if (!pPyWnd->pNotifyHookList || 
		!pPyWnd->pNotifyHookList->Lookup (code, (void *&)method)) {
		Py_DECREF(pPyWnd);
		return FALSE; // no hook installed.
	}
	Py_DECREF(pPyWnd);

	// have method to call.  Build arguments.
	PyObject *ob1 = Py_BuildValue("Nii",
		PyWinLong_FromHANDLE(pHdr->hwndFrom),
		pHdr->idFrom, pHdr->code);
	if (ob1==NULL){
		gui_print_error();
		return FALSE;
		}
	char *fmt;
	/*
	NMTOOLBAR format needs to be adjusted for 64-bit, and already doesn't work
	for 32-bit since PyNotifyMakeExtraTuple doesn't have a case for 'b'
	typedef struct tagNMTOOLBAR {
	    NMHDR hdr;
		int iItem;
		TBBUTTON tbButton;
		int cchText;
		LPTSTR pszText;
		RECT rcButton;} NMTOOLBAR

	typedef struct _TBBUTTON {
		int         iBitmap; 
		int         idCommand; 
		BYTE     fsState; 
		BYTE     fsStyle; 
		#ifdef _WIN64
			BYTE     bReserved[6]     // padding for alignment
		#elif defined(_WIN32)
			BYTE     bReserved[2]     // padding for alignment
		#endif
		DWORD_PTR   dwData; 
		INT_PTR          iString;} TBBUTTON
	*/

	PyObject *ob2=Py_None;	// Use None so we can catch NULL for error.
	if (code >= UDN_LAST && code <= UDN_FIRST)
		fmt = "ii";		// NMUPDOWN
	else if (code == TBN_GETBUTTONINFOW) 
		fmt = "iiibbiiiZ";		// NMTOOLBAR
	else if (code == TBN_QUERYDELETE || code == TBN_QUERYINSERT || (code >= TBN_ENDDRAG && code <= TBN_FIRST ))
		fmt = "iiibbiiiz";
	else if (code == TBN_CUSTHELP || code == TBN_TOOLBARCHANGE || (code >= TBN_RESET && code <= TBN_BEGINADJUST))
		fmt = NULL;		//NMHDR only
	else if (code >= TCN_LAST && code <= TCN_SELCHANGE)
		fmt = "V";		//HWND
	else if (code == TCN_KEYDOWN)
		fmt = "ii";		// NMTCKEYDOWN - ??? First element is a WORD, may work due to alignment ???
	else if (code == TTN_NEEDTEXTA)
		fmt = "-zs80Vi";	// TOOLTIPTEXTA - ie, NMTTDISPINFOA
	else if (code == TTN_NEEDTEXTW)
		fmt = "-ZS80Vi";	// TOOLTIPTEXTW - ie, NMTTDISPINFOW
	else if (code == TTN_POP || code == TTN_SHOW)
		fmt = NULL;		//NMHDR only
	else if (code == TVN_ENDLABELEDITW || code == TVN_BEGINLABELEDITW || code == TVN_SETDISPINFOW
			|| code == TVN_GETDISPINFOW || code == TVN_ENDLABELEDITA || code == TVN_BEGINLABELEDITA
			|| code == TVN_SETDISPINFOA || code == TVN_GETDISPINFOA)
		fmt = "T";		//TV_DISPINFO
	else if (code == TVN_KEYDOWN)
		fmt = "ii";		// NMTVKEYDOWN ??? First element is a WORD ???
	else if (code >= TVN_LAST && code <= TVN_FIRST){
		/* NM_TREEVIEW
			On 64-bit, struct alignment prevents the size-based unpacking in PyNotifyMakeExtraTuple
			from correctly converting this struct.
		*/
		fmt=NULL;
		NMTREEVIEW *nmtv=(NMTREEVIEW *)pHdr;
		ob2=Py_BuildValue("iNN(ll)",
			nmtv->action,
			PyWinObject_FromTV_ITEM(&nmtv->itemOld),
			PyWinObject_FromTV_ITEM(&nmtv->itemNew),
			nmtv->ptDrag.x, nmtv->ptDrag.y);
		}
	else if (code == HDN_ITEMDBLCLICKW || code == HDN_ITEMDBLCLICKA)
		fmt = NULL;		//NMHDR only
	else if (code >= HDN_LAST && code <= HDN_FIRST)
		fmt = "iiH";	//HD_NOTIFY
	else if (code == LVN_KEYDOWN)
		fmt = "ii";		// NMLVKEYDOWN ??? First element is a WORD ???
	else if ((code >= LVN_LAST && code <= LVN_GETDISPINFOW) || code == LVN_ENDLABELEDIT || code == LVN_BEGINLABELEDIT)
		fmt = "L";		// NMLVDISPINFO
	else if (code >= LVN_BEGINRDRAG && code <= LVN_FIRST)
		fmt = "iiiiiPV";		// NMLISTVIEW - Last item is an LPARAM
	else
		fmt = NULL;
	
	if (ob2==Py_None){
		if (fmt==NULL)
			ob2 = PyWinLong_FromVoidPtr(pHdr + 1);
		else
			ob2 = PyNotifyMakeExtraTuple(pHdr, fmt);
		}
	if (ob2==NULL){
		gui_print_error();
		return FALSE;
		}

	// make the call with my params.
	PyObject *args = Py_BuildValue("NN", ob1, ob2);
	if (args==NULL){
		gui_print_error();
		return FALSE;
		}

	LRESULT rc = 0;
	BOOL bPassOn = FALSE;
	PyObject *obOther;
	PyObject *result = Python_do_callback(method, args);
	if (result==NULL)
		PyErr_Warn(PyExc_Warning, "Exception in OnNotify() handler");
	else if (result==Py_None)	// allow for None "dont pass on", else result to windows
		bPassOn = TRUE;
	else if PyTuple_Check(result){
		// Result should be a tuple of the LRESULT and a tuple to fill the appropriate
		//	struct for this particular message
		if (PyArg_ParseTuple(result, "O&O", PyWinLong_AsVoidPtr, &rc, &obOther))
			PyNotifyParseExtraTuple( pHdr, obOther, fmt);
		if (PyErr_Occurred()){
			gui_print_error();
			PyErr_Format(ui_module_error, "Error parsing OnNotify() extra return info for code %d, fmt='%s'", code, fmt);
			gui_print_error();
			}
		}
	// Otherwise result is just the LRESULT, which can be anything that fits in pointer size
	else if (!PyWinObject_AsPARAM(result, (LPARAM *)&rc)){
		gui_print_error();
		PyErr_SetString(ui_module_error, "OnNotify did not return an LRESULT, or a tuple of (LRESULT, notify info tuple)");
		gui_print_error();
		rc = 0;
		}
	Py_XDECREF(result);
	*pResult = rc;
	return !bPassOn;
}
