//-----------------------------------------------------------------------------
// File: usefuldi.cpp
//
// Desc: Contains various DInput-specific utility classes and functions
//       to help the UI carry its operations more easily.
//
// Copyright (C) 1999-2001 Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------------------------

#include "common.hpp"

// don't want useful.cpp to use precompiled header
#include "useful.cpp"


BOOL IsObjectOnExcludeList(DWORD dwOfs)
{
	if (dwOfs == DIK_PREVTRACK ||
	    dwOfs == DIK_NEXTTRACK ||
	    dwOfs == DIK_MUTE ||
	    dwOfs == DIK_CALCULATOR ||
	    dwOfs == DIK_PLAYPAUSE ||
	    dwOfs == DIK_MEDIASTOP ||
	    dwOfs == DIK_VOLUMEDOWN ||
	    dwOfs == DIK_VOLUMEUP ||
	    dwOfs == DIK_WEBHOME ||
	    dwOfs == DIK_SLEEP ||
	    dwOfs == DIK_WEBSEARCH ||
	    dwOfs == DIK_WEBFAVORITES ||
	    dwOfs == DIK_WEBREFRESH ||
	    dwOfs == DIK_WEBSTOP ||
	    dwOfs == DIK_WEBFORWARD ||
	    dwOfs == DIK_WEBBACK ||
	    dwOfs == DIK_MYCOMPUTER ||
	    dwOfs == DIK_MAIL ||
	    dwOfs == DIK_MEDIASELECT ||
	    dwOfs == DIK_LWIN ||
	    dwOfs == DIK_RWIN ||
	    dwOfs == DIK_POWER ||
	    dwOfs == DIK_WAKE)
		return TRUE;

	return FALSE;
}

BOOL CALLBACK IncrementValPerObject(LPCDIDEVICEOBJECTINSTANCEW lpddoi, LPVOID pvRef)
{
	if (pvRef != NULL)
		++(*((int *)pvRef));
	return DIENUM_CONTINUE;
}

BOOL CALLBACK KeyboardIncrementValPerObject(LPCDIDEVICEOBJECTINSTANCEW lpddoi, LPVOID pvRef)
{
	if (pvRef != NULL && !IsObjectOnExcludeList(lpddoi->dwOfs))
		++(*((int *)pvRef));
	return DIENUM_CONTINUE;
}

BOOL CALLBACK FillDIDeviceObject(LPCDIDEVICEOBJECTINSTANCEW lpddoi, LPVOID pvRef)
{
	if (pvRef == NULL || lpddoi == NULL)
		return DIENUM_CONTINUE;

	DIDEVOBJSTRUCT &os = *((DIDEVOBJSTRUCT *)pvRef);
	assert(os.pdoi != NULL);
	assert(os.n < os.nObjects);
	if (os.pdoi != NULL && os.n < os.nObjects)
		os.pdoi[os.n++] = *lpddoi;

	return DIENUM_CONTINUE;
}

// This is a special EnumObjects() callback for keyboard type devices.  When we enumerate, dwOfs
// member of lpddoi is meaningless.  We need to take the middle 16 bits of dwType as dwOfs
// (also same as DIK_xxx).
BOOL CALLBACK FillDIKeyboardDeviceObject(LPCDIDEVICEOBJECTINSTANCEW lpddoi, LPVOID pvRef)
{
	if (pvRef == NULL || lpddoi == NULL || IsObjectOnExcludeList(lpddoi->dwOfs))
		return DIENUM_CONTINUE;

	DIDEVOBJSTRUCT &os = *((DIDEVOBJSTRUCT *)pvRef);
	assert(os.pdoi != NULL);
	assert(os.n < os.nObjects);
	if (os.pdoi != NULL && os.n < os.nObjects)
	{
		os.pdoi[os.n] = *lpddoi;
		os.pdoi[os.n].dwOfs = os.pdoi[os.n].dwType >> 8;
		wcscpy(os.pdoi[os.n].tszName, lpddoi->tszName);
		++os.n;
	}

	return DIENUM_CONTINUE;
}

HRESULT FillDIDeviceObjectStruct(DIDEVOBJSTRUCT &os, LPDIRECTINPUTDEVICE8W pDID)
{
	if (pDID == NULL)
		return E_FAIL;

	DIDEVICEINSTANCEW didi;
	didi.dwSize = sizeof(didi);
	pDID->GetDeviceInfo(&didi);

	HRESULT hr;
	if (LOBYTE(didi.dwDevType) == DI8DEVTYPE_KEYBOARD)
		hr = pDID->EnumObjects(KeyboardIncrementValPerObject, &os.nObjects,
				DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV);
	else
		hr = pDID->EnumObjects(IncrementValPerObject, &os.nObjects,
				DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV);
	if (FAILED(hr))
	{
		os.nObjects = 0;
		return hr;
	}

	if (os.nObjects == 0)
		return S_OK;

	if (os.pdoi != NULL)
		free(os.pdoi);
	os.pdoi = (DIDEVICEOBJECTINSTANCEW *)malloc(sizeof(DIDEVICEOBJECTINSTANCEW) * os.nObjects);
	if (os.pdoi == NULL)
	{
		os.nObjects = 0;
		return E_FAIL;
	}

	// Check if this device is a keyboard. If so, it needs special treatment.
	os.n = 0;
	if ((didi.dwDevType & 0xFF) == DI8DEVTYPE_KEYBOARD)
	{
		hr = pDID->EnumObjects(FillDIKeyboardDeviceObject, &os,
				DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV);
	} else {
		hr = pDID->EnumObjects(FillDIDeviceObject, &os,
				DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV);
	}

	if (FAILED(hr))
	{
		os.nObjects = 0;
		return hr;
	}

	assert(os.nObjects == os.n);
	os.nObjects = os.n;

	return S_OK;
}

LPTSTR AllocConfigureFlagStr(DWORD dwFlags)
{
	static const AFS_FLAG flag[] = {
#define f(F) { F, _T(#F) }
		f(DICD_EDIT), f(DICD_DEFAULT)
#undef f
	};
	static const int flags = sizeof(flag) / sizeof(AFS_FLAG);
	return AllocFlagStr(dwFlags, flag, flags);
}

LPTSTR AllocActionFlagStr(DWORD dwFlags)
{
	static const AFS_FLAG flag[] = {
#define f(F) { F, _T(#F) }
		f(DIA_FORCEFEEDBACK),
		f(DIA_APPMAPPED),
		f(DIA_APPNOMAP),
		f(DIA_NORANGE),
#undef f
	}; static const int flags = sizeof(flag) / sizeof(AFS_FLAG);
	return AllocFlagStr(dwFlags, flag, flags);
}

LPTSTR AllocActionHowFlagStr(DWORD dwFlags)
{
	static const AFS_FLAG flag[] = {
#define f(F) { F, _T(#F) }
		f(DIAH_UNMAPPED),
		f(DIAH_USERCONFIG),
		f(DIAH_APPREQUESTED),
		f(DIAH_HWAPP),
		f(DIAH_HWDEFAULT),
		f(DIAH_DEFAULT),
		f(DIAH_ERROR)
#undef f
	}; static const int flags = sizeof(flag) / sizeof(AFS_FLAG);
	return AllocFlagStr(dwFlags, flag, flags);
}

void CleanupActionFormatCopy(DIACTIONFORMATW &c)
{
	if (c.rgoAction != NULL)
	{
		for (DWORD i = 0; i < c.dwNumActions; i++)
			if (c.rgoAction[i].lptszActionName != NULL)
				free((LPTSTR)c.rgoAction[i].lptszActionName);
		free(c.rgoAction);
	}
	c.rgoAction = NULL;
}

HRESULT CopyActionFormat(DIACTIONFORMATW &to, const DIACTIONFORMATW &from)
{
	DWORD i;

	// copy all simple members
	to = from;

	// null copied pointers since we're going to duplicate them (makes sure cleanup works)
	to.rgoAction = NULL;

	// handle pointers/arrays/strings
	to.rgoAction = new DIACTIONW [to.dwNumActions];
	if (to.rgoAction == NULL)
		goto fail;

	// first null it all 
	memset(to.rgoAction, 0, sizeof(DIACTIONW) * to.dwNumActions);

	// now copy...
	for (i = 0; i < to.dwNumActions; i++)
	{
		// copy simple members
		to.rgoAction[i] = from.rgoAction[i];

		// handle pointers/arrays/strings
		to.rgoAction[i].lptszActionName = _wcsdup(from.rgoAction[i].lptszActionName);
		if (to.rgoAction[i].lptszActionName == NULL)
			goto fail;
	}

	return S_OK;
fail:
	CleanupActionFormatCopy(to);
	return E_OUTOFMEMORY;
}

LPDIACTIONFORMATW DupActionFormat(LPCDIACTIONFORMATW lpAcFor)
{
	if (!lpAcFor)
		return NULL;

	LPDIACTIONFORMATW pdup = new DIACTIONFORMATW;
	if (!pdup)
		return NULL;

	if (FAILED(CopyActionFormat(*pdup, *lpAcFor)))
	{
		delete pdup;
		return NULL;
	}

	return pdup;
}

void FreeActionFormatDup(LPDIACTIONFORMATW &lpAcFor)
{
	if (!lpAcFor)
		return;

	CleanupActionFormatCopy(*lpAcFor);
	delete lpAcFor;
	lpAcFor = NULL;
}

void TraceActionFormat(LPTSTR header, const DIACTIONFORMATW &acf)
{
#ifdef CFGUI__TRACE_ACTION_FORMATS
	tracescope(a, header);
	trace(_T("\n"));

	traceDWORD(acf.dwSize);
	traceDWORD(acf.dwActionSize);
	traceDWORD(acf.dwDataSize);
	traceDWORD(acf.dwNumActions);
	{tracescope(b, _T("acf.rgoAction Array\n"));
		for (DWORD i = 0; i < acf.dwNumActions; i++)
		{
			const DIACTIONW &a = acf.rgoAction[i];
			static TCHAR buf[MAX_PATH];
			_stprintf(buf, _T("Action %d\n"), i);
			{tracescope(c, buf);
				traceHEX(a.uAppData);
				traceDWORD(a.dwSemantic);
				LPTSTR str = AllocActionFlagStr(a.dwFlags);
				trace1(_T("a.dwFlags = %s\n"), str);
				free(str);
				traceWSTR(a.lptszActionName);
				traceUINT(a.uResIdString);
				traceDWORD(a.dwObjID);
				traceGUID(a.guidInstance);
				str = AllocActionHowFlagStr(a.dwHow);
				trace1(_T("a.dwHow = %s\n"), str);
				free(str);
			}
		}
	}
	traceGUID(acf.guidActionMap);
	traceDWORD(acf.dwGenre);
	traceDWORD(acf.dwBufferSize);
	traceLONG(acf.lAxisMin);
	traceLONG(acf.lAxisMax);
	traceHEX(acf.hInstString);
	traceHEX(acf.dwCRC);
	traceWSTR(acf.tszActionMap);
#endif
}

BOOL IsZeroOrInvalidColorSet(const DICOLORSET &cs)
{
	if (cs.dwSize < sizeof(DICOLORSET))
		return TRUE;

	const int colors = 8;
	D3DCOLOR color[colors] = {
		cs.cTextFore,
		cs.cTextHighlight,
		cs.cCalloutLine,
		cs.cCalloutHighlight,
		cs.cBorder,
		cs.cControlFill,
		cs.cHighlightFill,
		cs.cAreaFill
	};

	for (int i = 0; i < colors; i++)
		if (color[i])
			return FALSE;

	return TRUE;
}

// D3DCOLOR2COLORREF swaps the blue and red components since GDI and D3D store RGB in the opposite order.
// It also removes the alpha component as the GDI doesn't use that, and including it causes incorrect color.
COLORREF D3DCOLOR2COLORREF(D3DCOLOR c)
{
	LPBYTE pC = (LPBYTE)&c;

	return (COLORREF)((DWORD(*pC) << 16) + (DWORD(*(pC+1)) << 8) + DWORD(*(pC+2)));
}
