/*
 *	IME Keyboard Layout
 *
 *	Copyright 2000 Hidenori Takeshima
 */

#include "config.h"

#include <string.h>
#include "winbase.h"
#include "windef.h"
#include "wingdi.h"
#include "winuser.h"
#include "winerror.h"
#include "winnls.h"
#include "winreg.h"
#include "immddk.h"

#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(imm);

#include "imm_private.h"

static const char	IMM32_szRegKL[] =
	"System\\CurrentControlSet\\Control\\keyboard layouts";
static const char	IMM32_szIME_File[] = "IME file";
static const char	IMM32_szLayout_File[] = "layout file";

static IMM32_IMEKL*	IMM32_pklFirst = NULL;


static LONG IMM32_RegOpenKey( HKL hkl, PHKEY phkRet )
{
	CHAR	szRegPath[ sizeof(IMM32_szRegKL)+16 ];

	wsprintfA( szRegPath, "%s\\%08x", IMM32_szRegKL, (unsigned)hkl );
	return RegOpenKeyExA( HKEY_LOCAL_MACHINE, szRegPath,
			      0, KEY_READ, phkRet );
}

static LONG IMM32_RegCreateKey( HKL hkl, PHKEY phkRet,
				LPDWORD lpdwDisposition )
{
	CHAR	szRegPath[ sizeof(IMM32_szRegKL)+16 ];

	wsprintfA( szRegPath, "%s\\%08x", IMM32_szRegKL, (unsigned)hkl );
	return RegCreateKeyExA( HKEY_LOCAL_MACHINE, szRegPath,
				0, "REG_SZ",
				REG_OPTION_NON_VOLATILE,
				KEY_ALL_ACCESS, NULL,
				phkRet, lpdwDisposition );
}

static DWORD IMM32_GetIMEFileName( HKL hkl, LPSTR lpBuf, INT nBufLen )
{
	HKEY	hkey;
	LONG	nError;
	DWORD	dwType;
	DWORD	cbData;
	CHAR	szValueName[ sizeof(IMM32_szIME_File) ];

	TRACE( "hkl = %08x\n", (unsigned)hkl );

	nError = IMM32_RegOpenKey( hkl, &hkey );
	if ( nError != ERROR_SUCCESS )
	{
		SetLastError( nError );
		return 0;
	}
	memcpy( szValueName, IMM32_szIME_File, sizeof(IMM32_szIME_File) );

	nError = RegQueryValueExA( hkey, szValueName, NULL,
				   &dwType, NULL, &cbData );
	if ( nError == ERROR_SUCCESS && dwType != REG_SZ )
		nError = ERROR_FILE_NOT_FOUND; /* FIXME? */
	if ( nError == ERROR_SUCCESS && lpBuf != NULL && nBufLen != 0 )
	{
		if ( nBufLen < (INT)cbData )
			nError = ERROR_INSUFFICIENT_BUFFER;
		else
			nError = RegQueryValueExA( hkey, szValueName, NULL,
						   &dwType, lpBuf, &cbData );
	}

	RegCloseKey( hkey );

	if ( nError != ERROR_SUCCESS )
	{
		SetLastError( nError );
		return 0;
	}

	return cbData;
}


static
BOOL IMM32_GetIMEHandlersA( HINSTANCE hInstIME,
			    struct IMM32_IME_EXPORTED_HANDLERS* phandlers )
{
	BOOL	fError;

	#define	FE(name)						\
		phandlers->p##name = (IMM32_p##name)			\
			GetProcAddress(hInstIME,#name);			\
		if ( phandlers->p##name == NULL ) fError = TRUE;
	#define	FE_(name)						\
		phandlers->p##name.A = (IMM32_p##name##A)		\
			GetProcAddress(hInstIME,#name);			\
		if ( phandlers->p##name.A == NULL ) fError = TRUE;

	fError = FALSE;

	FE_(ImeInquire)
	FE_(ImeConfigure)
	FE_(ImeConversionList)
	FE(ImeDestroy)
	FE_(ImeEnumRegisterWord)
	FE_(ImeGetRegisterWordStyle)
	FE_(ImeEscape)
	FE(ImeProcessKey)
	FE_(ImeRegisterWord)
	FE(ImeSelect)
	FE(ImeSetActiveContext)
	FE_(ImeSetCompositionString)
	FE(ImeToAsciiEx)
	FE_(ImeUnregisterWord)
	FE(NotifyIME)

	if ( fError )
		return FALSE;

	FE_(ImeGetImeMenuItems)

	#undef	FE
	#undef	FE_

	return TRUE;
}

static
BOOL IMM32_GetIMEHandlersW( HINSTANCE hInstIME,
			    struct IMM32_IME_EXPORTED_HANDLERS* phandlers )
{
	BOOL	fError;

	#define	FE(name)						\
		phandlers->p##name = (IMM32_p##name)			\
			GetProcAddress(hInstIME,#name);			\
		if ( phandlers->p##name == NULL ) fError = TRUE;
	#define	FE_(name)						\
		phandlers->p##name.W = (IMM32_p##name##W)		\
			GetProcAddress(hInstIME,#name);			\
		if ( phandlers->p##name.W == NULL ) fError = TRUE;

	fError = FALSE;

	FE_(ImeInquire)
	FE_(ImeConfigure)
	FE_(ImeConversionList)
	FE(ImeDestroy)
	FE_(ImeEnumRegisterWord)
	FE_(ImeGetRegisterWordStyle)
	FE_(ImeEscape)
	FE(ImeProcessKey)
	FE_(ImeRegisterWord)
	FE(ImeSelect)
	FE(ImeSetActiveContext)
	FE_(ImeSetCompositionString)
	FE(ImeToAsciiEx)
	FE_(ImeUnregisterWord)
	FE(NotifyIME)

	if ( fError )
		return FALSE;

	FE_(ImeGetImeMenuItems)

	#undef	FE
	#undef	FE_

	return TRUE;
}


static IMM32_IMEKL* IMM32_LoadIME( HKL hkl )
{
	IMM32_IMEKL*	pkl = NULL;
	BOOL	fInitialized = FALSE;
	CHAR*	pszFileName = NULL;
	DWORD	dwBufLen;
	IMM32_pImeInquireA	pImeInquire;
	IMM32_pImeDestroy	pImeDestroy = NULL;
	CHAR	szUIClassName[ (IMM32_UICLASSNAME_MAX+1)*sizeof(WCHAR) ];

	dwBufLen = IMM32_GetIMEFileName( hkl, NULL, 0 );
	if ( dwBufLen == 0 )
		goto err;
	pszFileName = (CHAR*)IMM32_HeapAlloc( 0, sizeof(CHAR)*(dwBufLen+1) );
	if ( pszFileName == NULL )
	{
		SetLastError( ERROR_OUTOFMEMORY );
		goto err;
	}
	if ( !IMM32_GetIMEFileName( hkl, pszFileName, dwBufLen + 1 ) )
		goto err;

	pkl = (IMM32_IMEKL*)
		IMM32_HeapAlloc( HEAP_ZERO_MEMORY, sizeof(IMM32_IMEKL) );
	if ( pkl == NULL )
	{
		SetLastError( ERROR_OUTOFMEMORY );
		goto err;
	}

	pkl->pNext = NULL;
	pkl->hkl = hkl;
	pkl->hInstIME = LoadLibraryA( pszFileName );
	if ( pkl->hInstIME == (HINSTANCE)NULL )
		goto err;

	pImeInquire = (IMM32_pImeInquireA)GetProcAddress
					( pkl->hInstIME, "ImeInquire" );
	pImeDestroy = (IMM32_pImeDestroy)GetProcAddress
					( pkl->hInstIME, "ImeDestroy" );
	if ( pImeInquire == NULL || pImeDestroy == NULL )
		goto err;

	if ( !pImeInquire( &(pkl->imeinfo), szUIClassName, NULL ) )
	{
		SetLastError( ERROR_DLL_INIT_FAILED ); /* FIXME? */
		goto err;
	}
	fInitialized = TRUE;

	/* FIXME: Is this correct??? */
	if ( pkl->imeinfo.fdwProperty & IME_PROP_UNICODE )
		pkl->fUnicode = TRUE;
	else
		pkl->fUnicode = FALSE;

	if ( pkl->fUnicode )
	{
		if ( !IMM32_GetIMEHandlersW( pkl->hInstIME, &pkl->handlers ) )
			goto err;
		memcpy( pkl->UIClassName.W, szUIClassName,
			IMM32_UICLASSNAME_MAX*sizeof(WCHAR) );
		TRACE( "UI class name(Unicode): %s\n",
			debugstr_w(pkl->UIClassName.W) );
	}
	else
	{
		if ( !IMM32_GetIMEHandlersA( pkl->hInstIME, &pkl->handlers ) )
			goto err;
		memcpy( pkl->UIClassName.A, szUIClassName,
			IMM32_UICLASSNAME_MAX*sizeof(CHAR) );
		TRACE( "UI class name(ASCII): %s\n",
			debugstr_a(pkl->UIClassName.A) );
	}

	/* The IME is loaded successfully. */
	pkl->pszIMEFileName = pszFileName; pszFileName = NULL;
	return pkl;

err:
	IMM32_HeapFree( pszFileName );
	if ( pkl != NULL )
	{
		if ( pkl->hInstIME != (HINSTANCE)NULL )
		{
			if ( fInitialized )
				(void)pImeDestroy(0);
			FreeLibrary( pkl->hInstIME );
		}
		IMM32_HeapFree( pkl );
	}

	return NULL;
}


const IMM32_IMEKL* IMM32_GetIME( HKL hkl )
{
	IMM32_IMEKL*	pkl;

	IMM32_Lock();

	pkl = IMM32_pklFirst;
	while ( pkl != NULL )
	{
		if ( pkl->hkl == hkl )
			goto end;
		pkl = pkl->pNext;
	}

	pkl = IMM32_LoadIME( hkl );
	if ( pkl != NULL )
	{
		pkl->pNext = IMM32_pklFirst;
		IMM32_pklFirst = pkl;
	}

end:
	IMM32_Unlock();

	return pkl;
}

void IMM32_UnloadAllIMEs( void )
{
	IMM32_IMEKL*	pkl;
	IMM32_IMEKL*	pklNext;

	IMM32_Lock();

	pkl = IMM32_pklFirst;
	while ( pkl != NULL )
	{
		TRACE( "hkl = %08x\n", (unsigned)pkl->hkl );

		pklNext = pkl->pNext;
		(void)pkl->handlers.pImeDestroy(0);
		FreeLibrary( pkl->hInstIME );
		IMM32_HeapFree( pkl->pszIMEFileName );
		IMM32_HeapFree( pkl );
		pkl = pklNext;
	}
	IMM32_pklFirst = NULL;

	IMM32_Unlock();
}



/***********************************************************************
 *		ImmInstallIMEA (IMM32.@)
 */
HKL WINAPI ImmInstallIMEA(
	LPCSTR lpszIMEFileName, LPCSTR lpszLayoutText)
{
	HKL	hkl;
	DWORD	dwLCID;
	DWORD	dwTryCount;
	LONG	nError;
	HKEY	hkey;
	DWORD	dwDisposition;
	DWORD	cbData;
	CHAR	szValueName[ sizeof(IMM32_szIME_File) ];

	TRACE("(%s, %s)\n",
		debugstr_a(lpszIMEFileName), debugstr_a(lpszLayoutText) );

	dwLCID = (DWORD)GetThreadLocale();
	dwTryCount = 0;

	FIXME( "IMEs don't work correctly now since\n"
	       "wine/windows/input.c doesn't handle HKL correctly.\n" );

	while ( 1 )
	{
		hkl = (HKL)(((0xe000|dwTryCount)<<16) | (dwLCID));

		nError = IMM32_RegCreateKey( hkl, &hkey, &dwDisposition );
		if ( nError != ERROR_SUCCESS )
			break;

		memcpy( szValueName, IMM32_szIME_File,
			sizeof(IMM32_szIME_File) );

		nError = RegQueryValueExA( hkey, szValueName, NULL,
					   NULL, NULL, &cbData );
		if ( nError == ERROR_SUCCESS && cbData > 0 )
		{
			RegCloseKey( hkey );

			/* try to assign an other HKL. */
			dwTryCount ++;
			if ( dwTryCount >= 0x100 )
			{
				nError = ERROR_ACCESS_DENIED; /* FIXME */
				break;
			}
			continue;
		}

		nError = RegSetValueExA( hkey, IMM32_szIME_File, 0,
					 REG_SZ, lpszIMEFileName,
					 strlen(lpszIMEFileName)+1 );
		if ( nError == ERROR_SUCCESS )
			nError = RegSetValueExA( hkey, IMM32_szLayout_File, 0,
						 REG_SZ, lpszLayoutText,
						 strlen(lpszLayoutText)+1 );
		RegCloseKey( hkey );
		break;
	}

	if ( nError != ERROR_SUCCESS )
	{
		SetLastError( nError );
		return (HKL)NULL;
	}

	return hkl;
}

/***********************************************************************
 *		ImmInstallIMEW (IMM32.@)
 */
HKL WINAPI ImmInstallIMEW(
	LPCWSTR lpszIMEFileName, LPCWSTR lpszLayoutText)
{
	LPSTR	lpszParam1;
	LPSTR	lpszParam2;
	HKL	hkl;

	TRACE("(%s, %s)\n",
		debugstr_w(lpszIMEFileName), debugstr_w(lpszLayoutText) );

	lpszParam1 = IMM32_strdupWtoA( lpszIMEFileName );
	lpszParam2 = IMM32_strdupWtoA( lpszLayoutText );
	if ( ( lpszParam1 == NULL ) || ( lpszParam2 == NULL ) )
	{
		SetLastError( ERROR_OUTOFMEMORY );
		hkl = (HKL)NULL;
	}
	else
	{
		hkl = ImmInstallIMEA( lpszParam1, lpszParam2 );
	}
	IMM32_HeapFree( lpszParam1 );
	IMM32_HeapFree( lpszParam2 );

	return hkl;
}


/***********************************************************************
 *		ImmIsIME (IMM32.@)
 */
BOOL WINAPI ImmIsIME(HKL hkl)
{
	const IMM32_IMEKL*	pkl;

	TRACE("(0x%08x)\n", hkl);

	pkl = IMM32_GetIME( hkl );
	if ( pkl == NULL )
		return FALSE;

	return TRUE;
}


/***********************************************************************
 *		ImmGetIMEFileNameA (IMM32.@)
 */
UINT WINAPI ImmGetIMEFileNameA(HKL hkl, LPSTR lpszFileName, UINT uBufLen)
{
	const IMM32_IMEKL*	pkl;
	UINT			uIMEFileNameLen;

	TRACE("(%08x,%p,%u)\n",hkl,lpszFileName,uBufLen);

	pkl = IMM32_GetIME( hkl );
	if ( pkl == NULL )
		return 0;

	uIMEFileNameLen = strlen(pkl->pszIMEFileName);
	if ( uBufLen == 0 )
		return uIMEFileNameLen;
	if ( uBufLen <= uIMEFileNameLen )
	{
		SetLastError(ERROR_INSUFFICIENT_BUFFER);
		return 0;
	}
	memcpy( lpszFileName, pkl->pszIMEFileName,
		sizeof(CHAR)*(uIMEFileNameLen+1) );

	return uIMEFileNameLen;
}

/***********************************************************************
 *		ImmGetIMEFileNameW (IMM32.@)
 */
UINT WINAPI ImmGetIMEFileNameW(HKL hkl, LPWSTR lpszFileName, UINT uBufLen)
{
	const IMM32_IMEKL*	pkl;
	UINT			uIMEFileNameLen;

	TRACE("(%08x,%p,%u)\n",hkl,lpszFileName,uBufLen);

	pkl = IMM32_GetIME( hkl );
	if ( pkl == NULL )
		return 0;

	uIMEFileNameLen = IMM32_strlenAtoW(pkl->pszIMEFileName);
	if ( uBufLen == 0 )
		return uIMEFileNameLen;
	if ( uBufLen <= uIMEFileNameLen )
	{
		SetLastError(ERROR_INSUFFICIENT_BUFFER);
		return 0;
	}
	IMM32_strncpyAtoW( lpszFileName, pkl->pszIMEFileName, uBufLen );

	return uIMEFileNameLen;
}

/***********************************************************************
 *		ImmGetProperty (IMM32.@)
 */
DWORD WINAPI ImmGetProperty(HKL hkl, DWORD fdwIndex)
{
	const IMM32_IMEKL*	pkl;
	DWORD			dwRet;

	TRACE("(0x%08x, %ld)\n", hkl, fdwIndex);

	pkl = IMM32_GetIME( hkl );
	if ( pkl == NULL )
		return 0;

	switch ( fdwIndex )
	{
	case IGP_GETIMEVERSION:
		dwRet = IMEVER_0400;
		break;
	case IGP_PROPERTY:
		dwRet = pkl->imeinfo.fdwProperty;
		break;
	case IGP_CONVERSION:
		dwRet = pkl->imeinfo.fdwConversionCaps;
		break;
	case IGP_SENTENCE:
		dwRet = pkl->imeinfo.fdwSentenceCaps;
		break;
	case IGP_UI:
		dwRet = pkl->imeinfo.fdwUICaps;
		break;
	case IGP_SETCOMPSTR:
		dwRet = pkl->imeinfo.fdwSCSCaps;
		break;
	case IGP_SELECT:
		dwRet = pkl->imeinfo.fdwSelectCaps;
		break;
	default:
		FIXME("(0x%08x, %ld): invalid/unknown property.\n",
		      hkl, fdwIndex);
		SetLastError( ERROR_INVALID_PARAMETER );
		dwRet = 0;
	}

	return dwRet;
}


/***********************************************************************
 *		ImmEnumRegisterWordA (IMM32.@)
 */
UINT WINAPI ImmEnumRegisterWordA(
	HKL hkl, REGISTERWORDENUMPROCA lpfnEnumProc,
	LPCSTR lpszReading, DWORD dwStyle,
	LPCSTR lpszRegister, LPVOID lpData)
{
	const IMM32_IMEKL*	pkl;

	TRACE("(0x%08x, %p, %s, %ld, %s, %p)\n",
		hkl, lpfnEnumProc, 
		debugstr_a(lpszReading), dwStyle,
		debugstr_a(lpszRegister), lpData);

	pkl = IMM32_GetIME( hkl );
	if ( pkl == NULL )
		return 0;

	if ( pkl->fUnicode )
	{
		FIXME( "please implement UNICODE->ANSI\n" );
		SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
		return 0;
	}
	else
	{
		return pkl->handlers.pImeEnumRegisterWord.A
			( lpfnEnumProc, lpszReading, dwStyle,
			  lpszRegister, lpData );
	}
}

/***********************************************************************
 *		ImmEnumRegisterWordW (IMM32.@)
 */
UINT WINAPI ImmEnumRegisterWordW(
	HKL hkl, REGISTERWORDENUMPROCW lpfnEnumProc,
	LPCWSTR lpszReading, DWORD dwStyle,
	LPCWSTR lpszRegister, LPVOID lpData)
{
	const IMM32_IMEKL*	pkl;

	TRACE("(0x%08x, %p, %s, %ld, %s, %p): stub\n",
		hkl, lpfnEnumProc, 
		debugstr_w(lpszReading), dwStyle,
		debugstr_w(lpszRegister), lpData);

	pkl = IMM32_GetIME( hkl );
	if ( pkl == NULL )
		return 0;

	if ( pkl->fUnicode )
	{
		return pkl->handlers.pImeEnumRegisterWord.W
			( lpfnEnumProc, lpszReading, dwStyle,
			  lpszRegister, lpData );
	}
	else
	{
		FIXME( "please implement ANSI->UNICODE\n" );
		SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
		return 0;
	}
}


/***********************************************************************
 *		ImmGetRegisterWordStyleA (IMM32.@)
 */
UINT WINAPI ImmGetRegisterWordStyleA(
	HKL hkl, UINT nItem, LPSTYLEBUFA lpStyleBuf)
{
	const IMM32_IMEKL*	pkl;

	TRACE("(0x%08x, %d, %p)\n", hkl, nItem, lpStyleBuf);

	pkl = IMM32_GetIME( hkl );
	if ( pkl == NULL )
		return 0;

	if ( pkl->fUnicode )
	{
		FIXME( "please implement UNICODE->ANSI\n" );
		SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
		return 0;
	}
	else
	{
		return pkl->handlers.pImeGetRegisterWordStyle.A
			( nItem, lpStyleBuf );
	}
}

/***********************************************************************
 *		ImmGetRegisterWordStyleW (IMM32.@)
 */
UINT WINAPI ImmGetRegisterWordStyleW(
	HKL hkl, UINT nItem, LPSTYLEBUFW lpStyleBuf)
{
	const IMM32_IMEKL*	pkl;

	TRACE("(0x%08x, %d, %p)\n", hkl, nItem, lpStyleBuf);

	pkl = IMM32_GetIME( hkl );
	if ( pkl == NULL )
		return 0;

	if ( pkl->fUnicode )
	{
		return pkl->handlers.pImeGetRegisterWordStyle.W
			( nItem, lpStyleBuf );
	}
	else
	{
		FIXME( "please implement ANSI->UNICODE\n" );
		SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
		return 0;
	}
}


