// This file is part of VSTGUI. It is subject to the license terms 
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE

#include "ctextedit.h"
#include "itexteditlistener.h"
#include "../cframe.h"
#include "../events.h"
#include "../platform/iplatformframe.h"
#include <cassert>

namespace VSTGUI {

//------------------------------------------------------------------------
// CTextEdit
//------------------------------------------------------------------------
/*! @class CTextEdit
Define a rectangle view where a text-value can be displayed and edited with a given font and color.
The user can specify its convert function (from char to char). The text-value is centered in the given rect.
A bitmap can be used as background.
*/
//------------------------------------------------------------------------
/**
 * CTextEdit constructor.
 * @param size the size of this view
 * @param listener the listener
 * @param tag the control tag
 * @param txt the initial text as c string (UTF-8 encoded)
 * @param background the background bitmap
 * @param style the display style (see CParamDisplay for styles)
 */
//------------------------------------------------------------------------
CTextEdit::CTextEdit (const CRect& size, IControlListener* listener, int32_t tag, UTF8StringPtr txt,
                      CBitmap* background, const int32_t style)
: CTextLabel (size, txt, background, style)
{
	this->listener = listener;
	this->tag = tag;

	setWantsFocus (true);
}

//------------------------------------------------------------------------
CTextEdit::CTextEdit (const CTextEdit& v)
: CTextLabel (v)
, bWasReturnPressed (false)
, stringToValueFunction (v.stringToValueFunction)
, immediateTextChange (v.immediateTextChange)
, secureStyle (v.secureStyle)
, platformFont (v.platformFont)
, placeholderString (v.placeholderString)
{
	setWantsFocus (true);
}

//------------------------------------------------------------------------
CTextEdit::~CTextEdit () noexcept
{
	listener = nullptr;
	vstgui_assert (platformControl == nullptr);
}

//------------------------------------------------------------------------
void CTextEdit::setStringToValueFunction (const StringToValueFunction& stringToValueFunc)
{
	stringToValueFunction = stringToValueFunc;
}

//------------------------------------------------------------------------
void CTextEdit::setStringToValueFunction (StringToValueFunction&& stringToValueFunc)
{
	stringToValueFunction = std::move (stringToValueFunc);
}

//------------------------------------------------------------------------
void CTextEdit::setImmediateTextChange (bool state)
{
	immediateTextChange = state;
}

//------------------------------------------------------------------------
void CTextEdit::setSecureStyle (bool state)
{
	if (secureStyle != state)
	{
		secureStyle = state;
		if (platformControl)
		{
			
		}
	}
}

//------------------------------------------------------------------------
bool CTextEdit::getSecureStyle () const
{
	return secureStyle;
}

//------------------------------------------------------------------------
void CTextEdit::registerTextEditListener (ITextEditListener* listener)
{
	textEditListeners.add (listener);
}

//------------------------------------------------------------------------
void CTextEdit::unregisterTextEditListener (ITextEditListener* listener)
{
	textEditListeners.remove (listener);
}

//------------------------------------------------------------------------
void CTextEdit::setValue (float val)
{
	CTextLabel::setValue (val);
	bool converted = false;
	std::string string;
	if (valueToStringFunction)
		converted = valueToStringFunction (getValue (), string, this);
	if (!converted)
	{
		char tmp[255];
		char precisionStr[10];
		snprintf (precisionStr, 10, "%%.%hhuf", valuePrecision);
		snprintf (tmp, 255, precisionStr, getValue ());
		string = tmp;
	}

	if (converted)
	{
		CTextLabel::setText (UTF8String (std::move (string)));
		if (platformControl)
			platformControl->setText (getText ());
	}
	else
		setText (UTF8String (std::move (string)));
}

//------------------------------------------------------------------------
void CTextEdit::setText (const UTF8String& txt)
{
	if (stringToValueFunction)
	{
		float val = getValue ();
		if (stringToValueFunction (txt, val, this))
		{
			CTextLabel::setValue (val);
			if (valueToStringFunction)
			{
				std::string string;
				valueToStringFunction (getValue (), string, this);
				CTextLabel::setText (UTF8String (std::move (string)));
				if (platformControl)
					platformControl->setText (getText ());
				return;
			}
		}
	}
	CTextLabel::setText (txt);
	if (platformControl)
		platformControl->setText (getText ());
}

//------------------------------------------------------------------------
void CTextEdit::valueChanged ()
{
	if (stringToValueFunction)
		CTextLabel::valueChanged ();
	
	CParamDisplay::valueChanged ();
}

//------------------------------------------------------------------------
void CTextEdit::setPlaceholderString (const UTF8String& str)
{
	placeholderString = str;
}

//------------------------------------------------------------------------
void CTextEdit::draw (CDrawContext *pContext)
{
	if (platformControl)
	{
		drawBack (pContext);
		if (!platformControl->drawsPlaceholder () && !placeholderString.empty () &&
		    platformControl->getText ().empty ())
		{
			pContext->saveGlobalState ();
			pContext->setGlobalAlpha (pContext->getGlobalAlpha () * 0.5f);
			drawPlatformText (pContext, placeholderString.getPlatformString ());
			pContext->restoreGlobalState ();
		}
		setDirty (false);
		return;
	}
	drawBack (pContext);
	if (text.empty ())
	{
		if (!placeholderString.empty ())
		{
			pContext->saveGlobalState ();
			pContext->setGlobalAlpha (pContext->getGlobalAlpha () * 0.5f);
			drawPlatformText (pContext, placeholderString.getPlatformString ());
			pContext->restoreGlobalState ();
		}
	}
	else if (getSecureStyle ())
	{
		constexpr auto bulletCharacter = "\xE2\x80\xA2";
		UTF8String str;
		for (auto i = 0u; i < text.length (); ++i)
			str += bulletCharacter;
		drawPlatformText (pContext, str.getPlatformString ());
	}
	else
		CTextLabel::draw (pContext);
	setDirty (false);
}

//------------------------------------------------------------------------
CMouseEventResult CTextEdit::onMouseDown (CPoint& where, const CButtonState& buttons)
{
	if (buttons & kLButton)
	{
		if (getFrame ()->getFocusView () != this)
		{
			if (isDoubleClickStyle ())
			{
				if (!(buttons & kDoubleClick))
					return kMouseEventNotHandled;
			}
		
			takeFocus ();
			return kMouseDownEventHandledButDontNeedMovedOrUpEvents;
		}
	}
	return kMouseEventNotHandled;
}

//------------------------------------------------------------------------
void CTextEdit::onKeyboardEvent (KeyboardEvent& event)
{
	if (!platformControl || event.type != EventType::KeyDown)
		return;
	
	if (event.virt == VirtualKey::Escape)
	{
		bWasReturnPressed = false;
		platformControl->setText (text);
		getFrame ()->setFocusView (nullptr);
		looseFocus ();
		event.consumed = true;
	}
	else if (event.virt == VirtualKey::Return)
	{
		bWasReturnPressed = true;
		getFrame ()->setFocusView (nullptr);
		looseFocus ();
		event.consumed = true;
	}
}

//------------------------------------------------------------------------
CFontRef CTextEdit::platformGetFont () const
{
	CFontRef font = getFont ();
	CCoord fontSize = font->getSize ();
	fontSize *= getGlobalTransform ().m11;
	if (fontSize == font->getSize ())
		return font;
	platformFont = makeOwned<CFontDesc> (*font);
	platformFont->setSize (fontSize);
	return platformFont;
}

//------------------------------------------------------------------------
CRect CTextEdit::platformGetSize () const
{
	return translateToGlobal (getViewSize ());
}

//------------------------------------------------------------------------
CRect CTextEdit::platformGetVisibleSize () const
{
	return translateToGlobal (getVisibleViewSize ());
}

//------------------------------------------------------------------------
void CTextEdit::platformLooseFocus (bool returnPressed)
{
	remember ();
	bWasReturnPressed = returnPressed;
	if (getFrame ()->getFocusView () == this)
		getFrame ()->setFocusView (nullptr);
	forget ();
}

//------------------------------------------------------------------------
void CTextEdit::platformOnKeyboardEvent (KeyboardEvent& event)
{
	dynamic_cast<IPlatformFrameCallback*> (getFrame ())->platformOnEvent (event);
	if (event.consumed)
		return;
	if (event.virt == VirtualKey::Return)
	{
		platformLooseFocus (true);
		event.consumed = true;
	}
	else if (event.virt == VirtualKey::Escape)
	{
		platformLooseFocus (false);
		event.consumed = true;
	}
}

//------------------------------------------------------------------------
void CTextEdit::platformTextDidChange ()
{
	if (platformControl && immediateTextChange)
		updateText (platformControl);
}

//------------------------------------------------------------------------
bool CTextEdit::platformIsSecureTextEdit ()
{
	return getSecureStyle ();
}

//------------------------------------------------------------------------
void CTextEdit::parentSizeChanged ()
{
	if (platformControl)
		platformControl->updateSize ();
}

//------------------------------------------------------------------------
void CTextEdit::setViewSize (const CRect& newSize, bool invalid)
{
	CTextLabel::setViewSize (newSize, invalid);
	if (platformControl)
		platformControl->updateSize ();
}

//------------------------------------------------------------------------
void CTextEdit::createPlatformTextEdit ()
{
	if (platformControl)
		return;
	
	bWasReturnPressed = false;
	platformControl = getFrame ()->getPlatformFrame ()->createPlatformTextEdit (this);
	textEditListeners.forEach (
	    [this] (ITextEditListener* l) { l->onTextEditPlatformControlTookFocus (this); });
}

//------------------------------------------------------------------------
bool CTextEdit::wantsFocus () const
{
	if (isDoubleClickStyle () && !platformControl)
		return false;
	return CTextLabel::wantsFocus ();
}

//------------------------------------------------------------------------
void CTextEdit::takeFocus ()
{
	if (!getFrame ())
		return;
	createPlatformTextEdit ();
	if (getFrame()->getFocusView () != this)
		getFrame()->setFocusView (this);
	CTextLabel::takeFocus ();
	invalid ();
}

//------------------------------------------------------------------------
void CTextEdit::looseFocus ()
{
	if (platformControl == nullptr)
		return;

	CBaseObjectGuard guard (this);

	auto _platformControl = std::move (platformControl);
	updateText (_platformControl);
	
	_platformControl = nullptr;

	textEditListeners.forEach (
	    [this] (ITextEditListener* l) { l->onTextEditPlatformControlLostFocus (this); });

	// if you want to destroy the text edit do it with the loose focus message
	CView* receiver = getParentView () ? getParentView () : getFrame ();
	while (receiver)
	{
		if (receiver->notify (this, kMsgLooseFocus) == kMessageNotified)
			break;
		receiver = receiver->getParentView ();
	}
	CTextLabel::looseFocus ();
	invalid ();
}

//------------------------------------------------------------------------
void CTextEdit::updateText (IPlatformTextEdit* pte)
{
	auto newText = pte->getText ();
	if (newText != getText ())
	{
		beginEdit ();

		setText (newText);

		CParamDisplay::valueChanged ();

		endEdit ();
	}
}

} // VSTGUI
