#define _WIN32_WINNT 0x0600
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_DEPRECATE 1

#include <windows.h>
#include <stdio.h>
#include <stdarg.h>
#include "dxwnd.h"
#include "dxwcore.hpp"
#include "dxhelper.h"

extern void SuppressIMEWindow();
extern void RecoverScreenMode();
extern void dx_FullScreenToggle(HWND);
extern void dx_DesktopToggle(HWND, BOOL);
extern BOOL JoyProcessMouseWheelMessage(WPARAM, LPARAM);

static void dx_ToggleLogging()
{
	// toggle LOGGING
	if(dxw.dwTFlags & OUTTRACE){
		OutTraceDW("Toggle logging OFF\n");
		dxw.dwTFlags &= ~OUTTRACE;
	}
	else {
		dxw.dwTFlags |= OUTTRACE;
		OutTraceDW("Toggle logging ON\n");
	}
	GetHookInfo()->isLogging=(dxw.dwTFlags & OUTTRACE);
}

LRESULT CALLBACK extDialogWindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
	static int i=0;
	static WINDOWPOS *wp;
	WNDPROC pWindowProc;
	LRESULT res;
	static int t = -1;
	static int iRecursion = 0;

	if(iRecursion) {
		iRecursion=0;
		return NULL;
	}
	iRecursion++;

	if (t == -1)
		t = (*pGetTickCount)();
	int tn = (*pGetTickCount)();

	OutTraceW("DEBUG: DialogWinMsg hwnd=%x msg=[0x%x]%s(%x,%x)\n", hwnd, message, ExplainWinMessage(message), wparam, lparam);

	// optimization: don't invalidate too often!
	// 200mSec seems a good compromise.
	if (tn-t > 200) {
		t=tn;
		(*pInvalidateRect)(hwnd, NULL, TRUE);
	}

	pWindowProc=dxwws.GetProc(hwnd);
	if(pWindowProc) {
		res =(*pCallWindowProcA)(pWindowProc, hwnd, message, wparam, lparam);
	}
	else {
		char *sMsg="ASSERT: DialogWinMsg pWindowProc=NULL !!!\n";
		OutTraceDW(sMsg);
		if (IsAssertEnabled) MessageBox(0, sMsg, "WindowProc", MB_OK | MB_ICONEXCLAMATION);
		res = NULL;
	}
	iRecursion=0;
	return res;
}

LRESULT CALLBACK extChildWindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
	static int i=0;
	static WINDOWPOS *wp;
	WNDPROC pWindowProc;

	OutTraceW("DEBUG: ChildWinMsg hwnd=%x msg=[0x%x]%s(%x,%x)\n", hwnd, message, ExplainWinMessage(message), wparam, lparam);

	if(dxw.Windowize){
		switch(message){
		// Cybermercs: it seems that all game menus are conveniently handled by the WindowProc routine,
		// while the action screen get messages processed by the ChildWindowProc, that needs some different
		// setting ..........
		// Beware: Cybermercs handles some static info about cursor position handling, so that if you resize
		// a menu it doesn't work correctly until you don't change screen.
		case WM_MOUSEMOVE:
		case WM_MOUSEWHEEL:
		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:
			// if(dxw.dwFlags1 & MODIFYMOUSE){ // mouse processing  
			//if((dxw.dwFlags1 & MODIFYMOUSE) && !(dxw.dwFlags6 & NOMOUSEPROC)){ // mouse processing  
			if((dxw.dwFlags1 & MODIFYMOUSE) && !(dxw.dwFlags1 & MESSAGEPROC)){ // mouse processing  
				POINT prev, curr;
				// scale mouse coordinates
				prev.x = LOWORD(lparam);
				prev.y = HIWORD(lparam);
				curr = prev;
				if(message == WM_MOUSEWHEEL){ // v2.02.33 mousewheel fix
					POINT upleft={0,0};
					(*pClientToScreen)(dxw.GethWnd(), &upleft);
					curr = dxw.SubCoordinates(curr, upleft);
				}
				//OutTraceC("ChildWindowProc: hwnd=%x pos XY prev=(%d,%d)\n", hwnd, prev.x, prev.y);
				curr=dxw.FixCursorPos(curr); // Warn! the correction must refer to the main window hWnd, not the current hwnd one !!!
				lparam = MAKELPARAM(curr.x, curr.y); 
				OutTraceC("ChildWindowProc: hwnd=%x pos XY=(%d,%d)->(%d,%d)\n", hwnd, prev.x, prev.y, curr.x, curr.y);
			}
			break;	
		default:
			break;
		}
	}

	pWindowProc=dxwws.GetProc(hwnd);
	
	// v2.02.82: use CallWindowProc that handles WinProc handles
	if(pWindowProc) return(*pCallWindowProcA)(pWindowProc, hwnd, message, wparam, lparam);
	// should never get here ....
	OutTraceDW("ChildWindowProc: no WndProc for CHILD hwnd=%x\n", hwnd);
	return DefWindowProc(hwnd, message, wparam, lparam);
}

static void dx_UpdatePositionLock(HWND hwnd)
{
	RECT rect;
	POINT p={0,0};
	(*pGetClientRect)(hwnd,&rect);
	(*pClientToScreen)(hwnd,&p);
	dxw.dwFlags1 |= LOCKWINPOS;
	OutTraceDW("Toggle position lock ON\n");
	dxw.InitWindowPos(p.x, p.y, rect.right-rect.left, rect.bottom-rect.top);
}

static void dx_TogglePositionLock(HWND hwnd)
{
	// toggle position locking
	if(dxw.dwFlags1 & LOCKWINPOS){
		// unlock
		OutTraceDW("Toggle position lock OFF\n");
		dxw.dwFlags1 &= ~LOCKWINPOS;
	}
	else {
		OutTraceDW("Toggle position lock ON\n");
		dxw.dwFlags1 |= LOCKWINPOS;
		dx_UpdatePositionLock(hwnd);
	}
}

static void dx_ToggleFPS()
{
	if(dxw.dwFlags2 & SHOWFPS){
		dxw.dwFlags2 &= ~SHOWFPS;
		OutTrace("ToggleFPS: SHOWFPS mode OFF\n");
	}
	else {
		dxw.dwFlags2 |= SHOWFPS;
		OutTrace("ToggleFPS: SHOWFPS mode ON\n");
	}
}

static void dx_Cornerize(HWND hwnd)
{
	static BOOL bCornerized = FALSE;
	static RECT WinRect = {0, 0, 0, 0};
	static DWORD OldStyle, OldExtStyle;

	if (bCornerized){ 	// toggle ....
		OutTraceDW("DxWnd: exiting corner mode\n");
		(*pSetWindowLong)(hwnd, GWL_STYLE, OldStyle);
		(*pSetWindowLong)(hwnd, GWL_EXSTYLE, OldExtStyle);
		(*pMoveWindow)(hwnd, WinRect.left, WinRect.top, WinRect.right, WinRect.bottom, TRUE);
		memset(&WinRect, 0, sizeof(WinRect));
	}
	else {
		OutTraceDW("DxWnd: entering corner mode\n");
		(*pGetWindowRect)(hwnd, &WinRect);
		OldStyle = (*pGetWindowLong)(hwnd, GWL_STYLE);
		OldExtStyle = (*pGetWindowLong)(hwnd, GWL_EXSTYLE);
		(*pSetWindowLong)(hwnd, GWL_STYLE, WS_VISIBLE|WS_CLIPSIBLINGS|WS_OVERLAPPED);
		(*pSetWindowLong)(hwnd, GWL_EXSTYLE, 0);
		(*pMoveWindow)(hwnd, 0, 0, dxw.GetScreenWidth(), dxw.GetScreenHeight(), TRUE);
	}
	bCornerized = !bCornerized; // switch toggle
	(*pUpdateWindow)(hwnd);
	dxw.ScreenRefresh();
}

LRESULT LastCursorPos;

void SetIdlePriority(BOOL idle)
{
	OutTrace("Setting priority class to %s\n", idle ? "IDLE_PRIORITY_CLASS" : "NORMAL_PRIORITY_CLASS");
	if(!SetPriorityClass(GetCurrentProcess(), idle ? IDLE_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS))
		OutTraceE("SetPriorityClass ERROR: err=%d at %d\n", GetLastError(), __LINE__);
}

void ExplainMsg(char *ApiName, HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	char sPos[321];
	sPos[320]=0;
	sPos[0]=0;
	switch(Msg){
	case WM_WINDOWPOSCHANGING:
	case WM_WINDOWPOSCHANGED:
		LPWINDOWPOS wp;
		wp = (LPWINDOWPOS)lParam;
		sprintf_s(sPos, 320, " pos=(%d,%d) size=(%dx%d) flags=%x(%s)", wp->x, wp->y, wp->cx, wp->cy, wp->flags, ExplainWPFlags(wp->flags));
		break;
	case WM_MOVE:
		sprintf_s(sPos, 320, " pos=(%d,%d)", HIWORD(lParam), LOWORD(lParam));
		break;
	case WM_STYLECHANGING:
	case WM_STYLECHANGED:
		LPSTYLESTRUCT style;
		style = (LPSTYLESTRUCT)lParam;
		if(wParam == GWL_STYLE) sprintf_s(sPos, 320, "style=%x(%s)->%x(%s)", 
			style->styleOld, ExplainStyle(style->styleOld), style->styleNew, ExplainStyle(style->styleNew));
		if(wParam == GWL_EXSTYLE) sprintf_s(sPos, 320, "exstyle=%x(%s)->%x(%s)", 
			style->styleOld, ExplainExStyle(style->styleOld), style->styleNew, ExplainExStyle(style->styleNew));
		break;
	case WM_SIZE:
		static char *modes[5]={"RESTORED", "MINIMIZED", "MAXIMIZED", "MAXSHOW", "MAXHIDE"};
		sprintf_s(sPos, 320, " mode=SIZE_%s size=(%dx%d)", modes[wParam % 5], HIWORD(lParam), LOWORD(lParam));
		break;	
	}
	OutTrace("%s[%x]: WinMsg=[0x%x]%s(%x,%x) %s\n", ApiName, hwnd, Msg, ExplainWinMessage(Msg), wParam, lParam, sPos);
}

static BOOL IsWindowMovingMessage(int msg)
{
	switch(msg){
		// minimum set for win move/resize on Win10
		case WM_NCLBUTTONDOWN:
		case WM_NCLBUTTONUP:
		case WM_WINDOWPOSCHANGED:
		case WM_WINDOWPOSCHANGING:
		case WM_STYLECHANGING: 
		case WM_STYLECHANGED: 
			return TRUE;
			break;
	}
	return FALSE;
}

LRESULT CALLBACK extWindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
	POINT prev, curr;
	RECT rect;
	static int i=0;
	WNDPROC pWindowProc;
	extern void dxwFixWindowPos(char *, HWND, LPARAM);
	extern LPRECT lpClipRegion;
	static BOOL DoOnce = TRUE;
	static BOOL IsToBeLocked;
	static int LastTimeShift;
	static int SaveTimeShift;
	static BOOL TimeShiftToggle=TRUE;
	extern void DDrawScreenShot(int);

	if(DoOnce){
		DoOnce=FALSE;
		IsToBeLocked=(dxw.dwFlags1 & LOCKWINPOS);
		LastTimeShift=SaveTimeShift=dxw.TimeShift;
	}

	// v2.1.93: adjust clipping region

	if(IsTraceW) ExplainMsg("WindowProc", hwnd, message, wparam, lparam);

	if(dxw.dwFlags3 & (FILTERMESSAGES|DEFAULTMESSAGES)){
		switch(message){
		case WM_NCMOUSELEAVE: 
		case WM_NCMOUSEMOVE:
		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_MOVE:
		case WM_MOVING:
			if(dxw.dwFlags3 & FILTERMESSAGES){
				OutTraceDW("WindowProc[%x]: SUPPRESS WinMsg=[0x%x]%s(%x,%x)\n", hwnd, message, ExplainWinMessage(message), wparam, lparam);
				return 0;
			}
			else {
				OutTraceDW("WindowProc[%x]: DEFAULT WinMsg=[0x%x]%s(%x,%x)\n", hwnd, message, ExplainWinMessage(message), wparam, lparam);
				return (*pDefWindowProcA)(hwnd, message, wparam, lparam);
			}
		}
	}

	// v2.04.11: the processing of al least WM_WINPOSCHANG-ING/ED for "Man TT Superbike" must be placed
	// here, before and avoiding the call to the original callback because of the AdjustWindowRect that
	// is inside the callback and would keep the position fixed.
	if(dxw.dwFlags2 & FORCEWINRESIZE){ 
		//ExplainMsg("WindowProc", hwnd, message, wparam, lparam);
		if(IsWindowMovingMessage(message)){
			return (*pDefWindowProcA)(hwnd, message, wparam, lparam);
		}
	}

	if(LastTimeShift != dxw.TimeShift){
		extern void SetVSyncDelays(int, LPDIRECTDRAW);
		extern LPDIRECTDRAW lpPrimaryDD;
		extern int iBakBufferVersion;
		if(dxw.dwFlags4 & STRETCHTIMERS) dxw.RenewTimers();
		if(lpPrimaryDD) SetVSyncDelays(iBakBufferVersion, lpPrimaryDD);
		LastTimeShift=dxw.TimeShift;
	}

	switch(message){
	// v2.02.13: added WM_GETMINMAXINFO/WM_NCCALCSIZE interception - (see Actua Soccer 3 problems...)
	//case WM_NCDESTROY:
	//	return 0;
	case WM_GETMINMAXINFO: 
		if(dxw.dwFlags1 & LOCKWINPOS){
			extern void dxwFixMinMaxInfo(char *, HWND, LPARAM);
			dxwFixMinMaxInfo("WindowProc", hwnd, lparam);
			return 0;
		}
		break;
	case WM_NCCALCSIZE:
	case WM_NCPAINT:
		// v2.02.30: don't alter child and other windows....
		// v2.04.09: comment inconsistent with code. Added '!' to if expression ....
		if(!((dxw.dwFlags1 & LOCKWINPOS) && (hwnd == dxw.GethWnd()) && dxw.IsFullScreen())){ 
			OutTraceDW("WindowProc: %s wparam=%x\n", ExplainWinMessage(message), wparam);
			return (*pDefWindowProcA)(hwnd, message, wparam, lparam);
		}
		break;
	case WM_NCCREATE:
		if(dxw.dwFlags2 & SUPPRESSIME) SuppressIMEWindow();
		break;
	case WM_IME_SETCONTEXT:
	case WM_IME_NOTIFY:
	case WM_IME_CONTROL:
	case WM_IME_COMPOSITIONFULL:
	case WM_IME_SELECT:
	case WM_IME_CHAR:
	case WM_IME_REQUEST:
	case WM_IME_KEYDOWN:
	case WM_IME_KEYUP:
		if(dxw.dwFlags2 & SUPPRESSIME){
			OutTraceDW("WindowProc[%x]: SUPPRESS IME WinMsg=[0x%x]%s(%x,%x)\n", hwnd, message, ExplainWinMessage(message), wparam, lparam);
			return 0;
		}
		break;
	case WM_NCHITTEST:
		// which one is good ?
		if((dxw.dwFlags2 & FIXNCHITTEST) && (dxw.dwFlags1 & MODIFYMOUSE)){ // mouse processing 
		//if((dxw.dwFlags2 & FIXNCHITTEST) && (dxw.dwFlags1 & MODIFYMOUSE) && !(dxw.dwFlags1 & MESSAGEPROC)){ // mouse processing 
			POINT cursor;
			LRESULT ret;
			ret=(*pDefWindowProcA)(hwnd, message, wparam, lparam);
			if (ret==HTCLIENT) {
				cursor.x=LOWORD(lparam);
				cursor.y=HIWORD(lparam);
				dxw.FixNCHITCursorPos(&cursor);
				lparam = MAKELPARAM(cursor.x, cursor.y); 
				OutTraceC("WindowProc[%x]: fixed WM_NCHITTEST pt=(%d,%d)\n", hwnd, cursor.x, cursor.y);
			}
			else
				return ret;
		}
		break;
	case WM_ERASEBKGND:
		// v2.03.97: fix for Adrenix lost backgrounds, thanks to Riitaoja hunt!
		if(dxw.Windowize && dxw.IsRealDesktop(hwnd)){ 
			OutTraceDW("WindowProc: WM_ERASEBKGND(%x,%x) - suppressed\n", wparam, lparam);
			return 1; // 1 == OK, erased
		}
		break;
	case WM_DISPLAYCHANGE:
		if (dxw.Windowize && (dxw.dwFlags1 & LOCKWINPOS) && dxw.IsFullScreen()){
			OutTraceDW("WindowProc: prevent WM_DISPLAYCHANGE depth=%d size=(%d,%d)\n",
				wparam, LOWORD(lparam), HIWORD(lparam));
			// v2.02.43: unless EMULATESURFACE is set, lock the screen resolution only, but not the color depth!
			if(dxw.dwFlags1 & EMULATESURFACE) return 0;
			// let rparam (color depth) change, but override lparam (screen width & height.)
			lparam = MAKELPARAM((LONG)dxw.GetScreenWidth(), (LONG)dxw.GetScreenHeight());
			//return 0;
		}
		break;
	case WM_STYLECHANGING:
	case WM_STYLECHANGED:
		if(dxw.dwFlags1 & LOCKWINSTYLE) {
			OutTraceDW("WindowProc: %s - suppressed\n", message==WM_STYLECHANGING ? "WM_STYLECHANGING" : "WM_STYLECHANGED");
			return 1;
		}
		break;
	case WM_WINDOWPOSCHANGING:
	case WM_WINDOWPOSCHANGED:
		if(dxw.Windowize && dxw.IsFullScreen()){
			LPWINDOWPOS wp = (LPWINDOWPOS)lparam;
			extern HWND hControlParentWnd;
			if(dxw.dwFlags5 & NOWINPOSCHANGES){
				OutTraceDW("WindowProc: %s - suppressed\n", message==WM_WINDOWPOSCHANGED ? "WM_WINDOWPOSCHANGED" : "WM_WINDOWPOSCHANGING");
				return 0;
			}
			wp = (LPWINDOWPOS)lparam;
			dxwFixWindowPos("WindowProc", hwnd, lparam);
			OutTraceDW("WindowProc: %s fixed size=(%d,%d)\n", 
				(message == WM_WINDOWPOSCHANGED) ? "WM_WINDOWPOSCHANGED" : "WM_WINDOWPOSCHANGING", wp->cx, wp->cy);
			if(message==WM_WINDOWPOSCHANGED) {
				// try to lock main wind & control parent together
				if(dxw.IsDesktop(hwnd) && hControlParentWnd) {
					POINT fo = dxw.GetFrameOffset();
					(*pMoveWindow)(hControlParentWnd, wp->x+fo.x, wp->y+fo.y, wp->cx, wp->cy, TRUE);
				}
				// v2.03.30: in window mode, it seems that the WM_ACTIVATEAPP message is not sent to the main win.
				// this PostMessage call recovers "Thorgal" block at the end of intro movie and "Championship Manager 03 04" cursor
				if (dxw.dwFlags6 & ACTIVATEAPP){
					PostMessage(hwnd, WM_ACTIVATEAPP, 1, 0);
				}
				// v2.03.91.fx4: keep position coordinates updated!
				if(!(wp->flags & (SWP_NOMOVE|SWP_NOSIZE))) dxw.UpdateDesktopCoordinates();
			}
		}
		break;
	case WM_ENTERSIZEMOVE:
		if(IsToBeLocked){
			dxw.dwFlags1 &= ~LOCKWINPOS;
		}
		while((*pShowCursor)(1) < 0);
		if (
			((dxw.dwFlags1 & CLIPCURSOR) && !(dxw.dwFlags8 & CLIPLOCKED)) ||
			(dxw.dwFlags1 & DISABLECLIPPING)) {
			dxw.EraseClipCursor();
		}
		break;
	case WM_EXITSIZEMOVE:
		if(IsToBeLocked){
			dxw.dwFlags1 |= LOCKWINPOS;
			dx_UpdatePositionLock(hwnd);
		}
		if((dxw.dwFlags1 & HIDEHWCURSOR) && dxw.IsFullScreen()) while((*pShowCursor)(0) >= 0);
		if(dxw.dwFlags2 & SHOWHWCURSOR) while((*pShowCursor)(1) < 0);
		if(dxw.dwFlags1 & DISABLECLIPPING) extClipCursor(lpClipRegion);
		if(dxw.dwFlags2 & REFRESHONRESIZE) dxw.ScreenRefresh();
		if(dxw.dwFlags4 & HIDEDESKTOP) dxw.HideDesktop(dxw.GethWnd());
		if(dxw.dwFlags5 & CENTERTOWIN) {
			HDC thdc;
			HWND w = dxw.GethWnd();
			RECT client;
			(*pGetClientRect)(w, &client);
			thdc=(*pGDIGetDC)(w);
			if(thdc) (*pGDIBitBlt)(thdc, client.left, client.top, client.right, client.bottom, 0, 0, 0, BLACKNESS);
		}
		dxw.UpdateDesktopCoordinates();
		break;
	case WM_ACTIVATE:
		// turn DirectInput bActive flag on & off .....
		dxw.bActive = (LOWORD(wparam) == WA_ACTIVE || LOWORD(wparam) == WA_CLICKACTIVE) ? 1 : 0;
	case WM_NCACTIVATE:
		// turn DirectInput bActive flag on & off .....
		if(message == WM_NCACTIVATE) dxw.bActive = wparam;
		if(dxw.bActive) (*pSetWindowPos)(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
		if(dxw.dwFlags6 & UNACQUIRE){
			ToggleAcquiredDevices(dxw.bActive);
		}
		if(dxw.dwFlags1 & UNNOTIFY){
			DefWindowProc(hwnd, message, wparam, lparam);
			return false;
		}
		break;
	case WM_NCMOUSEMOVE:
		// Posted to a window when the cursor is moved within the nonclient area of the window. 
		// This message is posted to the window that contains the cursor. 
		// If a window has captured the mouse, this message is not posted.
		// V2.1.90: on nonclient areas the cursor is always shown.
		while((*pShowCursor)(1) < 0);
		break;
	case WM_MOUSEMOVE:
		if(dxw.Windowize){
			prev.x = LOWORD(lparam);
			prev.y = HIWORD(lparam);
			if(dxw.IsFullScreen()){
				if (dxw.dwFlags1 & HIDEHWCURSOR){
					(*pGetClientRect)(hwnd, &rect);
					if(prev.x >= 0 && prev.x < rect.right && prev.y >= 0 && prev.y < rect.bottom)
						while((*pShowCursor)(0) >= 0);
					else
						while((*pShowCursor)(1) < 0);
				}
				if (dxw.dwFlags1 & SHOWHWCURSOR){
					while((*pShowCursor)(1) < 0);
				}
			}
			if((dxw.dwFlags1 & MODIFYMOUSE) && !(dxw.dwFlags1 & MESSAGEPROC)){ // mouse processing  
				// scale mouse coordinates
				curr=dxw.FixCursorPos(prev); //v2.02.30
				lparam = MAKELPARAM(curr.x, curr.y); 
				OutTraceC("WindowProc: hwnd=%x pos XY=(%d,%d)->(%d,%d)\n", hwnd, prev.x, prev.y, curr.x, curr.y);
			}
			GetHookInfo()->CursorX=LOWORD(lparam);
			GetHookInfo()->CursorY=HIWORD(lparam);
		}
		break;	
	// fall through cases:
	case WM_MOUSEWHEEL:
		if(dxw.dwFlags6 & VIRTUALJOYSTICK) {
			if(dxw.Windowize && (dxw.dwFlags1 & CLIPCURSOR) && !dxw.IsClipCursorActive()) dxw.SetClipCursor();
			if (JoyProcessMouseWheelMessage(wparam, lparam)) return 0;	
		} // fall through
	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:
		if(dxw.Windowize){
			if((dxw.dwFlags1 & CLIPCURSOR) && !dxw.IsClipCursorActive()) {
				dxw.SetClipCursor();
			}
			if((dxw.dwFlags1 & MODIFYMOUSE) && !(dxw.dwFlags1 & MESSAGEPROC)){ // mouse processing  
				// scale mouse coordinates
				prev.x = LOWORD(lparam);
				prev.y = HIWORD(lparam);
				curr = prev;
				if(message == WM_MOUSEWHEEL){ // v2.02.33 mousewheel fix
					POINT upleft={0,0};
					(*pClientToScreen)(dxw.GethWnd(), &upleft);
					curr = dxw.SubCoordinates(curr, upleft);
				}
				curr=dxw.FixCursorPos(curr); //v2.02.30
				lparam = MAKELPARAM(curr.x, curr.y); 
				OutTraceC("WindowProc: hwnd=%x pos XY=(%d,%d)->(%d,%d)\n", hwnd, prev.x, prev.y, curr.x, curr.y);
			}
			GetHookInfo()->CursorX=LOWORD(lparam);
			GetHookInfo()->CursorY=HIWORD(lparam);
		}
		break;	
	case WM_SETFOCUS:
		OutTraceDW("WindowProc: hwnd=%x GOT FOCUS\n", hwnd);
		if(dxw.dwFlags1 & CLIPCURSOR) {
			dxw.SetClipCursor();
		}
		if (dxw.dwFlags1 & DISABLECLIPPING) extClipCursor(lpClipRegion);
		if (dxw.dwFlags8 & BACKGROUNDPRIORITY) SetIdlePriority(FALSE);
		break;
	case WM_KILLFOCUS:
		OutTraceDW("WindowProc: hwnd=%x LOST FOCUS\n", hwnd);
		if (
			((dxw.dwFlags1 & CLIPCURSOR) && !(dxw.dwFlags8 & CLIPLOCKED)) ||
			(dxw.dwFlags1 & DISABLECLIPPING)){
			dxw.EraseClipCursor();
		}
		if (dxw.dwFlags8 & BACKGROUNDPRIORITY) SetIdlePriority(TRUE);
		break;
	case WM_SYSCOMMAND:
		// v2.03.56.fix1 by FunkyFr3sh: ensure that "C&C Red Alert 2" receives the WM_SYSCOMMAND / SC_CLOSE message
		// that likely is filtered by the application logic
		// v2.03.91: from msdn - In WM_SYSCOMMAND messages, the four low-order bits of the wParam parameter are used 
		// internally by the system. To obtain the correct result when testing the value of wParam, an application 
		// must combine the value 0xFFF0 with the wParam value by using the bitwise AND operator. 
		if(dxw.Windowize && ((wparam & 0xFFF0)== SC_CLOSE) && (dxw.dwFlags6 & TERMINATEONCLOSE)) {
			LRESULT lres;
			lres = (*pDefWindowProcA)(hwnd, message, wparam, lparam);
			return lres;
		}
		if(dxw.Windowize){
			static int iLastX, iLastY, iLastW, iLastH;
			switch(wparam & 0xFFF0){
				case SC_MINIMIZE:
					dxw.IsVisible = FALSE;
					iLastX = dxw.iPosX; iLastY = dxw.iPosY;
					iLastW = dxw.iSizX; iLastH = dxw.iSizY;
					dxw.iPosX = dxw.iPosY = dxw.iSizX = dxw.iSizY = 0;
					break;
				case SC_RESTORE:
					dxw.IsVisible = TRUE;
					dxw.iPosX = iLastX; dxw.iPosY = iLastY;
					dxw.iSizX = iLastW; dxw.iSizY = iLastH;
					break;
				case SC_MAXIMIZE:
					dxw.IsVisible = TRUE;
					break;
			}
		}
		break;
	case WM_CLOSE:
		// Beware: closing main window does not always mean that the program is about to terminate!!!
		extern void gShowHideTaskBar(BOOL);
		if(dxw.dwFlags6 & CONFIRMONCLOSE){
			OutTraceDW("WindowProc: WM_CLOSE - terminating process?\n");
			if (MessageBoxA(NULL, "Do you really want to exit the game?", "DxWnd", MB_YESNO | MB_TASKMODAL) != IDYES) return FALSE;
		}			
		if(dxw.dwFlags6 & HIDETASKBAR) gShowHideTaskBar(FALSE);
		if(dxw.dwFlags3 & FORCE16BPP) RecoverScreenMode();
		if(dxw.dwFlags6 & TERMINATEONCLOSE) TerminateProcess(GetCurrentProcess(),0);
		break;
	case WM_SYSKEYDOWN:
		if ((dxw.dwFlags1 & HANDLEALTF4) && (wparam == VK_F4)) {
			OutTraceDW("WindowProc: WM_SYSKEYDOWN(ALT-F4) - terminating process\n");
			TerminateProcess(GetCurrentProcess(),0);
		}
		// fall through
	case WM_KEYDOWN:
		if(!(dxw.dwFlags4 & ENABLEHOTKEYS)) break;
		OutTraceW("event %s wparam=%x lparam=%x\n", (message==WM_SYSKEYDOWN)?"WM_SYSKEYDOWN":"WM_KEYDOWN", wparam, lparam);
		UINT DxWndKey;
		DxWndKey=dxw.MapKeysConfig(message, lparam, wparam);
		switch (DxWndKey){
		case DXVK_CLIPTOGGLE:
			OutTraceDW("WindowProc: WM_SYSKEYDOWN key=%x clipper=%x\n", wparam, dxw.IsClipCursorActive());
			dxw.IsClipCursorActive() ? dxw.EraseClipCursor() : dxw.SetClipCursor();
			break;
		case DXVK_REFRESH:
			dxw.ScreenRefresh();
			break;
		case DXVK_LOGTOGGLE:
			dx_ToggleLogging();
			break;
		case DXVK_PLOCKTOGGLE:
			dx_TogglePositionLock(hwnd);
			break;
		case DXVK_FPSTOGGLE:
			dx_ToggleFPS();
			break;
		//case DXVK_FREEZETIME:
		//	dxw.ToggleFreezedTime();
		//	break;
		case DXVK_TIMEFAST:
		case DXVK_TIMESLOW:
			if (dxw.dwFlags2 & TIMESTRETCH) {
				if (DxWndKey == DXVK_TIMESLOW && (dxw.TimeShift <  8)) dxw.TimeShift++;
				if (DxWndKey == DXVK_TIMEFAST && (dxw.TimeShift > -8)) dxw.TimeShift--;
				GetHookInfo()->TimeShift=dxw.TimeShift;
				OutTrace("Time Stretch: shift=%d speed=%s\n", dxw.TimeShift, dxw.GetTSCaption());
			}
			break;
		case DXVK_TIMETOGGLE:
			if (dxw.dwFlags2 & TIMESTRETCH) {
				if(TimeShiftToggle){
					SaveTimeShift=dxw.TimeShift;
					dxw.TimeShift=0;
				}
				else{
					dxw.TimeShift=SaveTimeShift;
				}
				TimeShiftToggle = !TimeShiftToggle;
				GetHookInfo()->TimeShift=dxw.TimeShift;
		}
			break;
		case DXVK_ALTF4:
			if (dxw.dwFlags1 & HANDLEALTF4) {
				OutTraceDW("WindowProc: WM_SYSKEYDOWN(virtual Alt-F4) - terminating process\n");
				TerminateProcess(GetCurrentProcess(),0);
			}
			break;
		case DXVK_PRINTSCREEN:
			DDrawScreenShot(dxw.dwDDVersion);
			break;
		case DXVK_CORNERIZE:
			dx_Cornerize(hwnd);
			break;
		case DXVK_FULLSCREEN:
			dx_FullScreenToggle(hwnd);
			break;
		case DXVK_FAKEDESKTOP:
			dx_DesktopToggle(hwnd, FALSE);
			break;
		case DXVK_FAKEWORKAREA:
			dx_DesktopToggle(hwnd, TRUE);
		default:
			break;
		}
	default:
		break;
	}
	if (dxw.dwFlags1 & AUTOREFRESH) dxw.ScreenRefresh();

	pWindowProc=dxwws.GetProc(hwnd);
	//OutTraceB("WindowProc: pWindowProc=%x extWindowProc=%x message=%x(%s) wparam=%x lparam=%x\n", 
	//	(*pWindowProc), extWindowProc, message, ExplainWinMessage(message), wparam, lparam);
	if(pWindowProc) {
		LRESULT ret;

		// v2.02.36: use CallWindowProc that handles WinProc handles
		ret=(*pCallWindowProcA)(pWindowProc, hwnd, message, wparam, lparam);

		switch(message){
			case WM_SIZE:
			//case WM_WINDOWPOSCHANGED: - no good!!!!
				// update new coordinates
				if (dxw.IsFullScreen()) dxw.UpdateDesktopCoordinates();
				break;
			case WM_NCHITTEST:
				// save last NCHITTEST cursor position for use with KEEPASPECTRATIO scaling
				LastCursorPos=ret;
				break;
		}

		// v2.1.89: if FORCEWINRESIZE add standard processing for the missing WM_NC* messages
		if(dxw.dwFlags2 & FORCEWINRESIZE){ 
			switch(message){
			//case WM_NCHITTEST:
			//case WM_NCPAINT:
			//case WM_NCMOUSEMOVE:
			//case WM_NCCALCSIZE:
			//case WM_NCACTIVATE:
			case WM_SETCURSOR:		// shows a different cursor when moving on borders
			case WM_NCLBUTTONDOWN:	// intercepts mouse down on borders
			case WM_NCLBUTTONUP:	// intercepts mouse up on borders
				ret=(*pDefWindowProcA)(hwnd, message, wparam, lparam);
				break;
			}
		}

		return ret;
	}

	//OutTraceDW("ASSERT: WindowProc mismatch hwnd=%x\n", hwnd);
	// ??? maybe it's a normal condition, whenever you don't have a WindowProc routine
	// like in Commandos 2. Flag it?
	char sMsg[81];
	sprintf(sMsg,"ASSERT: WindowProc mismatch hwnd=%x\n", hwnd);
	OutTraceDW(sMsg);
	if (IsAssertEnabled) MessageBox(0, sMsg, "WindowProc", MB_OK | MB_ICONEXCLAMATION);	
	return (*pDefWindowProcA)(hwnd, message, wparam, lparam);
}