#define _CRT_SECURE_NO_WARNINGS 
#define _CRT_NON_CONFORMING_SWPRINTFS

#include "dxwnd.h"
#include "dxwcore.hpp"
#include "syslibs.h"
#include "dxhook.h"
#include "dxhelper.h"

#include "MMSystem.h"
#include <stdio.h>

#define SUPPRESSMCIERRORS FALSE

BOOL IsWithinMCICall = FALSE;

typedef MCIDEVICEID (WINAPI *mciGetDeviceIDA_Type)(LPCTSTR);
mciGetDeviceIDA_Type pmciGetDeviceIDA = NULL;
MCIDEVICEID WINAPI extmciGetDeviceIDA(LPCTSTR);
typedef MCIDEVICEID (WINAPI *mciGetDeviceIDW_Type)(LPCWSTR);
mciGetDeviceIDW_Type pmciGetDeviceIDW = NULL;
MCIDEVICEID WINAPI extmciGetDeviceIDW(LPCWSTR);

static HookEntry_Type Hooks[]={
	{HOOK_HOT_CANDIDATE, "mciSendCommandA", NULL, (FARPROC *)&pmciSendCommandA, (FARPROC)extmciSendCommandA},
	{HOOK_HOT_CANDIDATE, "mciSendCommandW", NULL, (FARPROC *)&pmciSendCommandW, (FARPROC)extmciSendCommandW},
	{HOOK_HOT_CANDIDATE, "mciGetDeviceIDA", NULL, (FARPROC *)&pmciGetDeviceIDA, (FARPROC)extmciGetDeviceIDA},
	{HOOK_HOT_CANDIDATE, "mciGetDeviceIDW", NULL, (FARPROC *)&pmciGetDeviceIDW, (FARPROC)extmciGetDeviceIDW},
	{HOOK_IAT_CANDIDATE, 0, NULL, 0, 0} // terminator
};

static HookEntry_Type TimeHooks[]={
	{HOOK_HOT_CANDIDATE, "timeGetTime", NULL, (FARPROC *)&ptimeGetTime, (FARPROC)exttimeGetTime},
	{HOOK_HOT_CANDIDATE, "timeKillEvent", NULL, (FARPROC *)&ptimeKillEvent, (FARPROC)exttimeKillEvent},
	{HOOK_HOT_CANDIDATE, "timeSetEvent", NULL, (FARPROC *)&ptimeSetEvent, (FARPROC)exttimeSetEvent},
	{HOOK_IAT_CANDIDATE, 0, NULL, 0, 0} // terminator
};

static HookEntry_Type RemapHooks[]={
	{HOOK_HOT_CANDIDATE, "mciSendStringA", NULL, (FARPROC *)&pmciSendStringA, (FARPROC)extmciSendStringA},
	{HOOK_HOT_CANDIDATE, "mciSendStringW", NULL, (FARPROC *)&pmciSendStringW, (FARPROC)extmciSendStringW},
	{HOOK_IAT_CANDIDATE, 0, NULL, 0, 0} // terminator
};

void HookWinMM(HMODULE module)
{
	HookLibrary(module, Hooks, "winmm.dll");
	if(dxw.dwFlags2 & TIMESTRETCH) HookLibrary(module, TimeHooks, "winmm.dll");
	if(dxw.dwFlags5 & REMAPMCI) HookLibrary(module, RemapHooks, "winmm.dll");
}

FARPROC Remap_WinMM_ProcAddress(LPCSTR proc, HMODULE hModule)
{
	FARPROC addr;

	if (addr=RemapLibrary(proc, hModule, Hooks)) return addr;
	if(dxw.dwFlags2 & TIMESTRETCH)
		if (addr=RemapLibrary(proc, hModule, TimeHooks)) return addr;
	if(dxw.dwFlags5 & REMAPMCI)
		if (addr=RemapLibrary(proc, hModule, RemapHooks)) return addr;

	return NULL;
}

DWORD WINAPI exttimeGetTime(void)
{
	DWORD ret;
	ret = dxw.GetTickCount();
	if (IsDebug) OutTrace("timeGetTime: time=%x\n", ret);
	return ret;
}

MMRESULT WINAPI exttimeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK lpTimeProc, DWORD_PTR dwUser, UINT fuEvent)
{
	MMRESULT res;
	UINT NewDelay;
	OutTraceDW("timeSetEvent: Delay=%d Resolution=%d Event=%x\n", uDelay, uResolution, fuEvent);
	if(dxw.dwFlags4 & STRETCHTIMERS) NewDelay = dxw.StretchTime(uDelay);
	else NewDelay = uDelay;
	res=(*ptimeSetEvent)(NewDelay, uResolution, lpTimeProc, dwUser, fuEvent);
	if(res) dxw.PushTimer(res, uDelay, uResolution, lpTimeProc, dwUser, fuEvent);
	OutTraceDW("timeSetEvent: ret=%x\n", res);
	return res;
}

MMRESULT WINAPI exttimeKillEvent(UINT uTimerID)
{
	MMRESULT res;
	OutTraceDW("timeKillEvent: TimerID=%x\n", uTimerID);
	res=(*ptimeKillEvent)(uTimerID);
	if(res==TIMERR_NOERROR) dxw.PopTimer(uTimerID);
	OutTraceDW("timeKillEvent: ret=%x\n", res);
	return res;
}

/* MCI_DGV_PUT_FRAME

    The rectangle defined for MCI_DGV_RECT applies to the frame rectangle. 
	The frame rectangle specifies the portion of the frame buffer used as the destination of the video images obtained from the video rectangle. 
	The video should be scaled to fit within the frame buffer rectangle.
    The rectangle is specified in frame buffer coordinates. 
	The default rectangle is the full frame buffer. 
	Specifying this rectangle lets the device scale the image as it digitizes the data. 
	Devices that cannot scale the image reject this command with MCIERR_UNSUPPORTED_FUNCTION. 
	You can use the MCI_GETDEVCAPS_CAN_STRETCH flag with the MCI_GETDEVCAPS command to determine if a device scales the image. A device returns FALSE if it cannot scale the image.
*/

MCIERROR WINAPI extmciSendCommand(mciSendCommand_Type pmciSendCommand, MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR fdwCommand, DWORD_PTR dwParam)
{
	RECT saverect;
	MCIERROR ret;
	MCI_ANIM_RECT_PARMS *pr;
	MCI_OVLY_WINDOW_PARMSW *pw;

	OutTraceDW("mciSendCommand: IDDevice=%x msg=%x(%s) Command=%x(%s)\n",
		IDDevice, uMsg, ExplainMCICommands(uMsg), fdwCommand, ExplainMCIFlags(uMsg, fdwCommand));

	if(dxw.IsFullScreen()){
		switch(uMsg){
		case MCI_WINDOW:
			pw = (MCI_OVLY_WINDOW_PARMSW *)dwParam;
			OutTraceB("mciSendCommand: hwnd=%x CmdShow=%x\n",
				pw->hWnd, pw->nCmdShow);
			//fdwCommand |= MCI_ANIM_WINDOW_ENABLE_STRETCH;
			//fdwCommand &= ~MCI_ANIM_WINDOW_DISABLE_STRETCH;
			if(dxw.IsRealDesktop(pw->hWnd)) {
				pw->hWnd = dxw.GethWnd();
				OutTraceB("mciSendCommand: REDIRECT hwnd=%x\n", pw->hWnd);
			}
			break;
		case MCI_PUT:
			RECT client;
			pr = (MCI_ANIM_RECT_PARMS *)dwParam;
			OutTraceB("mciSendCommand: rect=(%d,%d),(%d,%d)\n",
				pr->rc.left, pr->rc.top, pr->rc.right, pr->rc.bottom);
			(*pGetClientRect)(dxw.GethWnd(), &client);
			//fdwCommand |= MCI_ANIM_PUT_DESTINATION;
			fdwCommand |= MCI_ANIM_RECT;
			saverect=pr->rc;
			pr->rc.top = (pr->rc.top * client.bottom) / dxw.GetScreenHeight();
			pr->rc.bottom = (pr->rc.bottom * client.bottom) / dxw.GetScreenHeight();
			pr->rc.left = (pr->rc.left * client.right) / dxw.GetScreenWidth();
			pr->rc.right = (pr->rc.right * client.right) / dxw.GetScreenWidth();
			OutTraceB("mciSendCommand: fixed rect=(%d,%d),(%d,%d)\n",
				pr->rc.left, pr->rc.top, pr->rc.right, pr->rc.bottom);
			break;
		case MCI_PLAY:
			if(dxw.dwFlags6 & NOMOVIES) return 0; // ???
			break;
		case MCI_OPEN:
			if(dxw.dwFlags6 & NOMOVIES) return 275; // quite brutal, but working ....
			break;
		}
	}

	ret=(*pmciSendCommand)(IDDevice, uMsg, fdwCommand, dwParam);

	if(dxw.IsFullScreen() && uMsg==MCI_PUT) pr->rc=saverect;
	if (ret) OutTraceE("mciSendCommand: ERROR res=%d\n", ret);
	return ret;
}

MCIERROR WINAPI extmciSendCommandA(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR fdwCommand, DWORD_PTR dwParam)
{
	return extmciSendCommand(pmciSendCommandA, IDDevice, uMsg, fdwCommand, dwParam);
}

MCIERROR WINAPI extmciSendCommandW(MCIDEVICEID IDDevice, UINT uMsg, DWORD_PTR fdwCommand, DWORD_PTR dwParam)
{
	return extmciSendCommand(pmciSendCommandW, IDDevice, uMsg, fdwCommand, dwParam);
}

MCIERROR WINAPI extmciSendStringA(LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback)
{
	MCIERROR ret;
	if(IsWithinMCICall) return(*pmciSendStringA)(lpszCommand, lpszReturnString, cchReturn, hwndCallback); // just proxy ...
	OutTraceDW("mciSendStringA: Command=\"%s\" Return=%x Callback=%x\n", lpszCommand, cchReturn, hwndCallback);
	char sMovieNickName[81];
	char sTail[81];
	RECT rect;
	sTail[0]=0;
	if (sscanf(lpszCommand, "put %s destination at %ld %ld %ld %ld %s", 
		sMovieNickName, &(rect.left), &(rect.top), &(rect.right), &(rect.bottom), sTail)>=5){
		char NewCommand[256];
		// v2.03.19 height / width fix
		rect.right += rect.left; // convert width to position
		rect.bottom += rect.top; // convert height to position
		rect=dxw.MapClientRect(&rect);
		rect.right -= rect.left; // convert position to width
		rect.bottom -= rect.top; // convert position to height
		sprintf(NewCommand, "put %s destination at %d %d %d %d %s", sMovieNickName, rect.left, rect.top, rect.right, rect.bottom, sTail);
		lpszCommand=NewCommand;
		OutTraceDW("mciSendStringA: replaced Command=\"%s\"\n", lpszCommand);
	}
	IsWithinMCICall=TRUE;
	ret=(*pmciSendStringA)(lpszCommand, lpszReturnString, cchReturn, hwndCallback);
	IsWithinMCICall=FALSE;
	if(ret) OutTraceDW("mciSendStringA ERROR: ret=%x\n", ret);
	OutTraceDW("mciSendStringA: RetString=\"%s\"\n", lpszReturnString);
	return ret;
}

MCIERROR WINAPI extmciSendStringW(LPCWSTR lpszCommand, LPWSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback)
{
	MCIERROR ret;
	WCHAR *target=L"put movie destination at ";
	if(IsWithinMCICall) return(*pmciSendStringW)(lpszCommand, lpszReturnString, cchReturn, hwndCallback); // just proxy ...
	OutTraceDW("mciSendStringW: Command=\"%ls\" Return=%x Callback=%x\n", lpszCommand, cchReturn, hwndCallback);
	WCHAR sMovieNickName[81];
	RECT rect;
	if (swscanf(lpszCommand, L"put %ls destination at %ld %ld %ld %ld", 
		sMovieNickName, &(rect.left), &(rect.top), &(rect.right), &(rect.bottom))==5){
		WCHAR NewCommand[256];
		// v2.03.19 height / width fix
		rect.right += rect.left; // convert width to position
		rect.bottom += rect.top; // convert height to position
		rect=dxw.MapClientRect(&rect);
		rect.right -= rect.left; // convert position to width
		rect.bottom -= rect.top; // convert position to height
		swprintf(NewCommand, L"put %ls destination at %d %d %d %d", sMovieNickName, rect.left, rect.top, rect.right, rect.bottom);
		lpszCommand=NewCommand;
		OutTraceDW("mciSendStringW: replaced Command=\"%ls\"\n", lpszCommand);
	}
	IsWithinMCICall=TRUE;
	ret=(*pmciSendStringW)(lpszCommand, lpszReturnString, cchReturn, hwndCallback);
	IsWithinMCICall=FALSE;
	if(ret) OutTraceDW("mciSendStringW ERROR: ret=%x\n", ret);
	OutTraceDW("mciSendStringW: RetString=\"%ls\"\n", lpszReturnString);
	return ret;
}

MCIDEVICEID WINAPI extmciGetDeviceIDA(LPCTSTR lpszDevice)
{
	MCIDEVICEID ret;
	ret = (*pmciGetDeviceIDA)(lpszDevice);
	OutTraceDW("mciGetDeviceIDA: device=\"%s\" ret=%x\n", lpszDevice, ret);
	return ret;
}

MCIDEVICEID WINAPI extmciGetDeviceIDW(LPCWSTR lpszDevice)
{
	MCIDEVICEID ret;
	ret = (*pmciGetDeviceIDW)(lpszDevice);
	OutTraceDW("mciGetDeviceIDW: device=\"%ls\" ret=%x\n", lpszDevice, ret);
	return ret;
}