//	VirtualDub - Video processing and capture application
//	Copyright (C) 1998-2001 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "VirtualDub.h"

#define _WIN32_DCOM

#include <crtdbg.h>

#include <windows.h>
#include <objbase.h>

#define DIRECTDRAW_VERSION 0x0300

#include <ddraw.h>

#include "ddrawsup.h"
#include "vbitmap.h"

static BOOL com_initialized = FALSE;
static IDirectDraw2 *lpdd = NULL;
static DDCAPS g_devCaps;
static DDSURFACEDESC ddsdPrimary;
static IDirectDrawSurface *lpddsPrimary = NULL;

HRESULT InitCOM() {
	HMODULE hModOLE = LoadLibrary("ole32.dll");
	HRESULT hr;
	
	if (hModOLE) {
		FARPROC fp = GetProcAddress(hModOLE, "CoInitializeEx");

		if (fp) {
			hr = ((HRESULT (__stdcall *)(void *, DWORD))fp)(NULL, COINIT_MULTITHREADED);
			FreeLibrary(hModOLE);

			return hr;
		}

		FreeLibrary(hModOLE);
	}

	return CoInitialize(NULL);
}

bool DDrawDetect() {
	HRESULT res;

	// first, try to initialize COM...

	_RPT0(0,"DirectDraw support: Initializing COM\n");

	if (FAILED(CoInitialize(NULL)))
//	if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
//		return false;

	// good, now let's get a IDirectDraw2 object

	_RPT0(0,"DirectDraw support: Getting IDirectDraw2 interface\n");

	res = CoCreateInstance(CLSID_DirectDraw, NULL, CLSCTX_ALL, IID_IDirectDraw2, (LPVOID *)&lpdd);
	if (!FAILED(res))
		res = lpdd->Initialize(NULL);

	if (FAILED(res)) {
		CoUninitialize();
		return false;	// damn!
	}

	// looks like we have directdraw support....

	lpdd->Release();
	lpdd = NULL;

	CoUninitialize();

	return true;
}

BOOL DDrawInitialize(HWND hwnd) {
	HRESULT res;

	// first, try to initialize COM...

	_RPT0(0,"DirectDraw support: Initializing COM\n");

	if (FAILED(CoInitialize(NULL)))
		return FALSE;

	com_initialized = TRUE;

	// good, now let's get a IDirectDraw2 object

	_RPT0(0,"DirectDraw support: Getting IDirectDraw2 interface\n");

	res = CoCreateInstance(CLSID_DirectDraw, NULL, CLSCTX_ALL, IID_IDirectDraw2, (LPVOID *)&lpdd);
	if (!FAILED(res))
		res = lpdd->Initialize(NULL);

	if (FAILED(res)) return FALSE;	// damn!

	// retrieve dev caps

	// why the hell can't I get caps on my laptop!?

#if 0
	g_devCaps.dwSize = sizeof(DDCAPS);

	res = lpdd->GetCaps(&g_devCaps, NULL);
	if (FAILED(res))
		return FALSE;
#endif
	// set cooperative level

	if (DD_OK != lpdd->SetCooperativeLevel(hwnd, DDSCL_NORMAL))
		return FALSE;

	// create primary surface

	_RPT0(0,"Creating primary surface\n");

	memset(&ddsdPrimary, 0, sizeof(ddsdPrimary));
	ddsdPrimary.dwSize = sizeof(ddsdPrimary);
	ddsdPrimary.dwFlags = DDSD_CAPS;
	ddsdPrimary.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
	res = lpdd->CreateSurface(&ddsdPrimary, &lpddsPrimary, NULL );

	if (res != DD_OK)
		return FALSE;

	return TRUE;
}


void DDrawDeinitialize() {
	// Deallocate primary surface

	if (lpddsPrimary) {
		lpddsPrimary->Release();
		lpddsPrimary = NULL;
	}

	// Deallocate the IDirectDraw2 object

	if (lpdd) {
		lpdd->Release();

		lpdd = NULL;
	}

	// Deinitialize COM

	if (com_initialized) {
		CoUninitialize();

		com_initialized = FALSE;
	}

}

IDirectDraw2 *DDrawObtainInterface() {
	return lpdd;
}

IDirectDrawSurface *DDrawObtainPrimary() {
	return lpddsPrimary;
}

///////////////////////////////////////////////////////////////////////////

class DDrawSurface : public IDDrawSurface {
private:
	IDirectDrawSurface *lpdds;

	bool fIsLocked;

public:

	DDrawSurface(IDirectDrawSurface *lpdds);
	~DDrawSurface();
	bool Lock(VBitmap *pvbm);
	bool LockInverted(VBitmap *pvbm);
	void Unlock();
	void SetColorKey(COLORREF rgb);
	void MoveOverlay(long x, long y);
	void SetOverlayPos(RECT *pr);

	IDirectDrawSurface *getSurface();

};

IDDrawSurface *CreateDDrawSurface(IDirectDrawSurface *lpdds) {
	return new DDrawSurface(lpdds);
}

DDrawSurface::DDrawSurface(IDirectDrawSurface *lpdds) {
	this->lpdds = lpdds;
	fIsLocked = false;
}

DDrawSurface::~DDrawSurface() {
	Unlock();
	lpdds->Release();
}

bool DDrawSurface::Lock(VBitmap *pvbm) {
	HRESULT res;
	DDSURFACEDESC ddsd;

	if (fIsLocked)
		return false;

	// *sigh*...

	memset(&ddsd, 0, sizeof ddsd);
	ddsd.dwSize = sizeof ddsd;

	while(DD_OK != (res = lpdds->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR|DDLOCK_WRITEONLY/*|DDLOCK_NOSYSLOCK*/, NULL))) {
//	while(DD_OK != (res = lpdds->Lock(NULL, &ddsd, DDLOCK_WRITEONLY | DDLOCK_NOSYSLOCK, NULL))) {

		if (res == DDERR_SURFACELOST) {
			res = lpdds->Restore();

			if (res != DD_OK)
				return false;
		} else if (res == DDERR_WASSTILLDRAWING)
			continue;

		// WinNT is lame -- doesn't like NOSYSLOCK.

		res = lpdds->Lock(NULL, &ddsd, DDLOCK_WRITEONLY, NULL );

		if (res == DD_OK)
			break;

		if (res == DDERR_SURFACELOST) {

			// dammit!!!!

			res = lpdds->Restore();

			if (res != DD_OK)
				return false;

		} else if (res != DDERR_WASSTILLDRAWING) {
			return false;
		}
	}

	// surface locked... translate DDSURFACEDESC struct to VBitmap

	pvbm->data		= (Pixel *)ddsd.lpSurface;
	pvbm->palette	= NULL;
	pvbm->w			= ddsd.dwWidth;
	pvbm->h			= ddsd.dwHeight;
	pvbm->pitch		= ddsd.lPitch;
	pvbm->depth		= ddsd.ddpfPixelFormat.dwRGBBitCount;
	pvbm->modulo	= ddsd.lPitch - (ddsd.dwWidth*ddsd.ddpfPixelFormat.dwRGBBitCount)/8;
	pvbm->size		= pvbm->pitch * pvbm->h;
	pvbm->offset	= 0;

	fIsLocked = true;

	return true;
}

bool DDrawSurface::LockInverted(VBitmap *pvbm) {
	if (!Lock(pvbm))
		return false;

	pvbm->modulo	= pvbm->modulo-2*pvbm->pitch;
	pvbm->pitch		= -pvbm->pitch;
	pvbm->data		= (Pixel *)((char *)pvbm->data + pvbm->size + pvbm->pitch);

	return true;
}

void DDrawSurface::Unlock() {

	if (!fIsLocked)
		return;

	// not like we care if an unlock fails...

	lpdds->Unlock(NULL);

	fIsLocked = false;
}

void DDrawSurface::SetColorKey(COLORREF rgb) {
	DDCOLORKEY ddck;

	ddck.dwColorSpaceLowValue = rgb;
	ddck.dwColorSpaceHighValue = rgb;

	lpdds->SetColorKey(DDCKEY_DESTOVERLAY, &ddck);
}

void DDrawSurface::MoveOverlay(long x, long y) {
	lpdds->SetOverlayPosition(x, y);
}

void DDrawSurface::SetOverlayPos(RECT *pr) {
	RECT r2 = *pr;
	HRESULT res;

//	--r2.right;
//	--r2.bottom;

	res = lpdds->UpdateOverlay(NULL, lpddsPrimary, &r2, DDOVER_SHOW, NULL);
	if (res == DDERR_SURFACELOST) {
		res = lpdds->Restore();

		if (!FAILED(res))
			res = lpdds->UpdateOverlay(NULL, lpddsPrimary, &r2, DDOVER_SHOW, NULL);
	}
}

IDirectDrawSurface *DDrawSurface::getSurface() {
	return lpdds;
}
