/*****
*
*  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 2 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, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*****/


#include "gui/InputField.h"
#include "gui/TextManager.h"
#include "loaders/FontLoader.h"

#include <cassert>

namespace GUI {

InputField::~InputField ()
{
	delete tElement;
	delete caret;
}

InputField::InputField (int x, int y, unsigned int width, unsigned int height,
						const char *text, unsigned short hue,
						unsigned char font, char passwordChar)
{
	OnKeyPress (NULL);
	this->x = x;
	this->y = y;
	this->_passwordChar = passwordChar;
	_width = width;

	if (height != 0)
	{
		_height = height;
	}
	else
	{
		const stFont *pFont = pFontLoader.getFont (font);

		if (pFont)
			_height = pFont->maxHeight;
	}

	// 2 * _height
	unsigned int *data = new unsigned int[_height * 2];

	for (int i = 0; i < _height * 2; ++i)
		data[i] = 0x7FFFFFFF;

	caret = new Texture;
	caret->LoadFromData (data, 2, _height, 32, GL_NEAREST);

	delete data;


	_caretPos = _text.length ();
	xCropOffset = 0;

	_hue = hue;
	_font = font;
	tElement = 0;
	generated = false;

	SetFlags (GUMPFLAG_FOCUSABLE);

	control_type = CONTROLTYPE_INPUTFIELD;

	IgnoreCursorKeys = false;

	setText ((char *) text, true);
	_insertMode = true;
}

void InputField::setText (const char *text, bool moveCaret/*=true*/)
{
	if (text)
	{
		_text = text;
	}
	else
	{
		_text = "";
	}

	delete tElement;

	if (_passwordChar)
	{
		// Create a masked text
		char *pText = new char[_text.length () + 1];

		pText[_text.length ()] = 0;
		memset (pText, _passwordChar, _text.length ());
		tElement = new cTextElement (pText, _hue, _font, 0, false);
		delete pText;
	}
	else
	{
		tElement = new cTextElement (_text.c_str (), _hue, _font, 0, false);
	}

	if (moveCaret) _caretPos = _text.length();

	recalcXCrop ();
}

void InputField::regenerate ()
{
	delete tElement;

	if (_passwordChar)
	{
		// Create a masked text
		char *pText = new char[_text.length () + 1];

		pText[_text.length ()] = 0;
		memset (pText, _passwordChar, _text.length ());
		tElement = new cTextElement (pText, _hue, _font, 0, false);
		delete pText;
	}
	else
	{
		tElement = new cTextElement (_text.c_str (), _hue, _font, 0, false);
	}


#if 0
	const stFont *font = pFontLoader.getFont (_font);

	if (!font)
		return;
#endif

	//  unsigned int innerWidth = _width;
	//  unsigned int innerHeight = font->maxHeight;

	recalcXCrop ();

	generated = true;
}

// check *p is trail_byte
// base is string start pointer
static bool istrailbyte( const char *base, const char *p )
{
	int lbc = 0;    // lead byte count

	assert(base <= p);

	while (p > base ) 
	{
		if (!::isleadbyte(*--p)) break;
		lbc++;
	}

	return (lbc & 1);
}

void InputField::updateCaretPos(bool isRight)
{
	int len = _text.length();
	_caretPos = std::max(0, std::min((int)_caretPos, len));

	const char *s = _text.c_str();
	if (_caretPos < len && istrailbyte(s, s + _caretPos))
		_caretPos += isRight ? 1:-1;
}

void InputField::recalcXCrop ()
{
	if (_caretPos == 0)
	{
		caretX = x;
		xCropOffset = 0;
		return;
	}
	// This has to be called whenever the caret position changes
	// It recalculates the inner X offset for the InputField, 
	// so the caret remains always visible to the user.

	// Relative Position of the Caret inside the text-texture
	// For passwords we need to calc the size for our password char and we're done
	unsigned int relX;

	if (_passwordChar)
	{
		/*const stFont *pFont = FontLoader->getFont( _font );

		if( !pFont )
		return;

		relX = _caretPos * pFont->chars[ _passwordChar ].width; */

		char *pText = new char[_text.length () + 1];

		pText[_text.length ()] = 0;
		memset (pText, _passwordChar, _text.length ());
		relX = pTextManager->getTextWidth (pText, _font, false) - 1;
		delete pText;
	}
	else
	{
		relX =
			pTextManager->getTextWidth (_text.substr (0, _caretPos).c_str (),
			_font, false) - 1;
	}

	unsigned int innerWidth = _width;

	if (tElement->width () <= innerWidth)
	{
		xCropOffset = 0;
		caretX = x + relX;
		return;
	}
	// Caret is left of our clip-box
	if (relX < xCropOffset)
	{
		// We need to move the "crop-window" to the left
		xCropOffset -= xCropOffset - relX - 1;
	}
	// It's "in" our clipbox (or right of it)
	else if (relX >= innerWidth)
	{
		unsigned int tempOffset = (relX + 1) - innerWidth;

		// If we are moving from right to left but we're still visible
		// there's no need to change xCropOffset
		// -----------------------------------------------------------
		// There's another problem we need to take care of:
		// When someone deletes text from right-to-left
		// then there is an "empty" space right of the caret
		// This needs to be fixed as gl will tile the texture
		// which leeds to an unwanted gfx glitch.
		if (tElement->width () - xCropOffset < innerWidth
			|| tempOffset > xCropOffset || relX - xCropOffset - 1 < 0)
			xCropOffset = tempOffset;
	}

	caretX = x + relX - xCropOffset;
}

void InputField::Draw (GumpHandler * gumps)
{
	Control::Draw (gumps);
	if (!generated)
		regenerate ();

	if (!tElement)
		return;

	// Three levels of Height (First Row = y, Second Row = y + HeightOfBackground(), Third Row + y + HeightOfBackground() + HeightOfFont())
#if 0
	const stFont *font = pFontLoader.getFont (_font);

	if (!font)
		return;
#endif

	// Do we draw the outer "Border" ?
	// Relevant for our border here are just the corners (positioning)
	unsigned int yFontOffset = _height - tElement->height ();

	tElement->draw (x, y + yFontOffset, _width, 0, xCropOffset);

	// Draw the Caret (y = secondRow, height = inner_height, x = secondCol + ( TextLen (0, caretPos) - xOffset ) )
	if (GetFocus ())
	{
		// Drawn Damn Rect
		DrawRect (caretX, y + 1, 2, _height - 2, caret);
	}
}

void InputField::addChar(Uint16 ch, bool insertMode, bool moveCaret/*=true*/)
{
	int caretPos = _caretPos;
	char *c = reinterpret_cast<char*>(&ch);
	char s[3] = {0,};
	int len = 0;

	s[len++] = *c++;
	if (::isleadbyte(*c))  s[len++] = *c;

	if (insertMode) {
		_text.insert(caretPos, s);
	} else {
		_text.replace(caretPos, len, s);
	}

	if (moveCaret) _caretPos += len;

	setText(_text.c_str(),false);
}

int InputField::delChar()
{
	if (_caretPos < 0 || _caretPos >= _text.length()) return 0;

	int delCh = ::isleadbyte(*(_text.c_str() + _caretPos)) ? 2 : 1;

	std::string::iterator i = _text.begin() + _caretPos;
	std::string::iterator j = i + delCh;
	_text.erase(i,j);

	return delCh;
}

int InputField::HandleMessage (gui_message * msg)
{
	if (!tElement)
		return false;

	if (msg->type == MESSAGE_KEYPRESSED && focus) {
		// Is the key a "normal" character, then append it
		Uint16 key = msg->keypressed.key;
		Uint32 mod = msg->keypressed.mod;

		bool updated = false;

#ifdef _MSC_VER
		if (mod & KMOD_IME_COMP)
		{
			if (_passwordChar) return true;

			bool insertMode = (KMOD_IME_COMP_START & mod) && _insertMode;

			if (KMOD_IME_COMP_RESULT & mod)
				addChar(key, false, true);
			else
				addChar(key, insertMode, false);

			return true;
		} else 
#endif        
        if(key >= 32 && key <= 126) {
			addChar(key, _insertMode, true);
			return true;
		} else if((key == SDLK_LEFT) && !IgnoreCursorKeys) {
			if(_caretPos > 0) {
				_caretPos--;
				updateCaretPos(false);
				recalcXCrop ();
			}
			return true;
		} else if((key == SDLK_RIGHT) && !IgnoreCursorKeys) {
			if(_caretPos < _text.length()) {
				_caretPos++;
				updateCaretPos(true);
				recalcXCrop ();
			}
			return true;
		} else if(key == SDLK_BACKSPACE) {
			// Delete the character before the caret
			_caretPos--;
			updateCaretPos(false);
			delChar();
			setText(_text.c_str(),false);
			return true;
		} else if(key == SDLK_DELETE) {
			// Use our current caret position ?!
			// Nope -> forget it
			delChar();
			setText(_text.c_str(),false);
			return true;
		} else if(key == SDLK_HOME) {
			_caretPos = 0;
			recalcXCrop();
			return true;
		} else if(key == SDLK_END) {
			_caretPos = _text.length();
			recalcXCrop ();
			return true;
		}

		if(callback_OnKeyPress) {
			callback_OnKeyPress (this, msg->keypressed.key);
			return true;
		}
	}

	//  bool msgT = msg->type == MESSAGE_MOUSEDOWN;

	return Control::HandleMessage (msg);
}


bool InputField::MouseIsOver (int x, int y)
{
	return (x >= this->x && x <= this->x + this->_width && y >= this->y
		&& y <= this->y + this->_height);
}

void InputField::
OnKeyPress (int (*callback) (Control * sender, unsigned short key))
{
	callback_OnKeyPress = callback;
}

void InputField::SetIgnoreCursorKeys (bool IgnoreCursorKeys)
{
	this->IgnoreCursorKeys = IgnoreCursorKeys;
}


void InputField::DoOnKeyPress (int key)
{
	if (callback_OnKeyPress)
		callback_OnKeyPress (this, key);
}

}
