#define _CRT_SECURE_NO_WARNINGS

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

//extern LPDIRECTDRAWSURFACE lpDDSBack;
extern LPDIRECTDRAWSURFACE lpDDSEmu_Prim;
extern LPDIRECTDRAW lpPrimaryDD;
extern Blt_Type pBlt;
extern ReleaseS_Type pReleaseS;
extern CreateSurface1_Type pCreateSurface1;
extern CreateSurface1_Type pCreateSurface2;
extern CreateSurface1_Type pCreateSurface3;
extern CreateSurface2_Type pCreateSurface4;
extern CreateSurface2_Type pCreateSurface7;
extern Unlock4_Type pUnlockMethod(LPDIRECTDRAWSURFACE);
extern HDC hFlippedDC;
extern ReleaseDC_Type pReleaseDC;

extern void BlitError(HRESULT, LPRECT, LPRECT, int);
extern void BlitTrace(char *, LPRECT, LPRECT, int);
extern void DescribeSurface(LPDIRECTDRAWSURFACE, int, char *, int);
extern void TextureHandling(LPDIRECTDRAWSURFACE);

static HRESULT sBltNoPrimary(char *api, LPDIRECTDRAWSURFACE lpdds, LPRECT lpdestrect,
	LPDIRECTDRAWSURFACE lpddssrc, LPRECT lpsrcrect, DWORD dwflags, LPDDBLTFX lpddbltfx)
{
	RECT srcrect;
	HRESULT res;
	BOOL FromScreen;
	//extern PrimaryBlt_Type pPrimaryBlt;
	//CkArg arg;

	FromScreen=dxwss.IsAPrimarySurface(lpddssrc) && !(dxw.dwFlags1 & EMULATESURFACE) && !(dxw.dwFlags1 & EMULATEBUFFER); // v2.02.77

	// make a working copy of srcrect if not NULL
	if (lpsrcrect){
		srcrect=*lpsrcrect;
	}
	// when blitting from a primary surface on screen (that is in non emulated mode), correct offsets
	// You should take account also for scaled primary surfaces, but that would be a hard task: 
	// a reduced primary surface (in not-emulated mode) would bring quality loss!!!
	// v2.1.83: BLITFROMBACKBUFFER mode, let you chose to blit from backbuffer, where the surface size
	// is fixed no matter how the window/primary surface is scaled. 
	// In "The Sims" there is no quality loss, but some scrolling artifact.
	if(lpsrcrect && FromScreen){
		LPDIRECTDRAWSURFACE lpDDSBack;
		lpDDSBack = dxwss.GetBackBufferSurface();
		if(lpDDSBack && (dxw.dwFlags1 & BLITFROMBACKBUFFER)){
			lpddssrc=lpDDSBack;
			srcrect=dxw.GetScreenRect(); 
		}
		else{
			srcrect=dxw.MapWindowRect(lpsrcrect);
		}
	}

	if (IsDebug) BlitTrace("NOPRIM", lpsrcrect, lpdestrect, __LINE__);
	res= (*pBlt)(lpdds, lpdestrect, lpddssrc, lpsrcrect ? &srcrect : NULL, dwflags, lpddbltfx);
	// Blitting compressed data may work to screen surfaces only. In this case, it may be worth
	// trying blitting directly to lpDDSEmu_Prim: it makes DK2 intro movies working.
	// Wrong guess!!! The cause was not compression, but simply a pixelformat mismatch. Better
	// configure things properly and avoid this branch.
	switch(res){
	// commented out: it was the cause of the "Divine Divinity" flickering.
	// commented in?: it seems useful in "Axis and Allies"....
	//case DDERR_UNSUPPORTED:
	//	if (dxw.dwFlags1 & EMULATESURFACE){
	//		RECT targetrect;
	//		if (IsDebug) BlitTrace("UNSUPP", lpsrcrect ? &srcrect : NULL, lpdestrect, __LINE__);
	//		targetrect = dxw.MapWindowRect(lpdestrect);
	//		res=(*pBlt)(lpDDSEmu_Prim, &targetrect, lpddssrc, lpsrcrect ? &srcrect : NULL, dwflags, lpddbltfx);
	//	}
	//	break;
	case DDERR_SURFACEBUSY:
		(*pUnlockMethod(lpdds))(lpdds, NULL);
		if (lpddssrc) (*pUnlockMethod(lpddssrc))(lpddssrc, NULL);	
		if (IsDebug) BlitTrace("BUSY", lpsrcrect ? &srcrect : NULL, lpdestrect, __LINE__);
		res=(*pBlt)(lpdds, lpdestrect, lpddssrc, lpsrcrect ? &srcrect : NULL, dwflags|DDBLT_WAIT, lpddbltfx);
		break;
	default:
		break;
	}
	if (res) BlitError(res, &srcrect, lpdestrect, __LINE__);
	if(IsDebug) {
		DescribeSurface(lpdds, 0, "[DST]" , __LINE__);
		if (lpddssrc) DescribeSurface(lpddssrc, 0, "[SRC]" , __LINE__); // lpddssrc could be NULL!!!
	}
	if(dxw.dwFlags1 & SUPPRESSDXERRORS) res=0;
	if(dxw.dwFlags5 & TEXTUREMASK) {
		// Texture Handling on Blt
		TextureHandling(lpdds);
	}
	return res;
}

static HRESULT sBltToPrimary(char *api, LPDIRECTDRAWSURFACE lpdds, LPRECT lpdestrect,
	LPDIRECTDRAWSURFACE lpddssrc, LPRECT lpsrcrect, DWORD dwflags, LPDDBLTFX lpddbltfx, BOOL isFlipping)
{
	HRESULT res;
	RECT destrect, emurect;
	extern PrimaryBlt_Type pPrimaryBlt;

	// debug suppressions
	if(isFlipping){
		if(dxw.dwFlags3 & NODDRAWFLIP) return DD_OK;
	}
	else {
		if(dxw.dwFlags3 & NODDRAWBLT) return DD_OK;
	}

#ifdef ONEPIXELFIX
	if (lpdestrect){
		if ((lpdestrect->top == 0) && (lpdestrect->bottom == dxw.GetScreenHeight() -1)) lpdestrect->bottom = dxw.GetScreenHeight();
		if ((lpdestrect->left == 0) && (lpdestrect->right  == dxw.GetScreenWidth()  -1)) lpdestrect->right  = dxw.GetScreenWidth();
	}
	if (lpsrcrect){
		if ((lpsrcrect->top == 0) && (lpsrcrect->bottom == dxw.GetScreenHeight() -1)) lpsrcrect->bottom = dxw.GetScreenHeight();
		if ((lpsrcrect->left == 0) && (lpsrcrect->right  == dxw.GetScreenWidth()  -1)) lpsrcrect->right  = dxw.GetScreenWidth();
	}
#endif

#define FIXBIGGERRECT 1
#if FIXBIGGERRECT
	if(lpdestrect){
		if((DWORD)lpdestrect->top < 0) lpdestrect->top = 0;
		if((DWORD)lpdestrect->left < 0) lpdestrect->left = 0;
		if((DWORD)lpdestrect->bottom > dxw.GetScreenHeight()) lpdestrect->bottom = dxw.GetScreenHeight();
		if((DWORD)lpdestrect->right > dxw.GetScreenWidth()) lpdestrect->right = dxw.GetScreenWidth();
	}
#endif

	if(dxw.dwFlags5 & QUARTERBLT){
		BOOL QuarterUpdate;
		QuarterUpdate = lpdestrect ? 
			(((lpdestrect->bottom - lpdestrect->top) * (lpdestrect->right - lpdestrect->left)) > ((LONG)(dxw.GetScreenHeight() * dxw.GetScreenWidth()) >> 2)) 
			: 
			TRUE;
		if(QuarterUpdate) if(dxw.HandleFPS()) return DD_OK;
	}
	else
		if(dxw.HandleFPS()) return DD_OK;
	if(dxw.dwFlags5 & NOBLT) return DD_OK;
	
	destrect=dxw.MapWindowRect(lpdestrect);
	OutTraceB("DESTRECT=(%d,%d)-(%d,%d) Screen=(%dx%d)\n", 
		destrect.left, destrect.top, destrect.right, destrect.bottom,
		dxw.GetScreenWidth(), dxw.GetScreenHeight());

	if(!lpddssrc) {
		if (isFlipping){
			// handle the flipping chain ...
			//lpddssrc=lpDDSBack;
			lpddssrc = dxwss.GetBackBufferSurface();
			OutTraceDW("Flip: setting flip chain to lpdds=%x\n", lpddssrc);
		}
	}

	// =========================
	// Blit to primary direct surface 
	// =========================

	if(!(dxw.dwFlags1 & (EMULATESURFACE|EMULATEBUFFER))){ 
		res=DD_OK;

		// blit only when source and dest surface are different. Should make ScreenRefresh faster.
		if (lpdds != lpddssrc) {
			dxw.ShowOverlay(lpddssrc);
			if (IsDebug) BlitTrace("PRIM-NOEMU", lpsrcrect, &destrect, __LINE__);
			res=(*pPrimaryBlt)(lpdds, &destrect, lpddssrc, lpsrcrect);
		}
		if(res){
			BlitError(res, lpsrcrect, &destrect, __LINE__);
			if(IsDebug) {
				DescribeSurface(lpdds, 0, "[DST]" , __LINE__);
				if (lpddssrc) DescribeSurface(lpddssrc, 0, "[SRC]" , __LINE__); // lpddssrc could be NULL!!!
			}
			// Try to handle HDC lock concurrency....		
			if(res==DDERR_SURFACEBUSY){
				(*pUnlockMethod(lpdds))(lpdds, NULL);
				if (IsDebug) BlitTrace("BUSY", lpsrcrect, &destrect, __LINE__);
				res= (*pBlt)(lpdds, &destrect, lpddssrc, lpsrcrect, dwflags, lpddbltfx);
				if (res) BlitError(res, lpsrcrect, &destrect, __LINE__);
			}
			// Try to handle DDBLT_KEYSRC on primary surface
			if((res==DDERR_INVALIDPARAMS) && (dwflags & DDBLT_KEYSRC)){
				// to do: handle possible situations with surface 2 / 4 / 7 types
				DDSURFACEDESC ddsd;
				LPDIRECTDRAWSURFACE lpddsTmp;
				extern GetSurfaceDesc_Type pGetSurfaceDesc1;
				if (IsDebug) BlitTrace("KEYSRC", lpsrcrect, &destrect, __LINE__);
				memset(&ddsd, 0, sizeof(ddsd));
				ddsd.dwSize = sizeof(ddsd);
				(*pGetSurfaceDesc1)(lpddssrc, &ddsd);
				res=(*pCreateSurface1)(lpPrimaryDD, &ddsd, &lpddsTmp, NULL);
				if(res) OutTraceE("CreateSurface: ERROR %x(%s) at %d", res, ExplainDDError(res), __LINE__);
				// copy background
				res= (*pBlt)(lpddsTmp, lpsrcrect, lpdds, &destrect, DDBLT_WAIT, NULL);
				if(res) OutTraceE("Blt: ERROR %x(%s) at %d", res, ExplainDDError(res), __LINE__);
				// overlay texture
				res= (*pBlt)(lpddsTmp, lpsrcrect, lpddssrc, lpsrcrect, dwflags, lpddbltfx);
				if(res) OutTraceE("Blt: ERROR %x(%s) at %d", res, ExplainDDError(res), __LINE__);
				// copy back to destination
				res= (*pBlt)(lpdds, &destrect, lpddsTmp, lpsrcrect, DDBLT_WAIT, lpddbltfx);
				if(res) OutTraceE("Blt: ERROR %x(%s) at %d", res, ExplainDDError(res), __LINE__);
				if (res) BlitError(res, lpsrcrect, &destrect, __LINE__);
				(*pReleaseS)(lpddsTmp);
			}
			if(dxw.dwFlags1 & SUPPRESSDXERRORS) res=DD_OK;
		}

		return res;
	}

	// ... else blitting on emulated surface
	
	// =========================
	// Blit/Flip to emulated primary surface
	// =========================

	if(dxw.dwFlags5 & GDIMODE){
		extern void BlitToWindow(HWND, LPDIRECTDRAWSURFACE);
		//if (lpdds != lpddssrc) 
			BlitToWindow(dxw.GethWnd(), lpddssrc);
		return DD_OK;
	}

	if (lpdestrect){
		emurect=*lpdestrect;
	}
	else{
		// emurect: emulated rect is full surface (dwWidth x dwHeight)
		emurect.left = 0;
		emurect.top = 0;
		emurect.right = dxw.GetScreenWidth();
		emurect.bottom = dxw.GetScreenHeight();
	}

	res=DD_OK;
	// blit only when source and dest surface are different. Should make ScreenRefresh faster.
	if (lpdds != lpddssrc){
		if (IsDebug) BlitTrace("SRC2EMU", &emurect, &destrect, __LINE__);
		if(destrect.top == -32000) return DD_OK; // happens when window is minimized & do not notify on task switch ...
	    if(lpdds->IsLost()) lpdds->Restore(); // lpDDSEmu_Back could get lost .....
		res=(*pBlt)(lpdds, &emurect, lpddssrc, lpsrcrect, dwflags, lpddbltfx);
	}

	if (res) {
		BlitError(res, lpsrcrect, &emurect, __LINE__);
		DescribeSurface(lpdds,    0, "[DST]" , __LINE__);
		if (lpddssrc) DescribeSurface(lpddssrc, 0, "[SRC]" , __LINE__); // lpddssrc could be NULL!!!
		/* 
		Dungeon Keeper II intro movies bug ....
		it seems that you can't blit from compressed or different surfaces in memory,
		while the operation COULD be supported to video. As a mater of fact, it DOES
		work on my PC. The error code is DDERR_UNSUPPORTED.
		v2.02.98: The same thing happens with "New York Racer", but with DDERR_EXCEPTION error code.
		V2.03.15: The same thing happens with "Silent Hunter III", but with DDERR_INVALIDRECT error code.
		*/
		if((res==DDERR_UNSUPPORTED) || (res==DDERR_EXCEPTION) || (res==DDERR_INVALIDRECT)){
			//RECT targetrect;
			dxw.ShowOverlay(lpddssrc);
			if (IsDebug) BlitTrace("UNSUPP", &emurect, &destrect, __LINE__);
			//targetrect=dxw.MapWindowRect(&destrect); // v2.03.18
			//res=(*pBlt)(lpDDSEmu_Prim, &targetrect, lpddssrc, lpsrcrect, dwflags, lpddbltfx);
			res=(*pBlt)(lpDDSEmu_Prim, &destrect, lpddssrc, lpsrcrect, dwflags, lpddbltfx);
			if (res) BlitError(res, lpsrcrect, &destrect, __LINE__);
		}

		// Try to handle HDC lock concurrency....		
		if(res==DDERR_SURFACEBUSY){
			(*pReleaseDC)(lpdds, hFlippedDC);
#if 0
			res=(*pUnlockMethod(lpddssrc))(lpddssrc, NULL);
			if(res && (res!=DDERR_NOTLOCKED)) OutTraceE("Unlock ERROR: lpdds=%x err=%x(%s)\n", lpddssrc, res, ExplainDDError(res));
			res=(*pUnlockMethod(lpdds))(lpdds, NULL); // v2.03.24 reintroduced because of "Virtua Cop"
			if(res && (res!=DDERR_NOTLOCKED)) OutTraceE("Unlock ERROR: lpdds=%x err=%x(%s)\n", lpdds, res, ExplainDDError(res));
#endif
			if (IsDebug) BlitTrace("BUSY", &emurect, &destrect, __LINE__);
			res=(*pBlt)(lpdds, &emurect, lpddssrc, lpsrcrect, dwflags, lpddbltfx);
			if (res) BlitError(res, lpsrcrect, &destrect, __LINE__);
		}

		if(dxw.dwFlags1 & SUPPRESSDXERRORS) res=DD_OK;
		return res;
	}

	LPDIRECTDRAWSURFACE lpDDSSource;
	if (res=(*pColorConversion)(lpdds, emurect, &lpDDSSource)) {
		OutTraceE("sBlt ERROR: Color conversion failed res=%x(%s)\n", res, ExplainDDError(res));
		if(dxw.dwFlags1 & SUPPRESSDXERRORS) res=DD_OK;
		return res;
	}

	if(lpDDSEmu_Prim->IsLost()) lpDDSEmu_Prim->Restore();

	dxw.ShowOverlay(lpDDSSource);
	if(dxw.dwFlags4 & BILINEAR2XFILTER){
		emurect.right <<= 1;
		emurect.bottom <<= 1;
	}
	if (IsDebug) BlitTrace("BACK2PRIM", &emurect, &destrect, __LINE__);
	res=(*pPrimaryBlt)(lpDDSEmu_Prim, &destrect, lpDDSSource, &emurect);

	if (res) BlitError(res, &emurect, &destrect, __LINE__);
	if(dxw.dwFlags1 & SUPPRESSDXERRORS) res=DD_OK;
	if (IsDebug) OutTrace("%s: done ret=%x at %d\n", api, res, __LINE__);
	return res;
}

HRESULT WINAPI sBlt(char *api, LPDIRECTDRAWSURFACE lpdds, LPRECT lpdestrect,
	LPDIRECTDRAWSURFACE lpddssrc, LPRECT lpsrcrect, DWORD dwflags, LPDDBLTFX lpddbltfx, BOOL isFlipping)
{
	POINT p = {0, 0};
	HRESULT res;
	BOOL ToPrim, FromPrim, ToScreen, FromScreen;

	if(dxw.dwFlags5 & MESSAGEPUMP){
		MSG msg;
		while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)){
			OutTraceW("MESSAGEPUMP: msg=%x l-wParam=(%x,%x)\n", msg.message, msg.lParam, msg.wParam);
			if((msg.message >= WM_KEYFIRST) && (msg.message <= WM_KEYLAST)) break; // do not consume keyboard inputs
			PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	ToPrim=dxwss.IsAPrimarySurface(lpdds);
	FromPrim=dxwss.IsAPrimarySurface(lpddssrc);
	ToScreen=ToPrim && !(dxw.dwFlags1 & EMULATESURFACE);
	FromScreen=FromPrim && !(dxw.dwFlags1 & EMULATESURFACE) && !(dxw.dwFlags1 & EMULATEBUFFER); // v2.02.77

	// log
	if(IsTraceDW){
		char sLog[256];
		char sInfo[128];
		sprintf(sLog, "%s: dest=%x%s src=%x%s dwFlags=%x(%s)",
			api, lpdds, (ToPrim ? "(PRIM)":""), lpddssrc, (FromPrim ? "(PRIM)":""), dwflags, ExplainBltFlags(dwflags));
		if (lpdestrect)
			sprintf(sInfo, " destrect=(%d,%d)-(%d,%d)", lpdestrect->left, lpdestrect->top, lpdestrect->right, lpdestrect->bottom);
		else
			sprintf(sInfo, " destrect=(NULL)");
		strcat(sLog, sInfo);
		if (lpsrcrect)
			sprintf(sInfo, " srcrect=(%d,%d)-(%d,%d)", lpsrcrect->left, lpsrcrect->top, lpsrcrect->right, lpsrcrect->bottom);
		else
			sprintf(sInfo, " srcrect=(NULL)");
		strcat(sLog, sInfo);
		if(lpddbltfx){
			if (dwflags & DDBLT_COLORFILL){
				sprintf(sInfo, " ddbltfx.FillColor=%x", lpddbltfx->dwFillColor); 
				strcat(sLog, sInfo);
			}
			if (dwflags & DDBLT_KEYDESTOVERRIDE){
				sprintf(sInfo, " ddbltfx.DestColorkey=%x", lpddbltfx->ddckDestColorkey); 
				strcat(sLog, sInfo);
			}
			if (dwflags & DDBLT_KEYSRCOVERRIDE){
				sprintf(sInfo, " ddbltfx.SrcColorkey=%x", lpddbltfx->ddckSrcColorkey); 
				strcat(sLog, sInfo);
			}
			if (dwflags & DDBLT_ROP){
				sprintf(sInfo, " ddbltfx.ROP=%x", lpddbltfx->dwROP);
				strcat(sLog, sInfo);
			}
			if (dwflags & DDBLT_DEPTHFILL){
				sprintf(sInfo, " ddbltfx.FillDepth=%x", lpddbltfx->dwFillDepth);
				strcat(sLog, sInfo);
			}
		}
		strcat(sLog,"\n");
		OutTrace(sLog);
	}

	if(ToPrim)
		res = sBltToPrimary(api, lpdds, lpdestrect, lpddssrc, lpsrcrect, dwflags, lpddbltfx, isFlipping);
	else
		res = sBltNoPrimary(api, lpdds, lpdestrect, lpddssrc, lpsrcrect, dwflags, lpddbltfx);

	return res;
}