// Copyright (C) 2002-2007 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CGUIEditBox.h"
#include "IGUISkin.h"
#include "IGUIEnvironment.h"
#include "IGUIFont.h"
#include "IVideoDriver.h"
#include "rect.h"
#include "os.h"
#include "Keycodes.h"

namespace irr
{
namespace gui
{

//! constructor
CGUIEditBox::CGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment,
			IGUIElement* parent, s32 id,
			const core::rect<s32>& rectangle)
: IGUIEditBox(environment, parent, id, rectangle), MouseMarking(false),
	Border(border), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0),
	OverrideColor(video::SColor(101,255,255,255)),
	OverrideFont(0), CursorPos(0), ScrollPos(0), Max(0)
{
	#ifdef _DEBUG
	setDebugName("CGUIEditBox");
	#endif

	Text = text;

	Operator = environment->getOSOperator();

	if (Operator)
		Operator->grab();
}


//! destructor
CGUIEditBox::~CGUIEditBox()
{
	if (OverrideFont)
		OverrideFont->drop();

	if (Operator)
		Operator->drop();
}


//! Sets another skin independent font.
void CGUIEditBox::setOverrideFont(IGUIFont* font)
{
	if (OverrideFont)
		OverrideFont->drop();

	OverrideFont = font;

	if (OverrideFont)
		OverrideFont->grab();
}


//! Sets another color for the text.
void CGUIEditBox::setOverrideColor(video::SColor color)
{
	OverrideColor = color;
	OverrideColorEnabled = true;
}


//! Sets if the text should use the overide color or the
//! color in the gui skin.
void CGUIEditBox::enableOverrideColor(bool enable)
{
	OverrideColorEnabled = enable;
}


//! called if an event happened.
bool CGUIEditBox::OnEvent(SEvent event)
{
	switch(event.EventType)
	{
	case EET_GUI_EVENT:
		if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
		{
			if (event.GUIEvent.Caller == (IGUIElement*)this)
			{
				MouseMarking = false;
				MarkBegin = 0;
				MarkEnd = 0;
			}
		}
		break;
	case EET_KEY_INPUT_EVENT:
		if (processKey(event))
			return true;
		break;
	case EET_MOUSE_INPUT_EVENT:
		if (processMouse(event))
			return true;
		break;
	}

	return Parent ? Parent->OnEvent(event) : false;
}


bool CGUIEditBox::processKey(const SEvent& event)
{
	if (!event.KeyInput.PressedDown)
		return false;

	// control shortcut handling

	if (event.KeyInput.Control)
	{
		switch(event.KeyInput.Key)
		{
		case KEY_KEY_C:
			// copy to clipboard
			if (Operator && MarkBegin != MarkEnd)
			{
				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				core::stringc s;
				s = Text.subString(realmbgn, realmend - realmbgn).c_str();
				Operator->copyToClipboard(s.c_str());
			}
			break;
		case KEY_KEY_X:
			if ( !this->IsEnabled )
				break;

			// cut to the clipboard
			if (Operator && MarkBegin != MarkEnd)
			{
				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				// copy
				core::stringc sc;
				sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
				Operator->copyToClipboard(sc.c_str());

				// delete
				core::stringw s;
				s = Text.subString(0, realmbgn);
				s.append( Text.subString(realmend, Text.size()-realmend) );
				Text = s;

				CursorPos = realmbgn;
				MarkBegin = 0;
				MarkEnd = 0;
			}
			break;
		case KEY_KEY_V:
			if ( !this->IsEnabled )
				break;

			// paste from the clipboard
			if (Operator)
			{
				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				// add new character
				const c8* p = Operator->getTextFromClipboard();
				if (p)
				{
					if (MarkBegin == MarkEnd)
					{
						// insert text
						core::stringw s = Text.subString(0, CursorPos);
						s.append(p);
						s.append( Text.subString(CursorPos, Text.size()-CursorPos) );

						if (!Max || s.size()<=(u32)Max) // thx to Fish FH for fix
						{
							Text = s;
							s = p;
							CursorPos += s.size();
						}
					}
					else
					{
						// replace text

						core::stringw s = Text.subString(0, realmbgn);
						s.append(p);
						s.append( Text.subString(realmend, Text.size()-realmend) );

						if (!Max || s.size()<=(u32)Max)  // thx to Fish FH for fix
						{
							Text = s;
							s = p;
							CursorPos = realmbgn + s.size();
						}
					}
				}

				MarkBegin = 0;
				MarkEnd = 0;
			}
			break;
		default:
			return false;
		}
	}

	// default keyboard handling

	if (!event.KeyInput.Control)
	switch(event.KeyInput.Key)
	{
	case KEY_END:
		if (event.KeyInput.Shift)
		{
			if (MarkBegin == MarkEnd)
				MarkBegin = CursorPos;
			MarkEnd = Text.size();
		}
		else
		{
			MarkBegin = 0;
			MarkEnd = 0;
		}
		CursorPos = Text.size();
		BlinkStartTime = os::Timer::getTime();
		break;
	case KEY_HOME:
		if (event.KeyInput.Shift)
		{
			if (MarkBegin == MarkEnd)
				MarkBegin = CursorPos;
			MarkEnd = 0;
		}
		else
		{
			MarkBegin = 0;
			MarkEnd = 0;
		}
		CursorPos = 0;
		BlinkStartTime = os::Timer::getTime();
		break;
	case KEY_RETURN:
		{
			SEvent e;
			e.EventType = EET_GUI_EVENT;
			e.GUIEvent.Caller = this;
			e.GUIEvent.EventType = EGET_EDITBOX_ENTER;
			Parent->OnEvent(e);
		}
		break;
	case KEY_LEFT:

		if (event.KeyInput.Shift)
		{
			if (CursorPos > 0) 
			{ 
				if (MarkBegin == MarkEnd) 
					MarkBegin = CursorPos; 

				MarkEnd = CursorPos-1; 
			}
		}
		else
		{
			MarkBegin = 0;
			MarkEnd = 0;
		}

		if (CursorPos > 0) CursorPos--;
		BlinkStartTime = os::Timer::getTime();
		break;

	case KEY_RIGHT:

		if (event.KeyInput.Shift) 
		{ 
			if (Text.size() > (u32)CursorPos) 
			{ 
				if (MarkBegin == MarkEnd) 
					MarkBegin = CursorPos; 

				MarkEnd = CursorPos+1; 
			} 
		}
		else
		{
			MarkBegin = 0;
			MarkEnd = 0;
		}

		if (Text.size() > (u32)CursorPos) CursorPos++;
		BlinkStartTime = os::Timer::getTime();
		break;

	case KEY_BACK:
		if ( !this->IsEnabled )
			break;

		if (Text.size())
		{
			core::stringw s;
			
			if (MarkBegin != MarkEnd)
			{
				// delete marked text
				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				s = Text.subString(0, realmbgn);
				s.append( Text.subString(realmend, Text.size()-realmend) );
				Text = s;

				CursorPos = realmbgn;
			}
			else
			{
				// delete text behind cursor
				if (CursorPos>0)
					s = Text.subString(0, CursorPos-1);
				else
					s = L"";
				s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
				Text = s;
				--CursorPos;
			}

			if (CursorPos < 0)
				CursorPos = 0;
			BlinkStartTime = os::Timer::getTime();
			MarkBegin = 0;
			MarkEnd = 0;
		}
		break;
	case KEY_DELETE:
		if ( !this->IsEnabled )
			break;

		if (Text.size() != 0)
		{
			core::stringw s;

			if (MarkBegin != MarkEnd)
			{
				// delete marked text
				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				s = Text.subString(0, realmbgn);
				s.append( Text.subString(realmend, Text.size()-realmend) );
				Text = s;

				CursorPos = realmbgn;
			}
			else
			{
				// delete text before cursor
				s = Text.subString(0, CursorPos);
				s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) );
				Text = s;
			}

			if (CursorPos > (s32)Text.size())
				CursorPos = (s32)Text.size();

			BlinkStartTime = os::Timer::getTime();
			MarkBegin = 0;
			MarkEnd = 0;
		}
		break;
	default:
		if ( !this->IsEnabled )
			break;

		if (event.KeyInput.Char != 0)
		{
			if (Text.size() < (u32)Max || Max == 0)
			{
				core::stringw s;

				if (MarkBegin != MarkEnd)
				{
					// replace marked text
					s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
					s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

					s = Text.subString(0, realmbgn);
					s.append(event.KeyInput.Char);
					s.append( Text.subString(realmend, Text.size()-realmend) );
					Text = s;
					CursorPos = realmbgn+1;
				}
				else
				{
					// add new character
					s = Text.subString(0, CursorPos);
					s.append(event.KeyInput.Char);
					s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
					Text = s;
					++CursorPos;
				}
				
				BlinkStartTime = os::Timer::getTime();
				MarkBegin = 0;
				MarkEnd = 0;
			}
		}
		break;
	}

	// calculate scrollpos

	IGUIFont* font = OverrideFont;
	IGUISkin* skin = Environment->getSkin();
	if (!OverrideFont)
		font = skin->getFont();

	s32 cursorwidth = font->getDimension(L"_ ").Width;

	s32 minwidht = cursorwidth*2;
	if (minwidht >= AbsoluteRect.getWidth())
		minwidht = AbsoluteRect.getWidth() / 2;

	s32 tries = Text.size()*2;
	if (tries < 100)
		tries = 100;

	for (s32 t=0; t<tries; ++t)
	{
		core::stringw s = Text.subString(0,CursorPos);
		s32 charcursorpos = font->getDimension(s.c_str()).Width;

		s = Text.subString(0, ScrollPos);
		s32 charscrollpos = font->getDimension(s.c_str()).Width;

		if ((charcursorpos + cursorwidth - charscrollpos) > AbsoluteRect.getWidth())
			ScrollPos++;
		else
		if ((charcursorpos + cursorwidth - charscrollpos) < minwidht)
		{
			if (ScrollPos > 0)
				ScrollPos--;
			else
				break;
		}
		else
			break;
	}

	return true;
}

//! draws the element and its children
void CGUIEditBox::draw()
{
	if (!IsVisible)
		return;

	bool focus = Environment->hasFocus(this);

	IGUISkin* skin = Environment->getSkin();
	if (!skin)
		return;

	irr::video::IVideoDriver* driver = Environment->getVideoDriver();

	core::rect<s32> frameRect(AbsoluteRect);

	// draw the border

	if (Border)
	{
		skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW),
			false, true, frameRect, &AbsoluteClippingRect);
		
		frameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X);
	}

	// draw the text

	IGUIFont* font = OverrideFont;
	if (!OverrideFont)
		font = skin->getFont();

	if (font)
	{
		// calculate cursor pos

		core::stringw s = Text.subString(0,CursorPos);
		s32 charcursorpos = font->getDimension(s.c_str()).Width;

		s = Text.subString(0, ScrollPos);
		s32 charscrollpos = font->getDimension(s.c_str()).Width;

		core::rect<s32> rct;

		// draw mark

		if (focus && MarkBegin != MarkEnd)
		{
			rct = frameRect;

			rct.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y);
			rct.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y);

			s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
			s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

			s = Text.subString(0, realmbgn);
			s32 mbegin = font->getDimension(s.c_str()).Width;

			s = Text.subString(realmbgn, realmend - realmbgn);
			s32 mend = font->getDimension(s.c_str()).Width;

			rct.UpperLeftCorner.X  += mbegin - charscrollpos;
			rct.LowerRightCorner.X = rct.UpperLeftCorner.X + mend;

			driver->draw2DRectangle(skin->getColor(EGDC_HIGH_LIGHT), rct, &AbsoluteClippingRect);
		}

		// draw cursor

		if (focus && (os::Timer::getTime() - BlinkStartTime) % 700 < 350)
		{
			rct = frameRect;
			rct.UpperLeftCorner.X += charcursorpos;
			rct.UpperLeftCorner.X -= charscrollpos;

			font->draw(L"_", rct, 
				OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
				false, true, &AbsoluteClippingRect);
		}

		// draw text

		if (Text.size())
		{
			rct = frameRect;
			rct.UpperLeftCorner.X -= charscrollpos;

			// Save the override color information.
			// Then, alter it if the edit box is disabled.
			bool prevOver = OverrideColorEnabled;
			video::SColor prevColor = OverrideColor;
			if ( !this->IsEnabled && !OverrideColorEnabled )
			{
				OverrideColorEnabled = true;
				OverrideColor = skin->getColor( EGDC_GRAY_TEXT );
			}

			if (focus && MarkBegin != MarkEnd)
			{
				// marked text

                font->draw(Text.c_str(), rct, 
					OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
					false, true, &AbsoluteClippingRect);

				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				s = Text.subString(0, realmbgn);
				s32 mbegin = font->getDimension(s.c_str()).Width;

				s = Text.subString(realmbgn, realmend - realmbgn);

				rct.UpperLeftCorner.X += mbegin;

				font->draw(s.c_str(), rct, 
					OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
					false, true, &AbsoluteClippingRect);								
			}
			else
			{
				// normal text
				font->draw(Text.c_str(), rct, 
					OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
					false, true, &AbsoluteClippingRect);
			}

			// Return the override color information to its previous settings.
			OverrideColorEnabled = prevOver;
			OverrideColor = prevColor;
		}
	}
}

//! Sets the new caption of this element.
void CGUIEditBox::setText(const wchar_t* text)
{
	Text = text;
	CursorPos = 0;
	ScrollPos = 0;
	MarkBegin = 0;
	MarkEnd = 0;
}


//! Sets the maximum amount of characters which may be entered in the box.
//! \param max: Maximum amount of characters. If 0, the character amount is 
//! infinity.
void CGUIEditBox::setMax(s32 max)
{
	Max = max;
	if (Max < 0)
		Max = 0;

	if (Text.size() > (u32)Max && Max != 0)
		Text = Text.subString(0, Max);
}


//! Returns maximum amount of characters, previously set by setMax();
s32 CGUIEditBox::getMax()
{
	return Max;
}


bool CGUIEditBox::processMouse(const SEvent& event)
{
	switch(event.MouseInput.Event)
	{
	case irr::EMIE_LMOUSE_LEFT_UP:
		if (Environment->hasFocus(this))
		{
			CursorPos = getCursorPos(event.MouseInput.X);
			if (MouseMarking)
				MarkEnd = CursorPos;
			MouseMarking = false;			
			return true;
		}
		break;
	case irr::EMIE_MOUSE_MOVED:
		{
			if (MouseMarking)
			{
				CursorPos = getCursorPos(event.MouseInput.X);
				MarkEnd = CursorPos;
				return true;
			}
		}
		break;
	case EMIE_LMOUSE_PRESSED_DOWN:
		if (!Environment->hasFocus(this))
		{
			// get focus
			BlinkStartTime = os::Timer::getTime();
			Environment->setFocus(this);
			MouseMarking = true;
			CursorPos = getCursorPos(event.MouseInput.X);
			MarkBegin = CursorPos;
			MarkEnd = CursorPos;
			return true;
		}
		else
		{
			if (!AbsoluteClippingRect.isPointInside(
			core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y)))
			{
				// remove focus
				Environment->removeFocus(this);
				return false;
			}

			// move cursor

			CursorPos = getCursorPos(event.MouseInput.X);

			if (!MouseMarking)
				MarkBegin = CursorPos;

			MouseMarking = true;
			MarkEnd = CursorPos;
			return true;
		}	
	}

	return false;
}


s32 CGUIEditBox::getCursorPos(s32 x)
{
	IGUIFont* font = OverrideFont;
	IGUISkin* skin = Environment->getSkin();
	if (!OverrideFont)
		font = skin->getFont();

	core::stringw s = Text.subString(0, ScrollPos);
	s32 charscrollpos = font->getDimension(s.c_str()).Width;

	s32 idx = font->getCharacterFromPos(Text.c_str(), x - (AbsoluteRect.UpperLeftCorner.X + 3) + charscrollpos);	
	if (idx != -1)
		return idx;		

	return Text.size();
}

//! Writes attributes of the element.
void CGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0)
{
	IGUIEditBox::serializeAttributes(out,options);
	out->addBool	("OverrideColorEnabled",		OverrideColorEnabled );
	out->addColor	("OverrideColor",				OverrideColor);
	// out->addFont("OverrideFont",OverrideFont);
	out->addInt		("MaxChars",					Max);
}

//! Reads attributes of the element
void CGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
{
	IGUIEditBox::deserializeAttributes(in,options);
	setOverrideColor(in->getAttributeAsColor("OverrideColor"));
	enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
	setMax(in->getAttributeAsInt("MaxChars"));

	// setOverrideFont(in->getAttributeAsFont("OverrideFont"));
}


} // end namespace gui
} // end namespace irr

