//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: XBox win32 replacements - Mocks trivial windows flow
//
//=============================================================================//

#include "pch_tier0.h"
#include "xbox/xbox_win32stubs.h"
#include "tier0/memdbgon.h"

// On the 360, threads can run on any of 6 logical processors
DWORD g_dwProcessAffinityMask = 0x3F;

#define HWND_MAGIC	0x12345678

struct xWndClass_t
{
	char*			pClassName;
	WNDPROC			wndProc;
	xWndClass_t*	pNext;
};

struct xWnd_t
{
	xWndClass_t*	pWndClass;
	int				x;
	int				y;
	int				w;
	int				h;
	long			windowLongs[GWL_MAX];
	int				show;
	int				nMagic;
	xWnd_t*			pNext;
};

static xWndClass_t*	g_pWndClasses;
static xWnd_t*		g_pWnds;
static HWND			g_focusWindow;
 
inline bool IsWndValid( HWND hWnd )
{
	if ( !hWnd || ((xWnd_t*)hWnd)->nMagic != HWND_MAGIC )
		return false;
	return true;
}

int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
	XBX_Error( lpText );
	Assert( 0 );

	return (0);
}

LONG GetWindowLong(HWND hWnd, int nIndex)
{
	LONG	oldLong;

	if ( !IsWndValid( hWnd ) )
		return 0;

	switch (nIndex)
	{
		case GWL_WNDPROC:
		case GWL_USERDATA:
		case GWL_STYLE:
		case GWL_EXSTYLE:
			oldLong = ((xWnd_t*)hWnd)->windowLongs[nIndex];
			break;
		default:
			// not implemented
			Assert( 0 );
			return 0;
	}

	return oldLong;
}

LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex)
{
	UINT idx;

	switch ( nIndex )
	{
	case GWLP_WNDPROC:
		idx = GWL_WNDPROC;
		break;
	case GWLP_USERDATA:
		idx = GWL_USERDATA;
		break;
	default:
		// not implemented
		Assert(0);
		return 0;
	}

	return GetWindowLong( hWnd, idx );
}

LONG_PTR GetWindowLongPtrW(HWND hWnd, int nIndex)
{
	AssertMsg( false, "GetWindowLongPtrW does not exist on Xbox 360." );
	return GetWindowLongPtr( hWnd, nIndex );
}


LONG SetWindowLong(HWND hWnd, int nIndex, LONG dwNewLong)
{
	LONG	oldLong;

	if ( !IsWndValid( hWnd ) )
		return 0;

	switch ( nIndex )
	{
		case GWL_WNDPROC:
		case GWL_USERDATA:
		case GWL_STYLE:
			oldLong = ((xWnd_t*)hWnd)->windowLongs[nIndex];
			((xWnd_t*)hWnd)->windowLongs[nIndex] = dwNewLong;
			break;
		default:
			// not implemented
			Assert( 0 );
			return 0;
	}

	return oldLong;
}

LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong)
{
	UINT idx;

	switch ( nIndex )
	{
	case GWLP_WNDPROC:
		idx = GWL_WNDPROC;
		break;
	case GWLP_USERDATA:
		idx = GWL_USERDATA;
		break;
	default:
		// not implemented
		Assert( 0 );
		return 0;
	}

	return SetWindowLong( hWnd, idx, dwNewLong );
}

LONG_PTR SetWindowLongPtrW(HWND hWnd, int nIndex, LONG_PTR dwNewLong)
{
	AssertMsg( false, "SetWindowLongPtrW does not exist on Xbox 360." );
	return SetWindowLongPtr( hWnd, nIndex, dwNewLong  );
}

HWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)
{
	// find classname
	xWndClass_t* pWndClass = g_pWndClasses;
	while ( pWndClass )
	{
		if ( !stricmp( lpClassName, pWndClass->pClassName ) )
			break;
		pWndClass = pWndClass->pNext;
	}
	if ( !pWndClass )
	{
		// no such class
		return (HWND)NULL;
	}

	// allocate and setup
	xWnd_t* pWnd = new xWnd_t;
	memset( pWnd, 0, sizeof(xWnd_t) );
	pWnd->pWndClass = pWndClass;
	pWnd->windowLongs[GWL_WNDPROC] = (LONG)pWndClass->wndProc;
	pWnd->windowLongs[GWL_STYLE] = dwStyle;
	pWnd->x = x;
	pWnd->y = y;
	pWnd->w = nWidth;
	pWnd->h = nHeight;
	pWnd->nMagic = HWND_MAGIC;

	// link into list
	pWnd->pNext = g_pWnds;
	g_pWnds = pWnd;

	// force the focus
	g_focusWindow = (HWND)pWnd;

	// send the expected message sequence
	SendMessage( (HWND)pWnd, WM_CREATE, 0, 0 );
	SendMessage( (HWND)pWnd, WM_ACTIVATEAPP, TRUE, 0 );

	return (HWND)pWnd;
}

HWND CreateWindowEx(DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)
{
	return CreateWindow( lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam );
}

BOOL DestroyWindow( HWND hWnd )
{
	if ( !IsWndValid( hWnd ) )
		return FALSE;

	xWnd_t*	pPrev = g_pWnds;
	xWnd_t*	pCur = g_pWnds;

	while ( pCur )
	{
		if ( pCur == (xWnd_t*)hWnd )
		{
			if ( pPrev == g_pWnds )
			{
				// at head of list, fixup
				g_pWnds = pCur->pNext;
			}
			else
			{
				// remove from chain
				pPrev->pNext = pCur->pNext;
			}
			pCur->nMagic = 0;
			delete pCur;
			break;
		}

		// advance through list
		pPrev = pCur;
		pCur = pCur->pNext;
	}

	return TRUE;
}

ATOM RegisterClassEx(CONST WNDCLASSEX *lpwcx)
{
	// create
	xWndClass_t* pWndClass = new xWndClass_t;
	memset(pWndClass, 0, sizeof(xWndClass_t));
	pWndClass->pClassName = new char[strlen(lpwcx->lpszClassName)+1];
	strcpy(pWndClass->pClassName, lpwcx->lpszClassName);
	pWndClass->wndProc = lpwcx->lpfnWndProc;

	// insert into list
	pWndClass->pNext = g_pWndClasses;
	g_pWndClasses = pWndClass;

	return (ATOM)pWndClass;
}

ATOM RegisterClass(CONST WNDCLASS *lpwc)
{
	// create
	xWndClass_t* pWndClass = new xWndClass_t;
	memset(pWndClass, 0, sizeof(xWndClass_t));
	pWndClass->pClassName = new char[strlen(lpwc->lpszClassName)+1];
	strcpy(pWndClass->pClassName, lpwc->lpszClassName);
	pWndClass->wndProc = lpwc->lpfnWndProc;

	// insert into list
	pWndClass->pNext = g_pWndClasses;
	g_pWndClasses = pWndClass;

	return (ATOM)pWndClass;
}

HWND GetFocus(VOID)
{
	if ( !IsWndValid( g_focusWindow ) )
		return NULL;

	return g_focusWindow;
}

HWND SetFocus( HWND hWnd )
{
	HWND hOldFocus = g_focusWindow;

	if ( IsWndValid( hWnd ) )
	{
		g_focusWindow = hWnd;
	}

	return hOldFocus;
}


LRESULT CallWindowProc(WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	return (lpPrevWndFunc(hWnd, Msg, wParam, lParam));
}

int GetSystemMetrics(int nIndex)
{
	XVIDEO_MODE videoMode;
	XGetVideoMode( &videoMode );
	// default to having the backbuffer the same as the mode resolution.
	int nFrameBufferWidth, nFrameBufferHeight;
	nFrameBufferWidth =  videoMode.dwDisplayWidth;
	nFrameBufferHeight = videoMode.dwDisplayHeight;

	// override for cases where we need to have a different backbuffer either for memory reasons
	// or for dealing with anamorphic modes.
	if ( !videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 640 && videoMode.dwDisplayHeight == 576 )
	{
		// PAL normal
		nFrameBufferWidth = 640;
		nFrameBufferHeight = 480;
	}
	else if ( videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 640 && videoMode.dwDisplayHeight == 576 )
	{
		// PAL widescreen
		nFrameBufferWidth = 848;
		nFrameBufferHeight = 480;
	}
	else if ( videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 640 && videoMode.dwDisplayHeight == 480 )
	{
		// anamorphic
		nFrameBufferWidth = 848;
		nFrameBufferHeight = 480;
	}
	else if ( videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 1024 && videoMode.dwDisplayHeight == 768 )
	{
		// anamorphic
		nFrameBufferWidth = 1280;
		nFrameBufferHeight = 720;
	}
	else if ( videoMode.dwDisplayWidth == 1280 && videoMode.dwDisplayHeight == 760 )
	{
		nFrameBufferWidth = 1280;
		nFrameBufferHeight = 720;
	}
	else if ( videoMode.dwDisplayWidth == 1280 && videoMode.dwDisplayHeight == 768 )
	{
		nFrameBufferWidth = 1280;
		nFrameBufferHeight = 720;
	}
	else if ( !videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 1280 && videoMode.dwDisplayHeight == 1024 )
	{
		nFrameBufferWidth = 1024;
		nFrameBufferHeight = 768;
	}
	else if ( videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 1280 && videoMode.dwDisplayHeight == 1024 )
	{
		// anamorphic
		nFrameBufferWidth = 1280;
		nFrameBufferHeight = 720;
	}
	else if ( videoMode.dwDisplayWidth == 1360 && videoMode.dwDisplayHeight == 768 )
	{
		nFrameBufferWidth = 1280;
		nFrameBufferHeight = 720;
	}
	else if ( videoMode.dwDisplayWidth == 1920 && videoMode.dwDisplayHeight == 1080 )
	{
		nFrameBufferWidth = 1280;
		nFrameBufferHeight = 720;
	}

	switch ( nIndex )
	{
		case SM_CXFIXEDFRAME:
		case SM_CYFIXEDFRAME:
		case SM_CYSIZE:
			return 0;
		case SM_CXSCREEN:
			return nFrameBufferWidth;
		case SM_CYSCREEN:
			return nFrameBufferHeight;
	}

	// not implemented
	Assert( 0 );
	return 0;
}

BOOL ShowWindow(HWND hWnd, int nCmdShow)
{
	if ( !IsWndValid( hWnd ) )
		return FALSE;

	((xWnd_t*)hWnd)->show = nCmdShow;

	if ((nCmdShow == SW_SHOWDEFAULT) || (nCmdShow == SW_SHOWNORMAL) || (nCmdShow == SW_SHOW))
		g_focusWindow = hWnd;

	return TRUE;
}

LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	if ( !IsWndValid( hWnd ) )
		return 0L;

	xWnd_t* pWnd    = (xWnd_t*)hWnd;
	WNDPROC wndProc = (WNDPROC)pWnd->windowLongs[GWL_WNDPROC];
	Assert( wndProc );
	LRESULT result  = wndProc(hWnd, Msg, wParam, lParam);

	return result;
}

LRESULT	SendMessageTimeout( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout, PDWORD_PTR lpdwResult )
{
	*lpdwResult = SendMessage( hWnd, Msg, wParam, lParam );

	return -1;
}

BOOL GetClientRect(HWND hWnd, LPRECT lpRect)
{
	if ( !IsWndValid( hWnd ) )
		return FALSE;

	xWnd_t* pWnd = (xWnd_t*)hWnd;
	lpRect->left = 0;
	lpRect->top = 0;
	lpRect->right  = pWnd->w;
	lpRect->bottom = pWnd->h;

	return TRUE;
}

int GetDeviceCaps(HDC hdc, int nIndex)
{
	switch (nIndex)
	{
		case HORZRES:
			return GetSystemMetrics( SM_CXSCREEN );
		case VERTRES:
			return GetSystemMetrics( SM_CYSCREEN );
		case VREFRESH:
			return 60;  
	}

	Assert( 0 );
	return 0;
}

BOOL SetWindowPos( HWND hWnd, HWND hWndInsertAfter, int x, int y, int cx, int cy, UINT uFlags )
{
	if ( !IsWndValid( hWnd ) )
		return FALSE;

	xWnd_t* pWnd = (xWnd_t*)hWnd;

	if ( !( uFlags & SWP_NOMOVE ) )
	{
		pWnd->x = x;
		pWnd->y = y;
	}

	if ( !( uFlags & SWP_NOSIZE ) )
	{
		pWnd->w = cx;
		pWnd->h = cy;
	}

	return TRUE;
}

int XBX_unlink( const char* filename )
{	
	bool bSuccess = DeleteFile( filename ) != 0;
	if ( !bSuccess )
	{
		if ( GetLastError() == ERROR_FILE_NOT_FOUND )
		{
			// not a real failure
			return 0;
		}
	}
	// 0 = sucess, -1 = failure
	return bSuccess ? 0 : -1;
}

int XBX_mkdir( const char *pszDir )
{
	char			dirPath[MAX_PATH];
	char*			ptr;
	BOOL			bSuccess;

	// prime and skip to first seperator after the drive path
	// must create directory one path at a time
	bSuccess = false;
	strcpy( dirPath, pszDir );
	ptr = strchr( dirPath, '\\' );
	while ( ptr )
	{		
		ptr = strchr( ptr+1, '\\' );
		if ( ptr )
		{
			*ptr = '\0';
			bSuccess = CreateDirectory( dirPath, XBOX_DONTCARE );
			if ( !bSuccess && GetLastError() == ERROR_ALREADY_EXISTS )
			{
				// not a real error
				bSuccess = true;
			}
			*ptr = '\\';
		}
	}

	return ( bSuccess ? 0 : -1 );
}

char *XBX_getcwd( char *buf, size_t size )
{
	if ( !buf )
	{
		buf = (char*)malloc( 4 );
	}
	strncpy( buf, "D:", size );
	return buf;
}

int XBX_access( const char *path, int mode )
{
	if ( !path )
	{
		return -1;
	}

	// get the fatx attributes
	DWORD dwAttr = GetFileAttributes( path );
	if ( dwAttr == (DWORD)-1 )
	{
		return -1;
	}

	if ( mode == 0 )
	{
		// is file exist?
		return 0;
	}
	else if ( mode == 2 )
	{
		// is file write only?
		return -1;
	}
	else if ( mode == 4 )
	{
		// is file read only?
		if ( dwAttr & FILE_ATTRIBUTE_READONLY )
			return 0;
		else
			return -1;
	}
	else if ( mode == 6 )
	{
		// is file read and write?
		if ( !( dwAttr & FILE_ATTRIBUTE_READONLY ) )
			return 0;
		else
			return -1;
	}

	return -1;
}

DWORD XBX_GetCurrentDirectory( DWORD nBufferLength, LPTSTR lpBuffer )
{
	XBX_getcwd( lpBuffer, nBufferLength );
	return strlen( lpBuffer );
}

DWORD XBX_GetModuleFileName( HMODULE hModule, LPTSTR lpFilename, DWORD nSize )
{
	int		len;
	char	*pStr;
	char	*pEnd;
	char	xexName[MAX_PATH];

	if ( hModule == GetModuleHandle( NULL ) )
	{
		// isolate xex of command line
		pStr = GetCommandLine();
		if ( pStr )
		{
			// cull possible quotes around xex
			if ( pStr[0] == '\"' )
			{
				pStr++;
				pEnd = strchr( pStr, '\"' );
				if ( !pEnd )
				{
					// no ending matching quote
					return 0;
				}
			}
			else
			{
				// find possible first argument
				pEnd = strchr( lpFilename, ' ' );
				if ( !pEnd )
				{
					pEnd = pStr+strlen( pStr );
				}
			}
			len = pEnd-pStr;
			memcpy( xexName, pStr, len );
			xexName[len] = '\0';

			len = _snprintf( lpFilename, nSize, "D:\\%s", xexName );
			if ( len == -1 )
				lpFilename[nSize-1] = '\0';

			return strlen( lpFilename );
		}
	}
	return 0;
}