#include <windows.h>
#include "uieditbox.h"
#include "ui.h"

// ----------------------------------------------------------------------------------
CUiEditBox::CUiEditBox(const char* name, CUiLayout* pParent, CUi* pUi)
	: CUiFrame(name, pParent, pUi)
{
	m_uiType			= eUiType_EditBox;

	m_state.isMouseEnable	= true;

	m_history.clear();
	m_historyLast		= 0;
	m_historyLines		= 0;
	m_fontString		= NULL;
	m_text				= NULL;
	m_blockColor		= 0x7F7F7F7F;
	m_textSize			= 0;
	m_letters			= 0;
	m_multiMode			= false;
	m_isNumeric			= false;
	m_isPassword		= false;
	m_isInsertMode		= false;

	m_blinkSpeed		= 0.5f;
	m_lastBlinkTime		= 0.0f;
	m_firstVisible		= 0;
	m_selStart			= 0;
	m_caretPos			= 0;
	m_caretBool			= false;

	SetLetters(255);
}

// ----------------------------------------------------------------------------------
CUiEditBox::~CUiEditBox()
{
	UI_SAFE_DELETE(m_fontString);
	UI_SAFE_DELETE_ARRAY(m_text);

	for ( list<WCHAR*>::iterator iter = m_history.begin(); iter != m_history.end(); iter++ )
		UI_SAFE_DELETE_ARRAY( *iter );
	m_history.clear();
}

// ----------------------------------------------------------------------------------
void CUiEditBox::SetFontString(CUiFontString* fontString)
{
	UI_SAFE_DELETE(m_fontString);
	m_fontString = fontString;
}

// ----------------------------------------------------------------------------------
void CUiEditBox::SetLetters(int letters)
{	
	if ( letters > 0 && m_letters != letters )
	{
		WCHAR *pTmp;
		m_letters = letters;

		pTmp = new WCHAR[m_letters + 1];
		memset( pTmp, 0, sizeof(WCHAR) * (m_letters + 1) );

		if ( m_text )
		{
			memcpy( pTmp, m_text, sizeof(WCHAR) * min(m_textSize, m_letters) );
			delete [] m_text;
		}

		m_text = pTmp;
	}
}

// ----------------------------------------------------------------------------------
void CUiEditBox::SetText(const WCHAR* text)
{
	if (text)
	{
		m_caretPos = 0;
		m_selStart = 0;
		if (wcsncmp(m_text, text, m_letters) == 0)
			return;

		wcsncpy(m_text, text, m_letters);
		m_text[m_letters] = 0;
		m_textSize = wcslen(m_text);

		OnTextChanged();
	}
}

// ----------------------------------------------------------------------------------
void CUiEditBox::SetTextA(const char* text)
{
	WCHAR* tmp = TransToWChar(text);
	if (tmp)
	{
		SetText(tmp);
		delete [] tmp;
	}
}

// ----------------------------------------------------------------------------------
void CUiEditBox::SetHistoryLines(int value)
{
	if ( value >= 0 && value != m_historyLines )
	{
		m_historyLines = value;
		AdjustHistory();
	}
}

// ----------------------------------------------------------------------------------
void CUiEditBox::AddHistoryLine(const WCHAR* text)
{
	if ( m_historyLines <= 0 )
		return;

	WCHAR* tmp = new WCHAR[wcslen(text) + 1];
	wcscpy( tmp, text );
	m_history.push_back( tmp );

	AdjustHistory();
}

// ----------------------------------------------------------------------------------
void CUiEditBox::AddHistoryLineA(const char* text)
{
	if ( m_historyLines <= 0 )
		return;

	WCHAR* tmp = TransToWChar(text);
	if (tmp)
	{
		AddHistoryLine(tmp);
		delete [] tmp;
	}
}

// ----------------------------------------------------------------------------------
const WCHAR* CUiEditBox::GetHistoryLine(int line)
{
	if ( m_history.empty() )
		return NULL;

	if ( line >= 0 && line < m_history.size() )
	{
		list<WCHAR*>::iterator iter = m_history.begin();
		for ( int i = 0; i < line; i++ )
			iter++;
		m_historyLast = line;
		return *iter;
	}
	return NULL;
}

// ----------------------------------------------------------------------------------
void CUiEditBox::AdjustHistory()
{
	if ( m_historyLines < (int)m_history.size() )
	{
		for ( int i = 0; i < (int)m_history.size() - m_historyLines; i++ )
		{
			list<WCHAR*>::iterator iter = m_history.begin();
			if ( *iter ) delete [] (*iter);
			m_history.erase( iter );
		}
	}
	m_historyLast = m_history.size();
}

// ----------------------------------------------------------------------------------
void CUiEditBox::SetTextColor(CUiColor& color)
{
	if ( m_fontString )
		m_fontString->SetColor(color.m_r, color.m_g, color.m_b);
}

// ----------------------------------------------------------------------------------
void CUiEditBox::PlaceCaret(int caretPos)
{
	if (caretPos >= 0 && caretPos <= m_textSize)
	{
		m_caretPos = caretPos;

		if ( m_caretPos < m_firstVisible )
			m_firstVisible = m_caretPos;
		else if (m_fontString)
		{
			float scale = GetRealScale();
			float extent = m_fontString->CaretToPos(m_firstVisible, m_caretPos);
			float width = (m_size.m_x - (m_textInset.m_left + m_textInset.m_right)) * scale;
			if ( width <= 0.0f )
			{
				m_firstVisible = min(m_firstVisible + 3, m_caretPos);
			}
			else
			{
				while (extent > width)
				{
					m_firstVisible = min(m_firstVisible + 3, m_caretPos);
					extent = m_fontString->CaretToPos(m_firstVisible, m_caretPos);
				}
			}
		}
	}
}

// ----------------------------------------------------------------------------------
void CUiEditBox::PasteFromClipboard()
{
	DeleteSelectionText();
	if( OpenClipboard(NULL) )
	{
		HANDLE handle = GetClipboardData(CF_UNICODETEXT);
		if( handle )
		{
			// Convert the ANSI string to Unicode, then insert to our text string.
			WCHAR *tmpTextStr = (WCHAR*)GlobalLock(handle);
			if( tmpTextStr )
			{
				// Copy all characters up to null.
				INT pasteCount;
				if ( (pasteCount = InsertString(m_caretPos, tmpTextStr)) > 0 )
				{
					OnTextChanged();
					PlaceCaret( m_caretPos + pasteCount );
				}
				m_selStart = m_caretPos;
				GlobalUnlock( handle );
			}
		}
		CloseClipboard();
	}
}

// ----------------------------------------------------------------------------------
void CUiEditBox::CopyToClipboard()
{
	// Copy the selection text to the clipboard
	if( m_caretPos != m_selStart && OpenClipboard( NULL ) )
	{
		EmptyClipboard();
		INT first = min( m_caretPos, m_selStart );
		INT last = max( m_caretPos, m_selStart );
		if ( last - first > 0 )
		{
			HGLOBAL hBlock = GlobalAlloc( GMEM_MOVEABLE, sizeof(WCHAR) * ( last - first + 1 ) );
			if ( hBlock )
			{
				WCHAR *tmpTextStr = (WCHAR*)GlobalLock( hBlock );
				memcpy( tmpTextStr, m_text + first, (last - first) * sizeof(WCHAR) );
				tmpTextStr[last - first] = L'\0';  // Terminate it
				GlobalUnlock( hBlock );
			}
			SetClipboardData( CF_UNICODETEXT, hBlock );

			// We must not free the object until CloseClipboard is called.
			if( hBlock )
				GlobalFree( hBlock );
		}
		CloseClipboard();
	}
}

// ----------------------------------------------------------------------------------
void CUiEditBox::DeleteSelectionText()
{
	if ( m_caretPos != m_selStart )
	{
		INT first = min( m_caretPos, m_selStart );
		INT last = max( m_caretPos, m_selStart );

		// Remove the characters
		for ( INT i = first; i < last; i++ )
			RemoveChar( first );

		OnTextChanged();

		// Update caret and selection
		PlaceCaret( first );

		m_selStart = m_caretPos;
	}
}

// ----------------------------------------------------------------------------------
void CUiEditBox::ResetCaretBlink()
{
	m_caretBool			= true;
	m_lastBlinkTime		= 0.0f;
}

// ----------------------------------------------------------------------------------
bool CUiEditBox::InsertChar(int index, WCHAR ch)
{
	if (m_textSize + 1 > m_letters || index > m_textSize)
		return false;

	memmove(m_text+index+1, m_text+index, sizeof(WCHAR) * (m_textSize-index+1));
	m_text[index] = ch;
	m_textSize++;
	return true;
}

// ----------------------------------------------------------------------------------
bool CUiEditBox::RemoveChar(int index)
{
	if (index >= m_textSize)
		return false;

	memmove(m_text+index, m_text+index+1, sizeof(WCHAR) * (m_textSize-index));
	m_textSize--;
	return true;
}

// ----------------------------------------------------------------------------------
int CUiEditBox::InsertString(int index, const WCHAR *text, int size)
{
	int count = 0;
	if (text)
	{
		if (size == -1)
			size = (int)wcslen(text);

		for (int i = 0; i < size; i++)
		{
			if ( InsertChar(index+count, text[i]) )
				count++;
		}
	}
	return count;
}

// ----------------------------------------------------------------------------------
void CUiEditBox::OnUpdate(float elapsedTime)
{
	m_lastBlinkTime += elapsedTime;
	if (m_lastBlinkTime > m_blinkSpeed)
	{
		m_lastBlinkTime = 0.0f;
		m_caretBool = !m_caretBool;
	}	
	CUiFrame::OnUpdate(elapsedTime);
}

// ----------------------------------------------------------------------------------
void CUiEditBox::OnPaint(float alpha)
{
	// CUiFrame::OnPaint();

	if (m_fontString)
	{
		float scale = GetRealScale();
		float sx = m_position.m_x + m_textInset.m_left * scale;
		float sy = m_position.m_y + m_textInset.m_top * scale;
		CUiSize size = m_size * scale;

		CRuFontType* fontType = m_fontString->GetFontType();
		float fontHeight = (fontType) ? fontType->GetRealHeight() * scale : 0.0f;

		bool isTrail;
		float width = size.m_x - (m_textInset.m_left + m_textInset.m_right) * scale;
		int lastVisible = m_fontString->PosToCaret(m_firstVisible, width, isTrail);

		if (lastVisible != m_firstVisible)
		{
			// 繪出選取字元矩形框
			if ( m_caretPos != m_selStart )
			{
				CRuRectangle rect;
				CUiRGBA color(m_blockColor);
				color.SetAlphaScale(alpha);
				int first = max( min( m_caretPos, m_selStart ), m_firstVisible );
				int last = min( max( m_caretPos, m_selStart ), lastVisible );

				rect.m_left		= sx + m_fontString->CaretToPos(m_firstVisible, first);
				rect.m_right	= sx + m_fontString->CaretToPos(m_firstVisible, last);
				rect.m_top		= sy;
				rect.m_bottom	= sy + fontHeight;

				g_ruSceneRenderer->Ru2D_Begin();
				g_ruSceneRenderer->Ru2D_SetEffectType( ru2DFXTYPE_FLAT );
				g_ruSceneRenderer->Ru2D_SetEffectParameter( ru2DFXPARAM_ROTATE, ru2DROTATE_NONE );
				g_ruSceneRenderer->Ru2D_Submit(1, &rect, &rect, &color.m_value);
				g_ruSceneRenderer->Ru2D_End();
			}
			m_fontString->Render(alpha, sx, sy, m_firstVisible, lastVisible);
		}

		// 繪出游標
		if (m_pUi->GetLockKeyboard() == this && m_caretBool)
		{
			CRuRectangle rect;
			CUiRGBA color(0xFFFFFFFF);
			color.SetAlphaScale(alpha);
			rect.m_left		= sx + m_fontString->CaretToPos(m_firstVisible, m_caretPos);
			rect.m_right	= rect.m_left + 2.0f;
			rect.m_top		= sy;
			rect.m_bottom	= sy + fontHeight;

			if ( m_isInsertMode )
				rect.m_right += 4.0f;

			g_ruSceneRenderer->Ru2D_Begin();
			g_ruSceneRenderer->Ru2D_SetEffectType( ru2DFXTYPE_FLAT );
			g_ruSceneRenderer->Ru2D_SetEffectParameter( ru2DFXPARAM_ROTATE, ru2DROTATE_NONE );
			g_ruSceneRenderer->Ru2D_Submit(1, &rect, &rect, &color.m_value);
			g_ruSceneRenderer->Ru2D_End();
		}
	}	
}

// ----------------------------------------------------------------------------------
void CUiEditBox::OnMouseDown(CUiPoint pt, int key)
{
	switch (key)
	{
	case MOUSE_LBUTTON:
		if (m_fontString)
		{
			bool isTrail;
			float scale = GetRealScale();
			float offset = pt.m_x - (m_position.m_x + m_textInset.m_left * scale);
			float first = m_fontString->CaretToPos(0, m_firstVisible);
			int caret = m_fontString->PosToCaret(0, first+offset, isTrail);
			if (isTrail && caret < m_textSize)
				PlaceCaret(caret + 1);
			else
				PlaceCaret(caret);

			m_selStart = m_caretPos;
			ResetCaretBlink();
		}
		break;
	}
	m_pUi->LockKeyboard(this);
	CUiFrame::OnMouseDown(pt, key);
}

// ----------------------------------------------------------------------------------
void CUiEditBox::OnMouseMove(CUiPoint pt)
{
	//if ( m_isPushed )
	if ( m_pUi->GetMouseDown(MOUSE_LBUTTON) == this )
	{
		if (m_fontString)
		{
			bool isTrail;
			float scale = GetRealScale();
			float offset = pt.m_x - (m_position.m_x + m_textInset.m_left * scale);
			float first = m_fontString->CaretToPos(0, m_firstVisible);
			int caret = m_fontString->PosToCaret(0, first+offset, isTrail);
			if (isTrail && caret < m_textSize)
				PlaceCaret(caret + 1);
			else
				PlaceCaret(caret);
		}
	}
	CUiFrame::OnMouseMove(pt);
}

// ----------------------------------------------------------------------------------
void CUiEditBox::OnChar(WPARAM wParam)
{
	switch (wParam)
	{
	case VK_BACK:
		if( m_caretPos != m_selStart )
			DeleteSelectionText();
		else if( m_caretPos > 0 )
		{
			RemoveChar(m_caretPos - 1);
			OnTextChanged();

			// Move the caret, then delete the char.
			PlaceCaret(m_caretPos - 1);
			m_selStart = m_caretPos;
		}
		ResetCaretBlink();
		break;

	case 3:				// Ctrl-C Copy
	case 24:			// Ctrl-X Cut
		CopyToClipboard();

		// If the key is Ctrl-X, delete the selection too.
		if ( wParam == 24 )
			DeleteSelectionText();
		break;

	case 22:			// Ctrl-V Paste
		PasteFromClipboard();
		break;

	default:
		if (wParam < L' ')
			break;

		if (m_isNumeric && (wParam < L'0' || wParam > L'9'))
			break;

		// If there's a selection and the user starts to type, the selection should be deleted.
		if(m_caretPos != m_selStart)
			DeleteSelectionText();

		// If we are in overwrite mode and there is already
		// a char at the caret's position, simply replace it.
		// Otherwise, we insert the char as normal.	
		if (m_isInsertMode && m_caretPos < m_textSize)
		{
			m_text[m_caretPos] = (WCHAR)wParam;
			OnTextChanged();
			PlaceCaret( m_caretPos + 1 );
			m_selStart = m_caretPos;
		}
		// Insert the char
		else if ( InsertChar( m_caretPos, (WCHAR)wParam ) )
		{
			OnTextChanged();
			PlaceCaret( m_caretPos + 1 );
			m_selStart = m_caretPos;
		}
		ResetCaretBlink();
		break;
	}

	CUiFrame::OnChar(wParam);
}

// ----------------------------------------------------------------------------------
void CUiEditBox::OnPressed(WPARAM wParam)
{	
	const WCHAR* history = NULL;
	switch ( wParam )
	{
	case VK_DELETE:
		// Check if there is a text selection.
		if( m_caretPos != m_selStart )
			DeleteSelectionText();
		// Deleting one character
		else if ( RemoveChar( m_caretPos ) )
			OnTextChanged();

		ResetCaretBlink();
		break;

	case VK_INSERT:
		if( GetKeyState( VK_CONTROL ) < 0 )
		{
			// Control Insert. Copy to clipboard
			CopyToClipboard();
		}
		else if( GetKeyState( VK_SHIFT ) < 0 )
		{
			// Shift Insert. Paste from clipboard
			PasteFromClipboard();
		}
		else
		{
			// Toggle caret insert mode
			m_isInsertMode = !m_isInsertMode;
		}
		break;

	case VK_LEFT:
		if ( m_caretPos > 0 )
			PlaceCaret( m_caretPos - 1 );

		// Shift is not down
		if( GetKeyState( VK_SHIFT ) >= 0 )
			m_selStart = m_caretPos;

		ResetCaretBlink();
		break;

	case VK_RIGHT:
		if ( m_caretPos < m_textSize )
			PlaceCaret( m_caretPos + 1 );

		// Shift is not down
		if( GetKeyState( VK_SHIFT ) >= 0 )
			m_selStart = m_caretPos;

		ResetCaretBlink();
		break;

	case VK_HOME:
		PlaceCaret( 0 );

		// Shift is not down
		if( GetKeyState( VK_SHIFT ) >= 0 )
			m_selStart = m_caretPos;

		ResetCaretBlink();
		break;

	case VK_END:
		PlaceCaret( m_textSize );

		// Shift is not down
		if( GetKeyState( VK_SHIFT ) >= 0 )
			m_selStart = m_caretPos;

		ResetCaretBlink();
		break;
	case VK_UP:
		history = GetHistoryLine(m_historyLast - 1);
		if ( history )
		{			
			SetText(history);
			m_selStart = 0;
			PlaceCaret( m_textSize );
		}
		break;

	case VK_DOWN:
		history = GetHistoryLine(m_historyLast + 1);
		if ( history )
		{
			SetText(history);
			m_selStart = 0;
			PlaceCaret( m_textSize );
		}
		break;
	}

	CUiFrame::OnPressed(wParam);
}

// ----------------------------------------------------------------------------------
void CUiEditBox::OnTextChanged()
{
	CUiFrame::OnTextChanged();

	if (m_fontString)
		m_fontString->SetText(m_text);
}

