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

#include <map>
#include <cstdio>

#include "helpers.h"

#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::wofstream 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

#ifndef MAXINT
#define MAXUINT ((UINT)~((UINT)0))
#define MAXINT ((INT(MAXUINT >> 1))
#endif

static MSG lastmsg;

static bool keysPressed[256];

inline bool keyAlreadyPressed(const MSG & msg) {
	return (msg.message==WM_KEYDOWN || msg.message==WM_SYSKEYDOWN) && keysPressed[msg.wParam];
}

inline bool keyNotPressed(const MSG & msg) {
	return (msg.message==WM_KEYUP || msg.message==WM_SYSKEYUP) && !keysPressed[msg.wParam];
}


HANDLE mutex;



USERLOG_API void __cdecl InitUsagelog() {

	mutex = CreateMutex(NULL, FALSE, TEXT("USAGELOGMUTEX"));
	if( mutex == NULL ) {
		MessageBox(0, L"MutexFailure", L"MutexFailure", 0);
	}
	for(int i=0; i<256; i++) {
		keysPressed[i] = false;
	}

#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;
	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;
	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;
	// simple heuristic to eliminate duplicate messages
	if( !( MessageEquals(*msg, lastmsg) || keyAlreadyPressed(*msg) || keyNotPressed(*msg) ) ) {
		lastmsg = *msg;
		if( msg->message==WM_KEYDOWN || msg->message==WM_SYSKEYDOWN ) {
			keysPressed[msg->wParam] = true;
		}
		if( msg->message==WM_KEYUP || msg->message==WM_SYSKEYUP ) {
			keysPressed[msg->wParam] = false;
		}

		// message filter:
		// all messages that are defined in this switch statement will be part of the log
		switch(msg->message) {
			// messages for monitoring GUI state
			case WM_CREATE:
			case WM_DESTROY:
			case WM_SETTEXT:

			// mouse messages
			case WM_LBUTTONDOWN:
			case WM_LBUTTONUP:
			case WM_LBUTTONDBLCLK:
			case WM_RBUTTONDOWN:
			case WM_RBUTTONUP:
			case WM_RBUTTONDBLCLK:
			case WM_MBUTTONDOWN:
			case WM_MBUTTONUP:
			case WM_MBUTTONDBLCLK:
			case WM_XBUTTONDOWN:
			case WM_XBUTTONUP:
			case WM_XBUTTONDBLCLK:
			case WM_NCLBUTTONDOWN:
			case WM_NCLBUTTONUP:
			case WM_NCLBUTTONDBLCLK:
			case WM_NCRBUTTONDOWN:
			case WM_NCRBUTTONUP:
			case WM_NCRBUTTONDBLCLK:
			case WM_NCMBUTTONDOWN:
			case WM_NCMBUTTONUP:
			case WM_NCMBUTTONDBLCLK:
			case WM_NCXBUTTONDOWN:
			case WM_NCXBUTTONUP:
			case WM_NCXBUTTONDBLCLK:

			// keyboard messages
			case WM_KEYDOWN:
			case WM_KEYUP:
			case WM_SYSKEYDOWN:
			case WM_SYSKEYUP:

			// internal messages usefull for replay/matching of events
			case WM_KILLFOCUS:
			case WM_SETFOCUS:
			case WM_COMMAND:
			case WM_SYSCOMMAND:
			case WM_HSCROLL:
			case WM_VSCROLL:
			case WM_MENUSELECT:
			case WM_USER:
				waitResult = WaitForSingleObject(mutex, 1000);
				if( waitResult==WAIT_OBJECT_0 ) {
					WriteLogentryWString(msg, nFrom);
					ReleaseMutex(mutex);
				}
#ifdef __TIMING__
				msgCounter++;
				msgCounterChange = true;
#endif // __TIMING__
				break;
			default:
				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__
}


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;
	int pointx = -1;
	int pointy = -1;

	// 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_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 ||
		msg->message==WM_NCLBUTTONUP || msg->message==WM_NCRBUTTONUP || msg->message==WM_NCMBUTTONUP ||
		msg->message==WM_NCLBUTTONDOWN || msg->message==WM_NCRBUTTONDOWN || msg->message==WM_NCMBUTTONDOWN ||
		msg->message==WM_NCLBUTTONDBLCLK || msg->message==WM_NCRBUTTONDBLCLK || msg->message==WM_NCMBUTTONDBLCLK) {
		
		RECT r;
		DWORD dCursorPos = GetMessagePos();
		GetWindowRect(msg->hwnd, &r);
		//dont log absolut coordinates but relativ ones so you can use them in rule "LeftClickRelativ"
		pointx = GET_X_LPARAM(dCursorPos)-r.left;
		pointy = GET_Y_LPARAM(dCursorPos)-r.top;
	}

	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 ||
		msg->message==WM_NCLBUTTONUP || msg->message==WM_NCRBUTTONUP || msg->message==WM_NCMBUTTONUP ||
		msg->message==WM_NCLBUTTONDOWN || msg->message==WM_NCRBUTTONDOWN || msg->message==WM_NCMBUTTONDOWN ||
		msg->message==WM_NCLBUTTONDBLCLK || msg->message==WM_NCRBUTTONDBLCLK || msg->message==WM_NCMBUTTONDBLCLK) {
		
		
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"point.x\" value=\"%i\"/>", pointx);
		pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"point.y\" value=\"%i\"/>", pointy);

		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);
			}


			retVal = GetClassName(msg->hwnd, buffer, 128);
			if( retVal >= -1  && retVal < MAXINT && (!lstrcmpi(buffer, L"ComboLBox")||!lstrcmpi(buffer, L"ComboBox")) )
			{
				scrollPos = (int)SendMessage(msg->hwnd, CB_GETCURSEL, 0, 0);
				pos += swprintf_s(msgBuffer+pos,bufsize-pos,L"<param name=\"scrollPos\" value=\"%i\"/>", scrollPos);
			}

			//check the TabControl selection, store it in "scrollPos"
			//no selection = -1
			retVal = GetClassName(msg->hwnd, buffer, 128);
			if( retVal >= -1  && retVal < MAXINT && !lstrcmpi(buffer, L"SysTabControl32") )
			{
				scrollPos = (int)SendMessage(msg->hwnd, (UINT)4875, 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 << LOGPREFIX << msgBuffer << std::endl;
#endif
	delete messageStr;
	delete newWindowText;
	delete windowName;
	delete windowClass;
	delete msgBuffer;
}
