#include "stdafx.h"
#include "userlog.h"

#include <map>
#include <cstdio>

#ifdef __USING_MTRACE__
#define OS_WIN32
#include "msl/merror/inc/trace.h"

#ifdef __ENCODE_BASE64__
#include  "encode.h"
#endif

const static int traceLevel = 3;
static int level;
#endif

#ifdef __USING_COSTUMLOG__
static std::ofstream logfile;
#endif

#ifdef __TIMING__
static unsigned long long timing = 0;
static unsigned long long msgCounter = 0;
static unsigned long long totalMsgCounter = 0;
static bool msgCounterChange = false;
#endif

static MSG lastmsg;

HANDLE mutex;



USERLOG_API void __cdecl InitUsagelog() {

	mutex = CreateMutex(NULL, FALSE, TEXT("USAGELOGMUTEX"));
	if( mutex == NULL ) {
		MessageBox(0, L"MutexFailure", L"MutexFailure", 0);
	}

#ifdef __USING_COSTUMLOG__
	InitLogfile();
#endif
	InitHookdata();
	InitHooks();
#ifdef __USING_MTRACE__
	MTrace_AddToLevel(traceLevel,"%s<session>", LOGPREFIX);
#endif
#ifdef __USING_COSTUMLOG__
	logfile << LOGPREFIX << "<session>" << std::endl;
#endif
}

USERLOG_API void __cdecl ReleaseUsagelog() {
	ReleaseHooks();
#ifdef __USING_MTRACE__
#ifdef __TIMING__
	char * mtraceBuffer = new char[128];
	sprintf(mtraceBuffer, "ul timing: %llu \t\t %llu \t\t %llu", timing, msgCounter, totalMsgCounter);
	MTrace_AddToLevel(traceLevel,mtraceBuffer);
	delete mtraceBuffer;
	msgCounterChange = false;
#endif
	MTrace_AddToLevel(traceLevel,"%s</session>", LOGPREFIX);
#endif
#ifdef __USING_COSTUMLOG__
	logfile << LOGPREFIX << "</session>" << std::endl;
	CloseLogfile();
#endif
}

#ifdef __USING_COSTUMLOG__
void InitLogfile() {
	logfile.open(LOGFILE, std::ios_base::app);
	if( logfile.fail() ) {
		MessageBox(0, L"Logfile could not be opend", L"Error", MB_OK);
	}
}
#endif

#ifdef __USING_COSTUMLOG__
void CloseLogfile() {
	logfile.close();
}
#endif

void InitHookdata() {
	myhookdata[CALLWNDHOOKID].nType = WH_CALLWNDPROC;
	myhookdata[CALLWNDHOOKID].hkproc = CallWndProc;
	myhookdata[GETMSGHOOKID].nType = WH_GETMESSAGE;
	myhookdata[GETMSGHOOKID].hkproc = GetMsgProc;
}

void InitHooks() {
	for( int i=0 ; i<NUMHOOKS ; i++ ) {
		myhookdata[i].hookhandle = SetWindowsHookEx(myhookdata[i].nType, myhookdata[i].hkproc, (HINSTANCE) NULL, GetCurrentThreadId());
		if( myhookdata[i].hookhandle!=NULL ) {
			myhookdata[i].active = true;
		} else {
			myhookdata[i].active = false;
		}
	}
}

void ReleaseHooks() {
	int counter = 0;
	for( int i=0 ; i<NUMHOOKS ; i++ ) {
		if( UnhookWindowsHookEx(myhookdata[i].hookhandle) ) counter++;
	}
}


LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
	
	PCWPSTRUCT cwpMsg = (PCWPSTRUCT) lParam;
	// Create a MSG struct from the cwpMsg struct
	// The missing parameters are filled with dummy values
	MSG msg;
	msg.hwnd = cwpMsg->hwnd;
	msg.message = cwpMsg->message;
	msg.lParam = cwpMsg->lParam;
	msg.wParam = cwpMsg->wParam;
	msg.pt.x = -1;
	msg.pt.y = -1;
	msg.time = -1;
	if( !MessageEquals(lastmsg, msg) ) {
		lastmsg = msg;
		HookProc(CALLWNDHOOKID, nCode, &msg);
	}

	return CallNextHookEx(myhookdata[CALLWNDHOOKID].hookhandle, nCode, wParam, lParam);
}

LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) {
	PMSG msg = (PMSG) lParam;
	if( !MessageEquals(*msg, lastmsg)) {
		lastmsg = *msg;
		HookProc(GETMSGHOOKID,nCode, msg);
	}
	return CallNextHookEx(myhookdata[GETMSGHOOKID].hookhandle, nCode, wParam, lParam);
}

void HookProc(int nFrom, int nCode, PMSG msg) {
#ifdef __TIMING__
	SYSTEMTIME systemTime;
	GetSystemTime( &systemTime );
	int startTime = systemTime.wMilliseconds+systemTime.wSecond*1000;
#endif

	DWORD waitResult;

	// inverse filter: defined messages will be filtered, all else just passes through
	// may be replaced with a lookup-table to improve perfomance
	// upon completion of the rules, i.e., when it is clear which messages are required,
	// this should be changed to a "normal" filter.
	switch(msg->message) {
		case WM_NULL:
		case WM_MOVE:
		case WM_SIZE:
		case WM_GETTEXT:
		case WM_GETTEXTLENGTH:
		case WM_PAINT:
		case WM_ERASEBKGND:
		case WM_SHOWWINDOW:
		case WM_CANCELMODE:
		case WM_SETCURSOR:
		case WM_GETMINMAXINFO:
		case WM_GETFONT:
		case WM_WINDOWPOSCHANGING:
		case WM_WINDOWPOSCHANGED:
		case WM_NOTIFY:
		case WM_STYLECHANGING:
		case WM_STYLECHANGED:
		case WM_GETICON:
		case WM_NCCREATE:
		case WM_NCDESTROY:
		case WM_NCCALCSIZE:
		case WM_NCHITTEST:
		case WM_NCPAINT:
		case WM_GETDLGCODE:
		case 0x0090: // WM_UAHDESTROYWINDOW
		case 0x0091: // WM_UAHDRAWMENU
		case 0x0092: // WM_UADRAWMENUITEM
		case 0x0093: // WM_UAHINITMENU
		case 0x0094: // WM_UAHMEASUREMENUITEM
		case 0x0095: // WM_UAHNCPAINTMENUPOPUP
		case WM_NCMOUSEMOVE:
		case WM_TIMER:
		case WM_ENTERIDLE:
		case WM_CTLCOLORMSGBOX:
		case WM_CTLCOLOREDIT:
		case WM_CTLCOLORLISTBOX:
		case WM_CTLCOLORBTN:
		case WM_CTLCOLORDLG:
		case WM_CTLCOLORSCROLLBAR:
		case WM_CTLCOLORSTATIC:
		case WM_MOUSEMOVE:
		case WM_PARENTNOTIFY:
		case WM_MDIGETACTIVE:
		case WM_IME_NOTIFY:
		case WM_IME_SETCONTEXT:
		case WM_AFXFIRST:
		case WM_AFXFIRST+1:
		case WM_AFXFIRST+2:
		case WM_AFXFIRST+3:
		case WM_AFXFIRST+4:
		case WM_AFXFIRST+5:
		case WM_AFXFIRST+6:
		case WM_AFXFIRST+7:
		case WM_AFXFIRST+8:
		case WM_AFXFIRST+9:
		case WM_AFXFIRST+10:
		case WM_AFXFIRST+11:
		case WM_AFXFIRST+12:
		case WM_AFXFIRST+13:
		case WM_AFXFIRST+14:
		case WM_AFXFIRST+15:
		case WM_AFXFIRST+16:
		case WM_AFXFIRST+17:
		case WM_AFXFIRST+18:
		case WM_AFXFIRST+19:
		case WM_AFXFIRST+20:
		case WM_AFXFIRST+21:
		case WM_AFXFIRST+22:
		case WM_AFXFIRST+23:
		case WM_AFXFIRST+24:
		case WM_AFXFIRST+25:
		case WM_AFXFIRST+26:
		case WM_AFXFIRST+27:
		case WM_AFXFIRST+28:
		case WM_AFXFIRST+29:
		case WM_AFXFIRST+30:
		case WM_AFXLAST:
		case 1025:
		case 1031:
		case 1142:
		case 2024:
		case 4100:
		case 4101:
		case 4103:
		case 4352:
		case 4362:
		case 4363:
		case 4364:
		case 4365:
		case 4372:
		case 4613:
			break;
		default:
			// exclude messages 0xC000-0xFFFF
			if( !(msg->message>=0xC000 && msg->message<=0xFFFF) ) {
				waitResult = WaitForSingleObject(mutex, 1000);
				if( waitResult==WAIT_OBJECT_0 ) {
					WriteLogentryWString(msg, nFrom);
					ReleaseMutex(mutex);
				}

#ifdef __TIMING__
				msgCounter++;
				msgCounterChange = true;
#endif // __TIMING__
			}
			break;
	}
#ifdef __TIMING__
	GetSystemTime( &systemTime );
	timing += systemTime.wMilliseconds+systemTime.wSecond*1000-startTime;
	totalMsgCounter++;
	if( msgCounterChange && msgCounter%100==0 ) {
#ifdef __USING_MTRACE__
		char * mtraceBuffer = new char[128];
		sprintf(mtraceBuffer, "ul timing: %llu \t\t %llu \t\t %llu", timing, msgCounter, totalMsgCounter);
		MTrace_AddToLevel(traceLevel,mtraceBuffer);
		delete mtraceBuffer;
		msgCounterChange = false;
#endif // __USING_MTRACE__
	}
#endif // __TIMING__
}


///////////////////////////////////////////////////////////
// 2 Byte character functions
///////////////////////////////////////////////////////////

bool MessageEquals(const MSG & msg1, const MSG & msg2) {
	bool retVal = false;
	if( (msg1.time==-1 && msg1.pt.x==-1 && msg1.pt.y==-1) || (msg2.time==-1 && msg2.pt.x==-1 && msg2.pt.y==-1) ) {
		retVal = msg1.hwnd==msg2.hwnd && msg1.message==msg2.message && msg1.lParam==msg2.lParam &&
			msg1.wParam==msg2.wParam;
	} else {
		retVal = msg1.hwnd==msg2.hwnd && msg1.message==msg2.message && msg1.lParam==msg2.lParam &&
			msg1.wParam==msg2.wParam && msg1.time==msg2.time && msg1.pt.x==msg2.pt.x && msg1.pt.y==msg2.pt.y;
	}
	return retVal;
}

int replaceWithXmlEntitiesWString(const wchar_t * source, wchar_t ** target, size_t sourceLength) {
	size_t j=0;
	size_t extraLength = 0;
	size_t bufsize = 300;
	wchar_t * tmpTarget = new wchar_t[sourceLength+bufsize];
	size_t i;
	for( i=0; i<sourceLength && j<sourceLength+bufsize-5; i++ ) {
		switch (source[i]) {
			case L'&':
				tmpTarget[j] = L'&';
				tmpTarget[j+1]=L'a';
				tmpTarget[j+2]=L'm';
				tmpTarget[j+3]=L'p';
				tmpTarget[j+4]=L';';
				j += 5;
				extraLength += 4;
				break;
			case L'<':
				tmpTarget[j] = L'&';
				tmpTarget[j+1]=L'l';
				tmpTarget[j+2]=L't';
				tmpTarget[j+3]=L';';
				j += 4;
				extraLength += 3;
				break;
			case L'>':
				tmpTarget[j] = L'&';
				tmpTarget[j+1]=L'g';
				tmpTarget[j+2]=L't';
				tmpTarget[j+3]=L';';
				j += 4;
				extraLength += 3;
				break;
			case L'\"':
				tmpTarget[j] = L'&';
				tmpTarget[j+1]=L'q';
				tmpTarget[j+2]=L'u';
				tmpTarget[j+3]=L'o';
				tmpTarget[j+4]=L't';
				tmpTarget[j+5]=L';';
				j += 6;
				extraLength += 5;
				break;
			case L'\'':
				tmpTarget[j] = L'&';
				tmpTarget[j+1]=L'a';
				tmpTarget[j+2]=L'p';
				tmpTarget[j+3]=L'o';
				tmpTarget[j+4]=L's';
				tmpTarget[j+5]=L';';
				j += 6;
				extraLength += 5;
				break;
			case L'%':
				tmpTarget[j] = L'\\';
				tmpTarget[j+1] = L'%';
				j += 2;
				extraLength += 1;
				break;
			default:
				tmpTarget[j] = source[i];
				j++;
		}
	}
	*target = new wchar_t[j+1];
	memcpy(*target,tmpTarget,j*sizeof(wchar_t));
	(*target)[j] = '\0';
	return j;
}


void WriteLogentryWString(PMSG msg, int nFrom) {
	wchar_t * messageStr = NULL;
	wchar_t buffer[128];
	wchar_t * newWindowText = NULL;
	wchar_t * windowName = NULL;
	unsigned int command = 0;
	int sourceType = -1;
	HWND source = NULL;
	HWND parentHandle = NULL;
	wchar_t * windowClass = NULL;
	bool isPopup = false;
	bool isModal = false;
	bool htMenu = false;
	HWND menuHandle = NULL;
	int scrollPos = -1;
	unsigned int scrollType = 0;
	HWND scrollBarHandle = NULL;
	int retVal = 0;

	// debug vars
	
	retVal = GetWindowText(msg->hwnd, buffer, 128);
	if( retVal > 0  && retVal<MAXINT ) {
		/*
		 * In one case at the start of MarWin, when a resource with DlgId 1049 is created,
		 * GetWindowText returns MAXINT. This behaviour is undocumented.
		 */
		replaceWithXmlEntitiesWString(buffer, &windowName, retVal+1);
	}
	int windowResourceId = GetDlgCtrlID(msg->hwnd);
	if( windowResourceId<0 ) {
		windowResourceId = 0;
	}

	//**************************************
	// Message specific variables
	//**************************************

	if( msg->message==WM_COMMAND ) {
		command = LOWORD(msg->wParam);
		sourceType = HIWORD(msg->wParam);
		source = (HWND) msg->lParam;
	}
	if( msg->message==WM_SYSCOMMAND ) {
		command = LOWORD(msg->wParam);
	}

	if( msg->message==WM_CREATE ) {
		parentHandle = GetParent(msg->hwnd);
		
		retVal = GetClassName(msg->hwnd, buffer, 128);
		if( retVal > 0  && retVal<MAXINT ) {
			replaceWithXmlEntitiesWString(buffer, &windowClass, retVal+1);
		}

		// check is dialog is modal
		// this check is not always accurate, but the best that I could come up with
		isModal = IsWindowEnabled(parentHandle)==false;
	}

	if( msg->message==WM_SETTEXT ) {
		wchar_t * newWindowTextBuffer = (wchar_t*)msg->lParam;
		if( newWindowTextBuffer!=NULL ) {
			size_t len = wcslen(newWindowTextBuffer);
			replaceWithXmlEntitiesWString(newWindowTextBuffer, &newWindowText, len+1);
		}
	}

	if( msg->message==WM_NCLBUTTONDOWN ) {
		if( msg->wParam==HTMENU ) {
			htMenu = true;
		}
	}
	
	if( msg->message==WM_INITMENU ) {
		menuHandle = (HWND) msg->wParam;
	}

	if( msg->message==WM_HSCROLL || msg->message==WM_VSCROLL ) {
		scrollType = LOWORD(msg->wParam);
		scrollPos = HIWORD(msg->wParam);
		scrollBarHandle = (HWND) msg->lParam;
	}

	/***************************************/
	// put debugging variables here
	/***************************************/


	/***************************************
	 * Printing part
	 ***************************************/
	
	size_t bufsize = 2048;
	wchar_t * msgBuffer = new wchar_t[bufsize];
	size_t pos = 0;
	//pos += swprintf_s(msgBuffer+pos, bufsize-pos,LOGPREFIXWSTRING);

	
	// print msg information
	pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<msg type=\"%i\">",msg->message);
	pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"WPARAM\" value=\"%i\"/>", msg->wParam);
	pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"LPARAM\" value=\"%i\"/>", msg->lParam);

	pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"window.hwnd\" value=\"%i\"/>", msg->hwnd);
	if( msg->message==WM_COMMAND ) {
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"command\" value=\"%i\"/>",command);
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"sourceType\" value=\"%i\"/>",sourceType);
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"source\" value=\"%i\"/>",source);
	}
	if( msg->message==WM_SYSCOMMAND ) {
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"command\" value=\"%i\"/>", command);
	}

	if( msg->message==WM_LBUTTONUP || msg->message==WM_RBUTTONUP || msg->message==WM_MBUTTONUP ||
		msg->message==WM_LBUTTONDOWN || msg->message==WM_RBUTTONDOWN || msg->message==WM_MBUTTONDOWN ||
		msg->message==WM_LBUTTONDBLCLK || msg->message==WM_RBUTTONDBLCLK || msg->message==WM_MBUTTONDBLCLK) {
		if( msg->time>-1 ) {
			pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"point.x\" value=\"%i\"/>", msg->pt.x);
			pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"point.x\" value=\"%i\"/>", msg->pt.y);
		}

		if(msg->message == WM_LBUTTONUP)
		{
			//check the listBox selection, store it in "scrollPos"
			//no selection = -1
			//this is only working for listBoxes with style 'selection = single'
			retVal = GetClassName(msg->hwnd, buffer, 128);
			if( retVal >= -1  && retVal < MAXINT && !lstrcmpi(buffer, L"ListBox") )
			{
				scrollPos = (int)SendMessage(msg->hwnd, LB_GETCURSEL, 0, 0);
				pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"scrollPos\" value=\"%i\"/>", scrollPos);
			}
		}
	}
	if( msg->message==WM_MOUSEACTIVATE ) {
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"toplevelwindow.hwnd\" value=\"%i\"/>", (HWND) msg->wParam);
	}
	if( msg->message==WM_KEYUP || msg->message==WM_KEYDOWN || msg->message==WM_SYSKEYUP || msg->message==WM_SYSKEYDOWN ) {
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"key\" value=\"%i\"/>", LOWORD(msg->wParam));
	}
	if( msg->message==WM_SETTEXT ) {
		if( newWindowText!=NULL ) {
			pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"window.newText\" value=\"%s\"/>", newWindowText);
		}
	}
	
	if( msg->message==WM_NCLBUTTONDOWN && htMenu ) {
		pos += swprintf_s(msgBuffer+pos, bufsize-pos,L"<param name=\"isMenu\" value=\"true\"/>");
	}

	if( msg->message==WM_INITMENU ) {
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"menu.hwnd\" value=\"%i\"/>", menuHandle);
	}

	if( msg->message==WM_CREATE ) {
		// print window information
		if( windowName!=NULL ) {
			pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"window.name\" value=\"%s\"/>", windowName);
		}
		if( windowResourceId>0 ) {
			pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"window.resourceId\" value=\"%i\"/>", windowResourceId);
		}
		if( msg->message==WM_CREATE ) {
			if( parentHandle!=NULL ) {
				pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"window.parent.hwnd\" value=\"%i\"/>", parentHandle);
			}
			if( windowClass!=NULL ) {
				pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"window.class\" value=\"%s\"/>", windowClass);
			}
			if( isModal ) {
				pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"window.ismodal\" value=\"true\"/>");
			}

		}
	}
	if( msg->message==WM_HSCROLL || msg->message==WM_VSCROLL ) {
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"scrollType\" value=\"%i\"/>", scrollType);
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"scrollPos\" value=\"%i\"/>", scrollPos);
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"scrollBarHandle\" value=\"%i\"/>", scrollBarHandle);
	}

	if( msg->time!=-1 ) {
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"time\" value=\"%i\"/>", msg->time);
	}
	
	/***************************************/
	// put debugging and experimental output stuff here
	/***************************************/

#ifdef __INCLUDEHOOKINFO__
	pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"hook\" value=\"%i\"/>", nFrom);
#endif
	

	pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"</msg>", msg->hwnd);
#ifdef __USING_MTRACE__
#ifdef __ENCODE_BASE64__
	size_t arraySize = (pos+1)*2;
	size_t encodingSize = arraySize*2;
	char * base64Buffer = new char[encodingSize];

	base64::encoder enc;
	retVal = enc.encode((char*)msgBuffer, arraySize, base64Buffer);
	base64Buffer[retVal] = '\0';

	char * mtraceBuffer = new char[retVal+30];
	sprintf_s(mtraceBuffer,retVal+29,"%s%s", LOGPREFIX, base64Buffer);
	delete base64Buffer;
#else
	char * mtraceBuffer = new char[pos+1];
	size_t numConverted;
	wcstombs_s(&numConverted,mtraceBuffer, pos+1, msgBuffer, pos);
#endif // __ENCODE_BASE64__
	MTrace_AddToLevel(traceLevel,mtraceBuffer);
	delete mtraceBuffer;
#endif // __USING_MTRACE__
#ifdef __USING_COSTUMLOG__
	SYSTEMTIME currentTime;
	GetSystemTime(&currentTime);
	logfile << currentTime.wDay << "." << currentTime.wMonth << "." << currentTime.wYear << " ";
	logfile << currentTime.wHour << ":" << currentTime.wMinute << ":" << currentTime.wSecond << ":";
	logfile << currentTime.wMilliseconds << "\t";
	logfile << buffer << std::endl;
#endif
	delete messageStr;
	delete newWindowText;
	delete windowName;
	delete windowClass;
	delete msgBuffer;
}
