/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	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 3 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, see <http://www.gnu.org/licenses/>.
*/

/******************************************************************************
*
* FILE
*     $Archive: /Commando/Code/wwui/IMEManager.cpp $
*
* DESCRIPTION
*     Input Method Editor Manager for input of far east characters.
*
* PROGRAMMER
*     $Author: Denzil_l $
*
* VERSION INFO
*     $Revision: 6 $
*     $Modtime: 1/12/02 9:44p $
*
******************************************************************************/

#include "IMEManager.h"
#include "WWString.h"
#include "WWDebug.h"
#include <locale.h>
#include <mbctype.h>

#if defined(_MSC_VER)
#pragma warning(push, 3)
#endif

#include <algorithm>
#include <memory>

#if defined(_MSC_VER)
#pragma warning(pop)
#endif

#pragma comment(lib, "imm32.lib")

namespace IME {

/******************************************************************************
*
* NAME
*     IMEManager::Create
*
* DESCRIPTION
*     Create an IME manager instance
*
* INPUTS
*     Window - HWND to associate IME manager instance with.
*
* RESULT
*     IMEManager - Pointer to IME manager
*
******************************************************************************/

IMEManager* IMEManager::Create(HWND hwnd)
	{
	if (hwnd)
		{
		IMEManager* ime = new IMEManager;

		if (ime)
			{
			if (ime->FinalizeCreate(hwnd))
				{
				return ime;
				}

			ime->Release_Ref();
			}
		}

	return NULL;
	}


/******************************************************************************
*
* NAME
*     IMEManager::IMEManager
*
* DESCRIPTION
*     Constructor
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

IMEManager::IMEManager() :
		mHWND(NULL),
		mHIMC(NULL),
		mDisabledHIMC(NULL),
		mDisableCount(0),
		mCodePage(CP_ACP),
		mIMEProperties(0),
		mHilite(true),
		mStartCandListFrom1(true),
		mOSCanUnicode(false),
		mUseUnicode(false),
		mInComposition(false)
	{
	WWDEBUG_SAY(("IMEManager: Instantiated\n"));

	mLangID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);

	ResetComposition();
	mResultString[0] = 0;
	}


/******************************************************************************
*
* NAME
*     IMEManager::~IMEManager
*
* DESCRIPTION
*     Destructor
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

IMEManager::~IMEManager()
	{
	WWDEBUG_SAY(("IMEManager: destroyed\n"));

	if (mHIMC)
		{
		ImmAssociateContext(mHWND, mDefaultHIMC);
		ImmDestroyContext(mHIMC);
		}

	mCandidateColl.clear();
	}


/******************************************************************************
*
* NAME
*     IMEManager::FinalizeCreate
*
* DESCRIPTION
*     Post creation finalization.
*
* INPUTS
*     HWND - Window to associate IME context with.
*
* RESULT
*     True if successful
*
******************************************************************************/

bool IMEManager::FinalizeCreate(HWND hwnd)
	{
	if (hwnd == NULL)
		{
		return false;
		}

	mHWND = hwnd;

	// Check the OS version, if Win98 or better then we can use unicode
	OSVERSIONINFO osvi;
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&osvi);

	bool isWin98orLater = (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && ((osvi.dwMajorVersion > 4) || ((osvi.dwMajorVersion == 4) && (osvi.dwMinorVersion >= 10)));
	bool isNT4orLater = (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) && ((osvi.dwMajorVersion > 4) || ((osvi.dwMajorVersion == 4) && (osvi.dwMinorVersion >= 0)));
	mOSCanUnicode = (isWin98orLater || isNT4orLater);

	// Create new input context for the specified window.
	mHIMC = ImmCreateContext();
	
	if (mHIMC == NULL)
		{
		return false;
		}

	// Associate the new context with this window.
	mDefaultHIMC = ImmAssociateContext(mHWND, mHIMC);

	// Set the language for the current keyboard layout.
	InputLanguageChanged(GetKeyboardLayout(0));

	mCandidateColl.resize(32);

	return true;
	}


/******************************************************************************
*
* NAME
*     IMEManager::Activate
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

void IMEManager::Activate(void)
	{
	WWDEBUG_SAY(("IMEManager: Activate\n"));

	HIMC imc = ImmGetContext(mHWND);

	if (imc)
		{
		BOOL open = ImmGetOpenStatus(imc);

		if (!open)
			{
			ImmSetOpenStatus(imc, TRUE);

			IMEEvent action(IME_ACTIVATED, this);
			NotifyObservers(action);
			}

		ImmReleaseContext(mHWND, imc);
		}
	}


/******************************************************************************
*
* NAME
*     IMEManager::Deactivate
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

void IMEManager::Deactivate(void)
	{
	WWDEBUG_SAY(("IMEManager: Deactivate\n"));

	HIMC imc = ImmGetContext(mHWND);

	if (imc)
		{
		BOOL open = ImmGetOpenStatus(imc);

		if (open)
			{
			ImmSetOpenStatus(imc, FALSE);

			IMEEvent action(IME_DEACTIVATED, this);
			NotifyObservers(action);
			}

		ImmReleaseContext(mHWND, imc);
		}

	ResetComposition();
	}


/******************************************************************************
*
* NAME
*     IMEManager::IsActive
*
* DESCRIPTION
*     Check if IME is active.
*
* INPUTS
*     NONE
*
* RESULT
*     True if IME is currently active.
*
******************************************************************************/

bool IMEManager::IsActive(void) const
	{
	bool isActive = false;

	HIMC imc = ImmGetContext(mHWND);

	if (imc)
		{
		BOOL open = ImmGetOpenStatus(imc);
		isActive = (open != 0);
		ImmReleaseContext(mHWND, imc);
		}

	return isActive;
	}


/******************************************************************************
*
* NAME
*     IMEManager::Disable
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

void IMEManager::Disable(void)
	{
	++mDisableCount;

	// If this is the first disable lock the perform the actuall disabling.
	if (1 == mDisableCount)
		{
		WWDEBUG_SAY(("IMEManager: Disabled\n"));
		mDisabledHIMC = ImmAssociateContext(mHWND, NULL);

		IMEEvent action(IME_DISABLED, this);
		NotifyObservers(action);
		}
	}


/******************************************************************************
*
* NAME
*     IMEManager::Enable
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

void IMEManager::Enable(void)
	{
	if (mDisableCount > 0)
		{
		--mDisableCount;

		// Re-enable when there is no disable locks.
		if (0 == mDisableCount)
			{
			WWDEBUG_SAY(("IMEManager: Enabled\n"));
			ImmAssociateContext(mHWND, mDisabledHIMC);
			mDisabledHIMC = NULL;

			IMEEvent action(IME_ENABLED, this);
			NotifyObservers(action);
			}
		}
	}


/******************************************************************************
*
* NAME
*     IMEManager::IsDisabled
*
* DESCRIPTION
*     Check if IME is turned off.
*
* INPUTS
*     NONE
*
* RESULT
*     True if IME is currently off.
*
******************************************************************************/

bool IMEManager::IsDisabled(void) const
	{
	return (mDisableCount > 0);
	}


/******************************************************************************
*
* NAME
*     IMEManager::ProcessMessage
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*     True of message handled and therefore should NOT be passed to the
*     default window procedure.
*
******************************************************************************/

bool IMEManager::ProcessMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& outResult)
	{
	if (hwnd != mHWND)
		{
		return false;
		}

	bool handled = true;
	outResult = 0;

	switch (msg)
		{
		// Request New keyboard layout and / or Input method
		case WM_INPUTLANGCHANGEREQUEST:
			{
			HKL layout = InputLanguageChangeRequest((HKL)lParam);

			if (layout)
				{
				lParam = (LPARAM)layout;
				handled = false;
				}
			}
			break;

		// Input language has changed.
		case WM_INPUTLANGCHANGE:
			InputLanguageChanged((HKL)lParam);
			outResult = TRUE;
			handled = false;
			break;

		// Sent when the system is about to change the current IME.
		case WM_IME_SELECT:
			WWDEBUG_SAY(("IMEManager: WM_IME_SELECT\n"));
			break;

		// We will handle all of the UI so clear all of the flags.
		case WM_IME_SETCONTEXT:
			lParam &= ~(ISC_SHOWUIALL);
			handled = false;
			break;

		// Sent when a composition string is about to be generated in response to a
		// keystroke. This message informs us to prepare for composition.
		case WM_IME_STARTCOMPOSITION:
			StartComposition();
			break;

		// Sent when composition status has changed in response to a keystroke.
		case WM_IME_COMPOSITION:
			DoComposition(wParam, lParam);
			break;

		// Sent when composition has closed.
		case WM_IME_ENDCOMPOSITION:
			EndComposition();
			break;

		// Sent when unable to extend the composition to accomodate any more characters.
		case WM_IME_COMPOSITIONFULL:
			{
			WWDEBUG_SAY(("IMEManager: WM_IME_COMPOSITIONFULL\n"));
			CompositionEvent event(COMPOSITION_FULL, this);
			NotifyObservers(event);
			}
			break;

		// Sent when the IME status has changed.
		case WM_IME_NOTIFY:
			outResult = IMENotify(wParam, lParam);
			break;

		// IMEs send this message when the user accepts the conversion string.
		// wParam contains a single-byte or double-byte character.
		case WM_IME_CHAR:
			handled = IMECharHandler((unsigned short)wParam);

			if (handled)
				{
				outResult = TRUE;
				}
			break;

		case WM_CHAR:
			handled = CharHandler((unsigned short)wParam);

			if (handled)
				{
				outResult = TRUE;
				}
			break;

		case WM_KEYDOWN:
			if (mInComposition)
				{
				outResult = DefWindowProc(hwnd, msg, wParam, lParam);
				}
			else
				{
				handled = false;
				}
			break;

		case WM_KEYUP:
			if (mInComposition)
				{
				#ifdef SHOW_IME_TYPING

				bool typingChanged = false;
				UINT virtualKey = wParam;

				if (VK_BACK == virtualKey)
					{
					if (mTypingCursorPos > 0)
						{
						--mTypingCursorPos;
						mTypingString[mTypingCursorPos] = 0;
						typingChanged = true;
						}
					}
				else if ((0x30 <= virtualKey && 0x39 >= virtualKey) || (0x41 <= virtualKey && 0x5A >= virtualKey))
					{
					if (mTypingCursorPos < IME_MAX_TYPING_LEN)
						{
						mTypingString[mTypingCursorPos] = (wchar_t)virtualKey;
						++mTypingCursorPos;
						mTypingString[mTypingCursorPos] = 0;
						typingChanged = true;
						}
					}

				if (typingChanged)
					{
					CompositionEvent event(COMPOSITION_TYPING, this);
					NotifyObservers(event);
					}
				#endif

				outResult = DefWindowProc(hwnd, msg, wParam, lParam);
				}
			else
				{
				handled = false;
				}
			break;

		case WM_IME_CONTROL:
		case WM_IME_KEYDOWN:
		case WM_IME_KEYUP:
		default:
			handled = false;
			break;
		}

	return handled;
	}


/******************************************************************************
*
* NAME
*     IMEManager::IMENotify
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

LRESULT IMEManager::IMENotify(WPARAM wParam, LPARAM lParam)
	{
	switch (wParam)
		{
		// The open status of the input context has changed.
		case IMN_SETOPENSTATUS:
			WWDEBUG_SAY(("IMEManager: IMN_SETOPENSTATUS\n"));
			{
			HIMC imc = ImmGetContext(mHWND);

			if (imc)
				{
				IMEAction action = IME_DEACTIVATED;

				if (!ImmGetOpenStatus(imc))
					{
					// If the IME conversion engine is closed, we need to
					// erase all currently displayed composition chars and any
					// candidate windows.
					mCandidateColl.clear();
					ResetComposition();
					}
				else
					{
					action = IME_ACTIVATED;
					}

				ImmReleaseContext(mHWND, imc);

				IMEEvent event(action, this);
				NotifyObservers(event);
				}
			}
			break;

		// Open the status window
		case IMN_OPENSTATUSWINDOW:
			WWDEBUG_SAY(("IMEManager: IMN_OPENSTATUSWINDOW\n"));
			break;

		// Close the status window
		case IMN_CLOSESTATUSWINDOW:
			WWDEBUG_SAY(("IMEManager: IMN_CLOSESTATUSWINDOW\n"));
			break;

		// Update the position of the status window.
		case IMN_SETSTATUSWINDOWPOS:
			WWDEBUG_SAY(("IMEManager: IMN_SETSTATUSWINDOWPOS\n"));
			break;

		// The font of the input context has changed.
		case IMN_SETCOMPOSITIONFONT:
			WWDEBUG_SAY(("IMEManager: IMN_SETCOMPOSITIONFONT\n"));
			break;

		// The style or position of the composition window has changed.
		case IMN_SETCOMPOSITIONWINDOW:
			WWDEBUG_SAY(("IMEManager: IMN_SETCOMPOSITIONWINDOW\n"));
			break;

		// The conversion mode of the input context has changed.
		case IMN_SETCONVERSIONMODE:
			WWDEBUG_SAY(("IMEManager: IMN_SETCONVERSIONMODE\n"));

#ifdef WWDEBUG
			{
			HIMC imc = ImmGetContext(mHWND);

			if (imc)
				{
				DWORD conversionMode = 0;
				DWORD sentenceMode = 0;

				BOOL okay = ImmGetConversionStatus(imc, &conversionMode, &sentenceMode);

				if (okay)
					{
					WWDEBUG_SAY(("IMEManager: ConversionMode - "));

					static struct convmodestruct {long flag; const char* ondesc; const char* offdesc;} _convModes[] = 
						{
						{IME_CMODE_CHARCODE, "CharCode:On", "CharCode:Off"},
						{IME_CMODE_EUDC, " EUDC:On", " EUDC:Off"},
						{IME_CMODE_FIXED, " Fixed:On", " Fixed:Off"},
						{IME_CMODE_FULLSHAPE, " FullShape", " HalfShape"},
						{IME_CMODE_HANJACONVERT, " Hanja:On", " Hanja:Off"},
						{IME_CMODE_KATAKANA, " Katakana", " Hiragana"},
						{IME_CMODE_NATIVE, " Native", " Alphanumberic"},
						{IME_CMODE_NOCONVERSION, " Conversion:Off", " Conversion:On"},
						{IME_CMODE_ROMAN, " Roman:On", " Roman:Off"},
						{IME_CMODE_SOFTKBD, " SoftKbd:On", " SoftKbd:Off"},
						{IME_CMODE_SYMBOL, " Symbol:On", " Symbol:Off"},
						{0, ""}
						};

					int flgidx = 0;

					while (_convModes[flgidx].flag)
						{
						if (conversionMode & _convModes[flgidx].flag)
							{
							WWDEBUG_SAY((_convModes[flgidx].ondesc));
							}
						else
							{
							WWDEBUG_SAY((_convModes[flgidx].offdesc));
							}

						flgidx++;
						}

					WWDEBUG_SAY(("\n"));
					}

				ImmReleaseContext(mHWND, imc);
				}
			}
#endif
			break;

		// The sentence mode has changed.
		case IMN_SETSENTENCEMODE:
			WWDEBUG_SAY(("IMEManager: IMN_SETSENTENCEMODE\n"));

#ifdef WWDEBUG
			{
			HIMC imc = ImmGetContext(mHWND);

			if (imc)
				{
				DWORD conversionMode = 0;
				DWORD sentenceMode = 0;

				BOOL okay = ImmGetConversionStatus(imc, &conversionMode, &sentenceMode);

				if (okay)
					{
					WWDEBUG_SAY(("IMEManager: SentenceMode - "));

					static struct smodestruct {long flag; const char* ondesc; const char* offdesc;} _sModes[] = 
						{
						{IME_SMODE_AUTOMATIC, "Automatic:On", "Automatic:Off"},
						{IME_SMODE_NONE, " SentenceInfo:Off", " SentenceInfo:On"},
						{IME_SMODE_PHRASEPREDICT, " PhrasePredict:On", " PhrasePredict:Off"},
						{IME_SMODE_PLAURALCLAUSE, " PluralClause:On", " PluralClause:Off"},
						{IME_SMODE_SINGLECONVERT, " SingleConvert:On", " SingleConvert:Off"},
						{IME_SMODE_CONVERSATION, " Conversation:On", " Conversation:Off"},
						{0, ""}
						};

					int flgidx = 0;

					while (_sModes[flgidx].flag)
						{
						if (sentenceMode & _sModes[flgidx].flag)
							{
							WWDEBUG_SAY((_sModes[flgidx].ondesc));
							}
						else
							{
							WWDEBUG_SAY((_sModes[flgidx].offdesc));
							}

						flgidx++;
						}

					WWDEBUG_SAY(("\n"));
					}

				ImmReleaseContext(mHWND, imc);
				}
			}
#endif
			break;

		// Open the candidate window (lParam = candidate flags)
		case IMN_OPENCANDIDATE:
			OpenCandidate(lParam);
			break;

		// Close the candidate window. (lParam = candidate flags)
		case IMN_CLOSECANDIDATE:
			CloseCandidate(lParam);
			break;

		// Changing the contents of the candidate window (lParam = candidate flags)
		case IMN_CHANGECANDIDATE:
			ChangeCandidate(lParam);
			break;

		// Candidate processing is finished; moving the candidate window
		case IMN_SETCANDIDATEPOS:
			WWDEBUG_SAY(("IMEManager: IMN_SETCANDIDATEPOS\n"));
			break;

		// Show error message or other information
		case IMN_GUIDELINE:
			WWDEBUG_SAY(("IMEManager: IMN_GUIDELINE\n"));
			{
			IMEEvent event(IME_GUIDELINE, this);
			NotifyObservers(event);
			}
			break;

		default:
			break;
		}
	
	return TRUE;
	}


/******************************************************************************
*
* NAME
*     IMEManager::InputLanguageChangeRequest
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

HKL IMEManager::InputLanguageChangeRequest(HKL hkl)
	{
	WWDEBUG_SAY(("IMEManager: Input language change request\n"));

	// Get the number of Keyboard layouts available to the system
	UINT numLayouts = GetKeyboardLayoutList(0, NULL);

	if (numLayouts)
		{
		// Get the list of layouts
		std::vector<HKL> layoutList(numLayouts);
		layoutList.resize(numLayouts);

		numLayouts = GetKeyboardLayoutList(numLayouts, layoutList.begin());

		// Find the position in the list of the layout which has been requested.
		std::vector<HKL>::iterator iter = std::find(layoutList.begin(), layoutList.end(), hkl);

		if (iter != layoutList.end())
			{
			// Rotate the list so the requested layout is at the head.
			std::rotate(layoutList.begin(), iter, layoutList.end());

			// Look for the layout that doesn't have the  AT_CARET or SPECIAL_UI properties
			iter = layoutList.begin();

			while (iter != layoutList.end())
				{
				DWORD property = ImmGetProperty(*iter, IGP_PROPERTY);

				if ((property & (IME_PROP_AT_CARET | IME_PROP_SPECIAL_UI)) == (IME_PROP_AT_CARET | IME_PROP_SPECIAL_UI))
					{
					iter++;
					}
				else
					{
					return *iter;
					}
				}
			}
		}

	return NULL;
	}


/******************************************************************************
*
* NAME
*     IMEManager::InputLanguageChanged
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

void IMEManager::InputLanguageChanged(HKL hkl)
	{
	mLangID = LOWORD(hkl);
	DWORD lcid = MAKELCID(mLangID, SORT_DEFAULT);

	// Get the default codepage for this input language
	char localeData[8];
	GetLocaleInfo(lcid, LOCALE_IDEFAULTANSICODEPAGE, localeData, sizeof(localeData));

	// Set the codepage for character conversion
	mCodePage = atoi(localeData);

	// Get properties
	mIMEProperties = ImmGetProperty(hkl, IGP_PROPERTY);

	mStartCandListFrom1 = ((mIMEProperties & IME_PROP_CANDLIST_START_FROM_1) == IME_PROP_CANDLIST_START_FROM_1);
	mUseUnicode = (mOSCanUnicode && (mIMEProperties & IME_PROP_UNICODE));

	// Get IME description
	if (mUseUnicode)
		{
		UINT descSize = ImmGetDescriptionW(hkl, NULL, 0);
		++descSize;
		wchar_t* descPtr = mIMEDescription.Get_Buffer(descSize);

		ImmGetDescriptionW(hkl, descPtr, descSize);
		}
	else
		{
		UINT descSize = ImmGetDescription(hkl, NULL, 0);
		++descSize;

		StringClass desc((int)descSize, true);
		char* descPtr = desc.Get_Buffer(descSize);

		ImmGetDescription(hkl, descPtr, descSize);
		mIMEDescription = desc;
		}

	#if(0)
	mHilite = true;

	static const wchar_t _TradChImeName[] = {0x6CE8,0x97F3,0x8F38,0x5165,0x6CD5,0x0020,0x0034,0x002E,0x0031,0x0020,0x7248,0x0000};

	if (mIMEDescription.Compare(_TradChImeName) == 0)
		{
		mHilite = false;
		}
	#endif

	WWDEBUG_SAY(("IMEManager: Language Changed - LangID = %04X, CodePage = %d, Description: '%S'\n",
			mLangID, mCodePage, mIMEDescription));

	WWDEBUG_SAY(("IMEManager: Properties - %s%s%s%s%s\n",
			mIMEProperties & IME_PROP_AT_CARET ? "At Caret" : "",
			mIMEProperties & IME_PROP_SPECIAL_UI ? ", SpecialUI" : "",
			mIMEProperties & IME_PROP_CANDLIST_START_FROM_1 ? ", CandlistFrom1" : "",
			mIMEProperties & IME_PROP_UNICODE ? ", Unicode" : "",
			mIMEProperties & IME_PROP_COMPLETE_ON_UNSELECT ? ", Complete on Unselect" : ""));

	IMEEvent action(IME_LANGUAGECHANGED, this);
	NotifyObservers(action);
	}


/******************************************************************************
*
* NAME
*     IMEManager::ResetComposition
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

void IMEManager::ResetComposition(void)
	{
	mInComposition = false;

#ifdef SHOW_IME_TYPING
	mTypingString[0] = 0;
	mTypingCursorPos = 0;
#endif

	mCompositionString[0] = 0;
	memset(mCompositionAttr, 0, sizeof(mCompositionAttr));
	memset(mCompositionClause, 0, sizeof(mCompositionClause));
	mCompositionCursorPos = 0;

	mReadingString[0] = 0;
	}


/******************************************************************************
*
* NAME
*     IMEManager::StartComposition
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

void IMEManager::StartComposition(void)
	{
	WWDEBUG_SAY(("IMEManager: StartComposition\n"));

	mInComposition = true;
	mResultString[0] = 0;

	CompositionEvent event(COMPOSITION_START, this);
	NotifyObservers(event);
	}


/******************************************************************************
*
* NAME
*     IMEManager::DoComposition
*
* DESCRIPTION
*     Handle composition message.
*
* INPUTS
*
* RESULT
*
******************************************************************************/

void IMEManager::DoComposition(unsigned int dbcs, long compFlags)
	{
	WWDEBUG_SAY(("IMEManager: DoComposition\n"));

#if(0)
	#ifdef _DEBUG
	static struct flagstruct {long flag; const char* desc;} _gcsFlags[] = 
		{
		{GCS_COMPATTR, "GCS_COMPATTR"},
		{GCS_COMPCLAUSE, " GCS_COMPCLAUSE"},
		{GCS_COMPREADATTR, " GCS_COMPREADATTR"},
		{GCS_COMPREADCLAUSE, " GCS_COMPREADCLAUSE"},
		{GCS_DELTASTART, " GCS_DELTASTART"},
		{GCS_RESULTCLAUSE, " GCS_RESULTCLAUSE"},
		{GCS_RESULTREADCLAUSE, " GCS_RESULTREADCLAUSE"},
		{GCS_RESULTREADSTR, " GCS_RESULTREADSTR"},
		{CS_INSERTCHAR, " CS_INSERTCHAR"},
		{CS_NOMOVECARET, " CS_NOMOVECARET"},
		{0, ""}
		};

	WWDEBUG_SAY(("Flags: "));

	int flgidx = 0;

	while (_gcsFlags[flgidx].flag)
		{
		if (compFlags & _gcsFlags[flgidx].flag)
			{
			WWDEBUG_SAY((_gcsFlags[flgidx].desc));
			}

		flgidx++;
		}

	WWDEBUG_SAY(("\n"));
	#endif
#endif

#ifdef SHOW_IME_TYPING
	// Reset the typing string
	mTypingString[0] = 0;
	mTypingCursorPos = 0;
	CompositionEvent event(COMPOSITION_TYPING, this);
	NotifyObservers(event);
#endif

	HIMC imc = ImmGetContext(mHWND);

	if (imc)
		{
		if (compFlags == 0)
			{
			ResetComposition();
			CompositionEvent event(COMPOSITION_CANCEL, this);
			NotifyObservers(event);
			}
		else if (compFlags & GCS_RESULTSTR)
			{
			// Retrieve the result string
			if (ReadCompositionString(imc, GCS_RESULTSTR, mResultString, sizeof(mResultString)))
				{
				WWDEBUG_SAY(("Result string '%S'\n", mResultString));

				CompositionEvent event(COMPOSITION_RESULT, this);
				NotifyObservers(event);
				}
			}
		else
			{
			CompositionAction action = COMPOSITION_INVALID;

			// Update reading string.
			if (compFlags & GCS_COMPREADSTR)
				{
				if (ReadCompositionString(imc, GCS_COMPREADSTR, mReadingString, sizeof(mReadingString)))
					{
					WWDEBUG_SAY(("Reading string '%S'\n", (const wchar_t*)mReadingString));
					action = COMPOSITION_CHANGE;
					}
				}

#ifdef WWDEBUG
			if (compFlags & GCS_COMPREADATTR)
				{
				unsigned char attr[IME_MAX_STRING_LEN * 2];
				long size = ReadReadingAttr(imc, attr, sizeof(attr));

				WWDEBUG_SAY(("ReadAttr: "));
				
				for (long index = 0; index < size; ++index)
					{
					WWDEBUG_SAY(("%01x", (int)attr[index]));
					}
				
				WWDEBUG_SAY(("\n"));
				}
#endif

			// Update composition string.
			if (compFlags & GCS_COMPSTR)
				{
				if (ReadCompositionString(imc, GCS_COMPSTR, mCompositionString, sizeof(mCompositionString)))
					{
					WWDEBUG_SAY(("Composition string '%S'\n", (const wchar_t*)mCompositionString));
					action = COMPOSITION_CHANGE;
					}
				}

			if (compFlags & GCS_COMPATTR)
				{
				long size = ReadCompositionAttr(imc, mCompositionAttr, sizeof(mCompositionAttr));

#ifdef WWDEBUG
				WWDEBUG_SAY(("CompAttr: "));

				for (long index = 0; index < size; ++index)
					{
					WWDEBUG_SAY(("%01x", (int)mCompositionAttr[index]));
					}
				
				WWDEBUG_SAY(("\n"));
#endif
				}

			if (compFlags & GCS_COMPCLAUSE)
				{
				mCompositionClause[0] = 0;
				long size = ReadCompositionClause(imc, mCompositionClause, sizeof(mCompositionClause));

#ifdef WWDEBUG
				WWDEBUG_SAY(("CompClause: "));
				const int count = (size / sizeof(unsigned long));

				for (int index = 0; index < count; ++index)
					{
					WWDEBUG_SAY(("%u ", mCompositionClause[index]));
					}

				WWDEBUG_SAY(("\n"));
#endif
				}

			if (compFlags & GCS_CURSORPOS)
				{
				mCompositionCursorPos = ReadCursorPos(imc);
				action = COMPOSITION_CHANGE;
				}

			if (action != COMPOSITION_INVALID)
				{
				CompositionEvent event(action, this);
				NotifyObservers(event);
				}
			}

		ImmReleaseContext(mHWND, imc);
		}
	}


/******************************************************************************
*
* NAME
*     IMEManager::EndComposition
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

void IMEManager::EndComposition(void)
	{
	WWDEBUG_SAY(("IMEManager: EndComposition\n"));

	HIMC imc = ImmGetContext(mHWND);

	if (imc)
		{
		ReadCompositionString(imc, GCS_COMPSTR, mResultString, sizeof(mResultString));
		ImmReleaseContext(mHWND, imc);
		}

	ResetComposition();
	CompositionEvent event(COMPOSITION_END, this);
	NotifyObservers(event);
	}


/******************************************************************************
*
* NAME
*     IMEManager::ReadCompositionString
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

bool IMEManager::ReadCompositionString(HIMC imc, unsigned long flag, wchar_t* buffer, int length)
	{
	if (mUseUnicode)
		{
		LONG size = ImmGetCompositionStringW(imc, flag, buffer, length);

		if (size < 0)
			{
			buffer[0] = 0;
			return false;
			}
		
		// Terminate string
		buffer[(size / sizeof(wchar_t))] = 0;
		}
	else
		{
		// Read the string as multibyte ANSI
		unsigned char string[IME_MAX_STRING_LEN];
		LONG size = ImmGetCompositionString(imc, flag, string, sizeof(string));

		if (size < 0)
			{
			buffer[0] = 0;
			return false;
			}

		// Terminate the string
		string[size] = 0;

		// Convert to Unicode
		MultiByteToWideChar(mCodePage, 0, (const char*)string, -1, buffer, (length / sizeof(wchar_t)));
		}

	return true;
	}


/******************************************************************************
*
* NAME
*     IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

long IMEManager::ReadReadingAttr(HIMC imc, unsigned char* attr, int length)
	{
	if (mUseUnicode)
		{
		LONG size = ImmGetCompositionStringW(imc, GCS_COMPREADATTR, attr, length);
		return (size / sizeof(wchar_t));
		}

	// Read the string as multibyte ANSI
	unsigned char string[IME_MAX_STRING_LEN];
	LONG size = ImmGetCompositionString(imc, GCS_COMPREADSTR, string, sizeof(string));

	if (size <= 0)
		{
		return 0;
		}

	// Terminate the string
	string[size] = 0;
		
	LONG attrSize = ImmGetCompositionString(imc, GCS_COMPREADATTR, attr, length);
	WWASSERT(size == attrSize);

	if (attrSize <= size)
		{
		return 0;
		}

	return ConvertAttrForUnicode(string, attr);
	}


/******************************************************************************
*
* NAME
*     IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

long IMEManager::ReadReadingClause(HIMC imc, unsigned long* clause, int length)
	{
	if (mUseUnicode)
		{
		LONG size = ImmGetCompositionStringW(imc, GCS_COMPREADCLAUSE, clause, length);
		return (size / sizeof(wchar_t));
		}

	// Read the string as multibyte ANSI
	unsigned char string[IME_MAX_STRING_LEN];
	LONG size = ImmGetCompositionString(imc, GCS_COMPREADSTR, string, sizeof(string));

	if (size <= 0)
		{
		return 0;
		}

	// Terminate the string
	string[size] = 0;

	LONG clauseSize = ImmGetCompositionString(imc, GCS_COMPREADCLAUSE, clause, length);

	if (clauseSize <= 0)
		{
		return 0;
		}
	
	return ConvertClauseForUnicode(string, size, clause);
	}


/******************************************************************************
*
* NAME
*     IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

long IMEManager::ReadCompositionAttr(HIMC imc, unsigned char* attr, int length)
	{
	if (mUseUnicode)
		{
		return ImmGetCompositionStringW(imc, GCS_COMPATTR, attr, length);
		}

	// Read the string as multibyte ANSI
	unsigned char string[IME_MAX_STRING_LEN];
	LONG size = ImmGetCompositionString(imc, GCS_COMPSTR, string, sizeof(string));

	if (size <= 0)
		{
		return size;
		}

	// Terminate the string
	string[size] = 0;
		
	LONG attrSize = ImmGetCompositionString(imc, GCS_COMPATTR, attr, length);
	WWASSERT(size == attrSize);

	if (attrSize <= size)
		{
		return 0;
		}

	return ConvertAttrForUnicode(string, attr);
	}


/******************************************************************************
*
* NAME
*     IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

long IMEManager::ReadCompositionClause(HIMC imc, unsigned long* clause, int length)
	{
	if (mUseUnicode)
		{
		return ImmGetCompositionStringW(imc, GCS_COMPCLAUSE, clause, length);
		}

	// Read the string as multibyte ANSI
	unsigned char string[IME_MAX_STRING_LEN];
	LONG size = ImmGetCompositionString(imc, GCS_COMPSTR, string, sizeof(string));

	if (size <= 0)
		{
		return size;
		}

	// Terminate the string
	string[size] = 0;

	LONG clauseSize = ImmGetCompositionString(imc, GCS_COMPCLAUSE, clause, length);

	if (clauseSize <= 0)
		{
		return 0;
		}
	
	return ConvertClauseForUnicode(string, size, clause);
	}


/******************************************************************************
*
* NAME
*     IMEManager::ReadCursorPos
*
* DESCRIPTION
*     Read the composition string cursor position.
*
* INPUTS
*
* RESULT
*
******************************************************************************/

long IMEManager::ReadCursorPos(HIMC imc)
	{
	if (mUseUnicode)
		{
		long cursorPos = ImmGetCompositionStringW(imc, GCS_CURSORPOS, NULL, 0);
		return (cursorPos & 0x0000FFFF);
		}

	// Get the multibyte string
	char string[IME_MAX_STRING_LEN];
	LONG size = ImmGetCompositionString(imc, GCS_COMPSTR, string, sizeof(string));

	if (size < 0)
		{
		return 0;
		}

	string[size] = 0;

	long cursorPos = ImmGetCompositionString(imc, GCS_CURSORPOS, NULL, 0);
	cursorPos = (cursorPos & 0x0000FFFF);

	// Convert multibyte character position in unicode position.
	return _mbsnccnt((unsigned char*)string, cursorPos);
	}


/******************************************************************************
*
* NAME
*     IMEManager::GetTargetClause
*
* DESCRIPTION
*     Get the composition string conversion target range. This is the characters
*     that are currently be considered for conversion.
*
* INPUTS
*
* RESULT
*
******************************************************************************/

void IMEManager::GetTargetClause(unsigned long& start, unsigned long& end)
	{
	int index = 0;
	const unsigned long compLength = wcslen(mCompositionString);

	while (mCompositionClause[index] < compLength)
		{
		unsigned long offset = mCompositionClause[index];

		if (ATTR_TARGET_CONVERTED == mCompositionAttr[offset])
			{
			start = offset;
			end = mCompositionClause[index + 1];
			return;
			}

		++index;
		}

	start = 0;
	end = 0;
	}


/******************************************************************************
*
* NAME
*     IMEManager::GetCompositionFont
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

bool IMEManager::GetCompositionFont(LPLOGFONT lpFont)
	{
	BOOL success = FALSE;
	HIMC imc = ImmGetContext(mHWND);

	if (imc)
		{
		success = ImmGetCompositionFont(imc, lpFont);
		ImmReleaseContext(mHWND, imc);
		}

	return (success == TRUE);
	}


/******************************************************************************
*
* NAME
*     IMEManager::OpenCandidate
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

void IMEManager::OpenCandidate(unsigned long candList)
	{
	WWDEBUG_SAY(("IMEManager: OpenCandidate\n"));

	for (int index = 0; index < 32; index++)
		{
		if (candList & (1 << index))
			{
			IMECandidate& candidate = mCandidateColl[index];
			candidate.Open(index, mHWND, mCodePage, mUseUnicode, mStartCandListFrom1);
			candidate.Read();

			CandidateEvent event(CANDIDATE_OPEN, &candidate);
			NotifyObservers(event);
			}
		}
	}


/******************************************************************************
*
* NAME
*     IMEManager::ChangeCandidate
*
* DESCRIPTION
*     The contents of the candidate list has changed.
*
* INPUTS
*     Changed - Bitfield of the candidate lists that have changed.
*
* RESULT
*     NONE
*
******************************************************************************/

void IMEManager::ChangeCandidate(unsigned long candList)
	{
	WWDEBUG_SAY(("IMEManager: ChangeCandidate\n"));

	for (int index = 0; index < 32; index++)
		{
		if (candList & (1 << index))
			{
			IMECandidate& candidate = mCandidateColl[index];
			candidate.Read();
			CandidateEvent event(CANDIDATE_CHANGE, &candidate);
			NotifyObservers(event);
			}
		}
	}

	
/******************************************************************************
*
* NAME
*     IMEManager::CloseCandidate
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

void IMEManager::CloseCandidate(unsigned long candList)
	{
	WWDEBUG_SAY(("IMEManager: CloseCandidate\n"));

	for (int index = 0; index < 32; index++)
		{
		if (candList & (1 << index))
			{
			IMECandidate& candidate = mCandidateColl[index];
			CandidateEvent event(CANDIDATE_CLOSE, &candidate);
			NotifyObservers(event);
			candidate.Close();
			}
		}
	}


/******************************************************************************
*
* NAME
*     IMEManager::GetGuideLine
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

unsigned long IMEManager::GetGuideline(wchar_t* outString, int length)
	{
	unsigned long level = GL_LEVEL_NOGUIDELINE;

	HIMC imc = ImmGetContext(mHWND);

	if (imc)
		{
		level = ImmGetGuideLine(imc, GGL_LEVEL, NULL, 0);

		if ((GL_LEVEL_NOGUIDELINE != level) && outString)
			{
			if (mUseUnicode)
				{
				DWORD size = ImmGetGuideLineW(imc, GGL_STRING, outString, (length * sizeof(wchar_t)));
				WWASSERT(size <= (DWORD)length);
				outString[size / sizeof(wchar_t)] = 0;
				}
			else
				{
				char temp[512];
				DWORD size = ImmGetGuideLine(imc, GGL_STRING, temp, sizeof(temp));
				temp[size] = 0;

				MultiByteToWideChar(mCodePage, 0, temp, -1, outString, length);
				outString[length] = 0;
				}
			}

		ImmReleaseContext(mHWND, imc);
		}

	return level;
	}


/******************************************************************************
*
* NAME
*     IMEManager::IMECharHandler
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*     True if character converted.
*
******************************************************************************/

bool IMEManager::IMECharHandler(unsigned short dbcs)
	{
	unsigned long mbcs = dbcs;

	// If this char has a lead byte then it is double byte. Swap the bytes
	// for generate string order
	if (dbcs & 0xFF00)
		{
		mbcs = (((dbcs & 0xFF) << 8) | (dbcs >> 8));
		}

	// Convert char to unicode
	wchar_t unicode = 0;
	MultiByteToWideChar(mCodePage, 0, (const char*)&mbcs, -1, &unicode, 1);

	UnicodeChar event(unicode);
	NotifyObservers(event);

	return true;
	}


/******************************************************************************
*
* NAME
*     IMEManager::CharHandler
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*     True if character handled.
*
******************************************************************************/

bool IMEManager::CharHandler(unsigned short ch)
	{
	// Because DBCS characters are usually generated by IMEs (as two PostMessages),
	// if a lead byte comes in, the trail byte should arrive very soon after.
	// We wait here for the trail byte and store them into the text buffer together.
	if (!IsDBCSLeadByte((unsigned char)ch))
		{
		return false;
		}

	// Wait an arbitrary amount of time for the trail byte to arrive.
	// If it doesn't, then discard the lead byte. This could happen if the IME
	// screwed up.  Or, more likely, the user generated the lead byte through ALT-numpad.
	MSG msg;
	int i = 10;

	while (!PeekMessage(&msg, mHWND, WM_CHAR, WM_CHAR, PM_REMOVE))
		{
		if (--i == 0)
			{
			return true;
			}

		Sleep(0);
		}

	// Convert char to unicode.
	unsigned long dbcs = (unsigned long)(((unsigned)msg.wParam << 8) | ch);
	wchar_t unicode = 0;
	MultiByteToWideChar(mCodePage, 0, (const char*)&dbcs, 2, &unicode, 1);

	UnicodeChar event(unicode);
	NotifyObservers(event);

	return true;
	}


/******************************************************************************
*
* NAME
*     IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

long IMEManager::ConvertAttrForUnicode(unsigned char* mbcs, unsigned char* attr)
	{
	// Scale the attributes for unicode string length
	unsigned char* mbsPtr = mbcs;
	unsigned char* attrPtr = attr;

	while (*mbsPtr != 0)
		{
		*attrPtr = attr[mbsPtr - mbcs];
		++attrPtr;
		mbsPtr = _mbsinc(mbsPtr);
		}

	return (attrPtr - attr);
	}


/******************************************************************************
*
* NAME
*     IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

long IMEManager::ConvertClauseForUnicode(unsigned char* mbcs, long length, unsigned long* clause)
	{
	//---------------------------------------------------------------------------
	// Scale the clause offsets for unicode string
	//---------------------------------------------------------------------------
	unsigned char* mbsPtr = mbcs;
	unsigned long offset = 0;

	// The first clause is always zero so there is no need to adjust it.
	int index = 1;

	// The clause is terminated with the size of the string
	while (clause[index] < (unsigned long)length)
		{
		// Count the number of characters in this clause
		unsigned char* mbsStop = (mbcs + clause[index]);

		while (mbsPtr < mbsStop)
			{
			++offset;
			mbsPtr = _mbsinc(mbsPtr);
			}

		clause[index] = offset;
		++index;
		}

	// Terminate the unicode adjusted clause with the string length
	clause[index] = _mbslen(mbcs);
	++index;

	return (&clause[index] - clause);
	}

} // namespace IME
