//	Zinc Application Framework - W_FMTSTR.CPP
//	COPYRIGHT (C) 1990-1999.  All Rights Reserved.
//	Zinc Software Incorporated.  Pleasant Grove, Utah  USA

#include <w_app.hpp>
#include <z_ctype.hpp>
#include <z_fmtstr.hpp>
#include <z_string.hpp>


void SimulateKey(HWND hwnd, UINT message, ZafIChar key, bool alt = false,
	bool control = false, bool shift = false)
{
	BYTE keyboardState[256];
	GetKeyboardState((LPBYTE)&keyboardState);

	BYTE saveAlt = keyboardState[VK_MENU];
	BYTE saveControl = keyboardState[VK_CONTROL];
	BYTE saveShift = keyboardState[VK_SHIFT];

	keyboardState[VK_MENU] = (BYTE)(alt ? 0x80 : 0x00);
	keyboardState[VK_CONTROL] = (BYTE)(control ? 0x80 : 0x00);
	keyboardState[VK_SHIFT] = (BYTE)(shift ? 0x80 : 0x00);
	SetKeyboardState((LPBYTE)&keyboardState);

	OSEventStruct osEvent;
	osEvent.hwnd = hwnd;
	osEvent.message = message;
	osEvent.wParam = (WPARAM)key;
	osEvent.lParam = (LPARAM)1;

	if (message == WM_CHAR)
	{
		osEvent.message = WM_CHAR;
#if defined(ZAF_UNICODE)
		char osChar[2];
		if (ZafMSWindowsApp::convertText)
			zafCodeSet->ConvertToOSChar(key, osChar);
		else
		{
			osChar[0] = LOBYTE(key);
			osChar[1] = HIBYTE(key);
		}

		for (int index = 0; index < 2 && osChar[index]; index++)
		{
			osEvent.wParam = (WPARAM)osChar[index];
			DefaultCallback(ZafEventStruct(E_MSWINDOWS, &osEvent));
		}
#else
		if (ZafMSWindowsApp::convertText)
			 	zafCodeSet->ConvertToOSChar(key, (char *)&osEvent.wParam);
		else
			osEvent.wParam = key;
  		DefaultCallback(ZafEventStruct(E_MSWINDOWS, &osEvent));
#endif
	}
	else
		DefaultCallback(ZafEventStruct(E_MSWINDOWS, &osEvent));

	keyboardState[VK_MENU] = saveAlt;
	keyboardState[VK_CONTROL] = saveControl;
	keyboardState[VK_SHIFT] = saveShift;
	SetKeyboardState((LPBYTE)&keyboardState);
}


// ----- ZafFormattedString -----------------------------------------------

void ZafFormattedString::DeleteText(ZafEventType ccode)
{
	// Get selection offset and cursor position.
	DWORD selection = SendMessage(screenID, EM_GETSEL, 0, 0);
	int startOffset = LOWORD(selection);
	int endOffset = HIWORD(selection);
	int cursorPos = startOffset;

	// Compute deletion range.
	if (startOffset == endOffset)
	{
		// Determine the delete range.
		switch (ccode)
		{
		case L_EOL:
			// Delete to end of line.
			endOffset = strlen(*deleteData);
			break;

		case L_LEFT:
			// Delete one character to the left.
			do
				--startOffset;
			while (startOffset > 0 && IsLiteralChar((*inputFormatData)[startOffset]));
			cursorPos = startOffset;
			break;

		case L_RIGHT:
			{
			// Delete one character to the right.
			OSGetText();
			const ZafIChar *text = stringData->Text();

			while ((*inputFormatData)[startOffset] && (IsLiteralChar((*inputFormatData)[startOffset]) ||
				text[startOffset] == (*deleteData)[startOffset]))
					startOffset++;
			if ((*inputFormatData)[startOffset])
				endOffset = startOffset + 1;
			}
			break;
		}
	}

	// Replace the deleted characters with the delete text.
	for (int i = startOffset; i < endOffset; i++)
	{
		// Mark the character to be replaced.
#if defined(ZAF_WIN32)
			SendMessage(screenID, EM_SETSEL, (WPARAM)i, (LPARAM)i + 1);
#else
	  		SendMessage(screenID, EM_SETSEL, (WPARAM)0, MAKELPARAM(i, i + 1));
#endif

		SimulateKey(screenID, WM_CHAR, (*deleteData)[i]);
	}

	// Update the cursor position.
#if defined(ZAF_WIN32)
	SendMessage(screenID, EM_SETSEL, (WPARAM)cursorPos, (LPARAM)cursorPos);
#else
	SendMessage(screenID, EM_SETSEL, (WPARAM)0, MAKELPARAM(cursorPos, cursorPos));
#endif
	MoveCursor(0);
}

#if defined(ZAF_DEFAULT_DRAW)
ZafEventType ZafFormattedString::Draw(const ZafEventStruct &event, ZafEventType ccode)
{
	return ZafString::Draw(event, ccode);
}
#endif

ZafEventType ZafFormattedString::Event(const ZafEventStruct &event)
{
	ZafEventType ccode = event.type;

	if (ccode == E_OSEVENT)
		return ZafFormattedString::OSEvent(event);

	// Process Zinc events.
	switch (ccode)
	{
	case S_CURRENT:
		// Make sure cursor is in a proper location.
		MoveCursor(0);
		ccode = ZafString::Event(event);
		break;
		
	default:
		ccode = ZafString::Event(event);
		break;
	}

	// Return the control code.
	return (ccode);
}

void ZafFormattedString::InsertChar(ZafIChar value)
{
	// Get selection range.
	DWORD selection = SendMessage(screenID, EM_GETSEL, 0, 0);
	int startOffset = LOWORD(selection);
	int endOffset = HIWORD(selection);

	// Get cursor offset of proper insertion point.
	int cursorOffset;
	if (startOffset != endOffset)
	{
		DeleteText(L_RIGHT);
		MoveCursor(0);
		selection = SendMessage(screenID, EM_GETSEL, 0, 0);
		cursorOffset = LOWORD(selection); 
	}
	else
		cursorOffset = startOffset;

	// Check for a valid position.
	value = IsLegalChar(value, cursorOffset);
	if (value)
	{
		// Delete the selection range or replacement character.
		SimulateKey(screenID, WM_KEYDOWN, VK_DELETE);

		// Insert the character.
		SimulateKey(screenID, WM_CHAR, value);

		// Update the cursor position.
		MoveCursor(0);
	}
	else if (startOffset != endOffset)
		// Restore the selection range.
#if defined(ZAF_WIN32)
		SendMessage(screenID, EM_SETSEL, (WPARAM)startOffset, (LPARAM)endOffset);
#else
		SendMessage(screenID, EM_SETSEL, (WPARAM)0, MAKELPARAM(startOffset, endOffset));
#endif
}

void ZafFormattedString::InsertText(ZafIChar *value)
{
	// Insert the string characters one by one.
	while (*value)
		InsertChar(*value++);
}

void ZafFormattedString::MoveCursor(ZafEventType ccode)
{
	// Get the current cursor position.
	DWORD selection = SendMessage(screenID, EM_GETSEL, 0, 0);
	int newOffset = LOWORD(selection);

	// Determine the new cursor position.
	switch (ccode)
	{
	case L_BOL:
		// Go to the beginning of line.
		newOffset = 0;
		while ((*inputFormatData)[newOffset] && IsLiteralChar((*inputFormatData)[newOffset]))
			newOffset++;
		break;

	case L_WORD_LEFT:
		// Move off the beginning of the word.
		if (newOffset)
			newOffset--;
		// Move until on a word.
		while (newOffset && IsLiteralChar((*inputFormatData)[newOffset]))
			newOffset--;
		// Move to the start of the word.
		while (newOffset && !IsLiteralChar((*inputFormatData)[newOffset-1]))
			newOffset--;
		break;

	case L_LEFT:
		// Move left one character.
		do
			newOffset--;
		while (newOffset > 0 && IsLiteralChar((*inputFormatData)[newOffset]));
		break;

	case L_EOL:
		// Go to the end of line.
		newOffset = strlen(*inputFormatData);
		break;

	case L_WORD_RIGHT:
		// Move to the start of the next word.
		while ((*inputFormatData)[newOffset] && !IsLiteralChar((*inputFormatData)[newOffset]))
			newOffset++;
		while ((*inputFormatData)[newOffset] && IsLiteralChar((*inputFormatData)[newOffset]))
			newOffset++;
		break;

	case L_RIGHT:
		// Move right one character.
		if ((*inputFormatData)[newOffset])
			newOffset++;
		while ((*inputFormatData)[newOffset] && IsLiteralChar((*inputFormatData)[newOffset]))
			newOffset++;
		break;
	}

	// Make sure cursor is in a valid location.
	while ((*inputFormatData)[newOffset] && IsLiteralChar((*inputFormatData)[newOffset]))
		newOffset++;
	if (!(*inputFormatData)[newOffset])
		while (newOffset > 0 && IsLiteralChar((*inputFormatData)[newOffset - 1]))
			newOffset--;

	// Move the cursor to the specified position.
#if defined(ZAF_WIN32)
	SendMessage(screenID, EM_SETSEL, (WPARAM)newOffset, (LPARAM)newOffset);
	SendMessage(screenID, EM_SCROLLCARET, (WPARAM)0, (LPARAM)0);
#else
	SendMessage(screenID, EM_SETSEL, (WPARAM)0, MAKELPARAM(newOffset, newOffset));
#endif
}

// ----- OS Specific Functions ----------------------------------------------

ZafEventType ZafFormattedString::OSEvent(const ZafEventStruct &event)
{
	if (editMode && event.InputType() == E_MOUSE)
		return (ZafWindowObject::Event(event));

	ZafEventType ccode = event.osEvent.message;

	switch (ccode)
	{
#if defined(ZAF_UNICODE) && defined(ZAF_WIN32)
	case WM_IME_CHAR:
		InsertChar(event.osEvent.wParam);
		ccode = 0;
		break;
#endif

	case WM_CHAR:
		{
		ZafIChar zafChar;
#if defined(ZAF_UNICODE)
		char osChar[3];
		osChar[0] = (char)event.osEvent.wParam;
		osChar[1] = 0;
		if (zafCodeSet->mblen(osChar) > 1)
		{
			MSG msg;
			GetMessage(&msg, 0, WM_KEYFIRST, WM_KEYLAST);
			osChar[1] = (char)msg.wParam;
			osChar[2] = 0;
		}
		zafCodeSet->ConvertToZafChar(osChar, zafChar);
#else
		zafChar = (ZafIChar)event.osEvent.wParam;
#endif
		if (IsPrint(zafChar))
		{
			InsertChar(zafChar);
			ccode = 0;
		}
		else
			ccode = ZafWindowObject::OSEvent(event);
		}
		break;

	case WM_KEYDOWN:
		ccode = 0;
		switch ((int)event.osEvent.wParam)
		{
		case VK_BACK:
			DeleteText(L_LEFT);
			break;

		case VK_DELETE:
			if (event.key.shiftState & S_CTRL)
				DeleteText(L_EOL);
			else
				DeleteText(L_RIGHT);
			break;

		case VK_END:
			MoveCursor(L_EOL);
			break;

		case VK_HOME:
			MoveCursor(L_BOL);
			break;

		case VK_LEFT:
			if (GetKeyState(VK_SHIFT) & 0x8000)
				ccode = ZafString::OSEvent(event);
			else if (GetKeyState(VK_CONTROL) & 0x8000)
				MoveCursor(L_WORD_LEFT);
			else
				MoveCursor(L_LEFT);
			break;

		case VK_RIGHT:
			if (GetKeyState(VK_SHIFT) & 0x8000)
				ccode = ZafString::OSEvent(event);
			else if (GetKeyState(VK_CONTROL) & 0x8000)
				MoveCursor(L_WORD_RIGHT);
			else
				MoveCursor(L_RIGHT);
			break;

		default:
			ccode = ZafString::OSEvent(event);
			break;
		}
		break;

	case WM_LBUTTONUP:
		{
		ccode = ZafString::OSEvent(event);

		// Fix cursor position... a selection region can exist anywhere,
		// but a normal cursor must be at an insertable location.
		DWORD selection = SendMessage(screenID, EM_GETSEL, 0, 0);
		int startOffset = LOWORD(selection);
		int endOffset = HIWORD(selection);
		if (startOffset == endOffset)
			MoveCursor(0);
		}
		break;

	case WM_CLEAR:
		DeleteText(L_EOL);
		break;

	case WM_CUT:
		SendMessage(screenID, WM_COPY, 0, 0);
		DeleteText(L_EOL);
		break;

	case WM_PASTE:
		if (IsClipboardFormatAvailable(CF_TEXT))
		{
			// Find out how much space is left in the strings buffer.
			OSGetText();
			int charsRemaining = stringData->MaxLength();
			if (charsRemaining >= 0)
				charsRemaining -= strlen(stringData->Text());

			// Paste text character by character to enforce variable name attribute.
			OpenClipboard(screenID);
			HANDLE hText = GetClipboardData(CF_TEXT);
			if (hText)
			{
				char *textPtr = (char *)GlobalLock(hText);
				ZafIChar *zafText = zafCodeSet->ConvertToZafString(textPtr);
				ZafIChar *tempPtr = zafText;
				while (*zafText && (charsRemaining == -1 || charsRemaining-- > 0))
					InsertChar(*zafText++);
				delete []tempPtr;
				GlobalUnlock(hText);
			}
			CloseClipboard();	
		}
		else
			ccode = ZafString::OSEvent(event);
		break;

	default:
		ccode = ZafString::OSEvent(event);
	}
	return (ccode);
}

ZafError ZafFormattedString::OSGetText(void)
{
	ZafString::OSGetText();
	UpdateCompressedData();
	return (ZAF_ERROR_NONE);
}

void ZafFormattedString::OSMapPalette(ZafPaletteStruct &palette, ZafPaletteType type, ZafPaletteState state)
{
    ZafString::OSMapPalette(palette, type, state);
}

void ZafFormattedString::OSRegisterObject(void)
{
	ZafString::OSRegisterObject();
}

ZafError ZafFormattedString::OSSetText(void)
{
	UpdateExpandedData();
	return ZafString::OSSetText();
}
