/**
 * @file llchatentry.cpp
 * @brief LLChatEntry implementation
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2012, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "llscrollcontainer.h"

#include "llchatentry.h"

static LLDefaultChildRegistry::Register<LLChatEntry> r("chat_editor");

LLChatEntry::Params::Params()
:	has_history("has_history", true),
 	is_expandable("is_expandable", false),
 	expand_lines_count("expand_lines_count", 1)
{}

LLChatEntry::LLChatEntry(const Params& p)
:	LLTextEditor(p),
 	mTextExpandedSignal(NULL),
 	mHasHistory(p.has_history),
 	mIsExpandable(p.is_expandable),
 	mExpandLinesCount(p.expand_lines_count),
 	mPrevLinesCount(0),
	mSingleLineMode(false),
	mPrevExpandedLineCount(S32_MAX),
	// <FS:Ansariel> FIRE-12537: CTRL-UP discards current input
	mCurrentInput("")
{
	// Initialize current history line iterator
	mCurrentHistoryLine = mLineHistory.begin();

	mAutoIndent = false;
	keepSelectionOnReturn(true);
}

LLChatEntry::~LLChatEntry()
{
	delete mTextExpandedSignal;
}

void LLChatEntry::draw()
{
	if(mIsExpandable)
	{
		reflow();
		expandText();
	}
	LLTextEditor::draw();
}

void LLChatEntry::onCommit()
{
	updateHistory();
	LLTextEditor::onCommit();
}

boost::signals2::connection LLChatEntry::setTextExpandedCallback(const commit_signal_t::slot_type& cb)
{
	if (!mTextExpandedSignal)
	{
		mTextExpandedSignal = new commit_signal_t();
	}
	return mTextExpandedSignal->connect(cb);
}

void LLChatEntry::expandText()
{
	S32 line_count = mSingleLineMode ? 1 : mExpandLinesCount;

	// <FS:Ansariel> Store result of getVisibleLines - it calls reflow
	//int visible_lines_count = llabs(getVisibleLines(true).first - getVisibleLines(true).second);
	std::pair<BOOL, BOOL> visible_lines = getVisibleLines(true);
	int visible_lines_count = llabs(visible_lines.first - visible_lines.second);
	// </FS:Ansariel>
	bool can_changed = getLineCount() <= line_count || line_count < mPrevExpandedLineCount ;
	mPrevExpandedLineCount = line_count;

	// true if pasted text has more lines than expand height limit and expand limit is not reached yet
	bool text_pasted = (getLineCount() > line_count) && (visible_lines_count < line_count);

	if (mIsExpandable && (can_changed || text_pasted || mSingleLineMode) && getLineCount() != mPrevLinesCount)
	{
		int lines_height = 0;
		if (text_pasted)
		{
			// text is pasted and now mLineInfoList.size() > mExpandLineCounts and mLineInfoList is not empty,
			// so lines_height is the sum of the last 'expanded_line_count' lines height
			lines_height = (mLineInfoList.end() - line_count)->mRect.mTop - mLineInfoList.back().mRect.mBottom;
		}
		else
		{
			lines_height = mLineInfoList.begin()->mRect.mTop - mLineInfoList.back().mRect.mBottom;
		}

		int height = mVPad * 2 +  lines_height;

		LLRect doc_rect = getRect();
		doc_rect.setOriginAndSize(doc_rect.mLeft, doc_rect.mBottom, doc_rect.getWidth(), height);
		setShape(doc_rect);

		mPrevLinesCount = getLineCount();

		if (mTextExpandedSignal)
		{
			(*mTextExpandedSignal)(this, LLSD() );
		}

		needsReflow();
	}
}

// line history support
void LLChatEntry::updateHistory()
{
	// On history enabled, remember committed line and
	// reset current history line number.
	// Be sure only to remember lines that are not empty and that are
	// different from the last on the list.
	if (mHasHistory && getLength())
	{
		// Add text to history, ignoring duplicates
		if (mLineHistory.empty() || getText() != mLineHistory.back())
		{
			mLineHistory.push_back(getText());
		}

		mCurrentHistoryLine = mLineHistory.end();
	}
}

void LLChatEntry::beforeValueChange()
{
    if(this->getLength() == 0 && !mLabel.empty())
    {
        this->clearSegments();
    }
}

void LLChatEntry::onValueChange(S32 start, S32 end)
{
    //Internally resetLabel() must meet a condition before it can reset the label
    resetLabel();
}

bool LLChatEntry::useLabel() const
{
    return !getLength() && !mLabel.empty();
}

void LLChatEntry::onFocusReceived()
{
	LLUICtrl::onFocusReceived();
	updateAllowingLanguageInput();
}

void LLChatEntry::onFocusLost()
{
	LLTextEditor::focusLostHelper();
	LLUICtrl::onFocusLost();
}

BOOL LLChatEntry::handleSpecialKey(const KEY key, const MASK mask)
{
	BOOL handled = FALSE;

	LLTextEditor::handleSpecialKey(key, mask);

	switch(key)
	{
	case KEY_RETURN:
		if (MASK_NONE == mask)
		{
			needsReflow();
		}
		// <FS:Ansariel> FIRE-12537: CTRL-UP discards current input
		mCurrentInput = "";

		break;

	case KEY_UP:
		if (mHasHistory && MASK_CONTROL == mask)
		{
			if (!mLineHistory.empty() && mCurrentHistoryLine > mLineHistory.begin())
			{
				// <FS:Ansariel> FIRE-12537: CTRL-UP discards current input
				if (mCurrentHistoryLine == mLineHistory.end())
				{
					mCurrentInput = getText();
				}
				// </FS:Ansariel>

				setText(*(--mCurrentHistoryLine));
				endOfDoc();
			}
			else
			{
				LLUI::getInstance()->reportBadKeystroke();
			}
			handled = TRUE;
		}
		break;

	case KEY_DOWN:
		if (mHasHistory && MASK_CONTROL == mask)
		{
			if (!mLineHistory.empty() && mCurrentHistoryLine < (mLineHistory.end() - 1) )
			{
				setText(*(++mCurrentHistoryLine));
				endOfDoc();
			}
			else if (!mLineHistory.empty() && mCurrentHistoryLine == (mLineHistory.end() - 1) )
			{
				mCurrentHistoryLine++;
				// <FS:Ansariel> FIRE-12537: CTRL-UP discards current input
				//std::string empty("");
				//setText(empty);
				setText(mCurrentInput);
				// </FS:Ansariel>
				needsReflow();
				endOfDoc();
			}
			else
			{
				LLUI::getInstance()->reportBadKeystroke();
			}
			handled = TRUE;
		}
		break;

	default:
		break;
	}

	return handled;
}

void LLChatEntry::enableSingleLineMode(bool single_line_mode)
{
	if (mScroller)
	{
		mScroller->setSize(single_line_mode ? 0 : -1);
	}

	mSingleLineMode = single_line_mode;
	mPrevLinesCount = -1;
	setWordWrap(!single_line_mode);
}


// <FS:Ansariel> Fix linefeed pasting
//virtual
void LLChatEntry::paste()
{
	LLTextEditor::paste();

	if (mSingleLineMode)
	{
		S32 cursor_pos = getCursorPos();
		LLWString content = getWText();
		LLWStringUtil::replaceChar(content, '\n', llwchar(182));
		setWText(content);
		setCursorPos(cursor_pos);
	}
}
// </FS:Ansariel>
