/** 
 * @file llconsole.cpp
 * @brief a scrolling console output device
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, 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 "llviewerprecompiledheaders.h"
#include "linden_common.h"

#include "llconsole.h"

// linden library includes
#include "llmath.h"
//#include "llviewercontrol.h"
#include "llcriticaldamp.h"
#include "llfontgl.h"
#include "llgl.h"
#include "llui.h"
#include "lluiimage.h"
//#include "llviewerimage.h"
//#include "llviewerimagelist.h"
//#include "llviewerwindow.h"
#include "llsd.h"
#include "llfontgl.h"
#include "llmath.h"
#include "llurlregistry.h" // for SLURL parsing

//#include "llstartup.h"

// Used for LCD display
extern void AddNewDebugConsoleToLCD(const LLWString &newLine);

LLConsole* gConsole = NULL;  // Created and destroyed in LLViewerWindow.

const F32 FADE_DURATION = 2.f;
 
static LLDefaultChildRegistry::Register<LLConsole> r("console");

LLConsole::LLConsole(const LLConsole::Params& p) 
:	LLUICtrl(p),
	LLFixedBuffer(p.max_lines),
	mLinePersistTime(p.persist_time), // seconds
	mFont(p.font),
	mConsoleWidth(0),
	mConsoleHeight(0),
	mParseUrls(p.parse_urls), // <FS:Ansariel> If lines should be parsed for URLs
	mSessionSupport(p.session_support) // <FS:Ansariel> Session support
{
	if (p.font_size_index.isProvided())
	{
		setFontSize(p.font_size_index);
	}
	mFadeTime = mLinePersistTime - FADE_DURATION;
	setMaxLines(LLUI::getInstance()->mSettingGroups["config"]->getS32("ConsoleMaxLines"));
	
	// <FS:Ansariel> Configurable background for different console types
	mBackgroundImage = LLUI::getUIImage(p.background_image);
}

void LLConsole::setLinePersistTime(F32 seconds)
{
	mLinePersistTime = seconds;
	mFadeTime = mLinePersistTime - FADE_DURATION;
}

void LLConsole::reshape(S32 width, S32 height, BOOL called_from_parent)
{
	S32 new_width = llmax(50, llmin(getRect().getWidth(), width));
	S32 new_height = llmax(llfloor(mFont->getLineHeight()) + 15, llmin(getRect().getHeight(), height));

	if (   mConsoleWidth == new_width
		&& mConsoleHeight == new_height )
	{
		return;
	}
	
	mConsoleWidth = new_width;
	mConsoleHeight= new_height;
	
	LLUICtrl::reshape(new_width, new_height, called_from_parent);
	
	for(paragraph_t::iterator paragraph_it = mParagraphs.begin(); paragraph_it != mParagraphs.end(); paragraph_it++)
	{		
		// <FS:Ansariel> Added styleflags parameter for style customization
		//(*paragraph_it).updateLines((F32)getRect().getWidth(), mFont, true);
		(*paragraph_it).updateLines((F32)getRect().getWidth(), mFont, (*paragraph_it).mLines.front().mStyleFlags, true);
		// </FS:Ansariel>
	}
}

void LLConsole::setFontSize(S32 size_index)
{
	if (-1 == size_index)
	{
		mFont = LLFontGL::getFontMonospace();
	}
	else if (0 == size_index)
	{
		// <FS>
		//mFont = LLFontGL::getFontSansSerif();
		mFont = LLFontGL::getFontSansSerifSmall();
		// </FS>
	}
	else if (1 == size_index)
	{
		// <FS>
		//mFont = LLFontGL::getFontSansSerifBig();
		mFont = LLFontGL::getFontSansSerif();
		// </FS>
	}
	// <FS>
	else if (2 == size_index)
	{
		mFont = LLFontGL::getFontSansSerifBig();
	}
	// </FS>
	else
	{
		mFont = LLFontGL::getFontSansSerifHuge();
	}
	// Make sure the font exists
	if (mFont == NULL)
	{
		mFont = LLFontGL::getFontDefault();
	}
	
	for(paragraph_t::iterator paragraph_it = mParagraphs.begin(); paragraph_it != mParagraphs.end(); paragraph_it++)
	{
		// <FSAnsariel> Added styleflags parameter for style customization
		//(*paragraph_it).updateLines((F32)getRect().getWidth(), mFont, true);
		(*paragraph_it).updateLines((F32)getRect().getWidth(), mFont, (*paragraph_it).mLines.front().mStyleFlags, true);
		// </FS:Ansariel>
	}
}

void LLConsole::draw()
{
	// Units in pixels
	// <FS>
	//static const F32 padding_horizontal = 10;
	//static const F32 padding_vertical = 3;
	static const F32 padding_horizontal = 15;
	static const F32 padding_vertical = 8;
	// </FS>
	LLGLSUIDefault gls_ui;

	// skip lines added more than mLinePersistTime ago
	F32 cur_time = mTimer.getElapsedTimeF32();
	
	F32 skip_time = cur_time - mLinePersistTime;
	F32 fade_time = cur_time - mFadeTime;

	if (mParagraphs.empty()) 	//No text to draw.
	{
		return;
	}

	// <FS:Ansariel> Session support
	if (!mSessionSupport)
	{
		// This is done in update() if session support is enabled
	// </FS:Ansariel>
	U32 num_lines=0;

	paragraph_t::reverse_iterator paragraph_it;
	paragraph_it = mParagraphs.rbegin();
	U32 paragraph_num=mParagraphs.size();
	
	while (!mParagraphs.empty() && paragraph_it != mParagraphs.rend())
	{
		num_lines += (*paragraph_it).mLines.size();
		if(num_lines > mMaxLines 
			|| ( (mLinePersistTime > (F32)0.f) && ((*paragraph_it).mAddTime - skip_time)/(mLinePersistTime - mFadeTime) <= (F32)0.f)) 
		{							//All lines above here are done.  Lose them.
			for (U32 i=0;i<paragraph_num;i++)
			{
				if (!mParagraphs.empty())
					mParagraphs.pop_front();
			}
			break;
		}
		paragraph_num--;
		paragraph_it++;
	}
	// <FS:Ansariel> Session support
	}
	// </FS:Ansariel>

	if (mParagraphs.empty())
	{
		return;
	}
	
	// draw remaining lines
	//[FIX  FIRE-2822 SJ] Start a little bit higher with the Console not to let it blend with the stand button when Avatar is sitting
	//F32 y_pos = 0.f;
	F32 y_pos = 10.f;

	// <FS> Different draw options
	//LLUIImagePtr imagep = LLUI::getUIImage("transparent");

	//static LLCachedControl<F32> console_bg_opacity(*LLUI::getInstance()->mSettingGroups["config"], "ConsoleBackgroundOpacity", 0.7f);
	//F32 console_opacity = llclamp(console_bg_opacity(), 0.f, 1.f);

	//LLColor4 color = LLUIColorTable::instance().getColor("ConsoleBackground");
	//color.mV[VALPHA] *= console_opacity;

	//F32 line_height = mFont->getLineHeight();

	//for(paragraph_it = mParagraphs.rbegin(); paragraph_it != mParagraphs.rend(); paragraph_it++)
	//{
	//	S32 target_height = llfloor( (*paragraph_it).mLines.size() * line_height + padding_vertical);
	//	S32 target_width =  llfloor( (*paragraph_it).mMaxWidth + padding_horizontal);

	//	y_pos += ((*paragraph_it).mLines.size()) * line_height;
	//	imagep->drawSolid(-14, (S32)(y_pos + line_height - target_height), target_width, target_height, color);

	//	F32 y_off=0;

	//	F32 alpha;

	//	if ((mLinePersistTime > 0.f) && ((*paragraph_it).mAddTime < fade_time))
	//	{
	//		alpha = ((*paragraph_it).mAddTime - skip_time)/(mLinePersistTime - mFadeTime);
	//	}
	//	else
	//	{
	//		alpha = 1.0f;
	//	}

	//	if( alpha > 0.f )
	//	{
	//		for (lines_t::iterator line_it=(*paragraph_it).mLines.begin(); 
	//				line_it != (*paragraph_it).mLines.end();
	//				line_it ++)
	//		{
	//			for (line_color_segments_t::iterator seg_it = (*line_it).mLineColorSegments.begin();
	//					seg_it != (*line_it).mLineColorSegments.end();
	//					seg_it++)
	//			{
	//				mFont->render((*seg_it).mText, 0, (*seg_it).mXPosition - 8, y_pos -  y_off,
	//					LLColor4(
	//						(*seg_it).mColor.mV[VRED], 
	//						(*seg_it).mColor.mV[VGREEN], 
	//						(*seg_it).mColor.mV[VBLUE], 
	//						(*seg_it).mColor.mV[VALPHA]*alpha),
	//					LLFontGL::LEFT, 
	//					LLFontGL::BASELINE,
	//					LLFontGL::NORMAL,
	//					LLFontGL::DROP_SHADOW,
	//					S32_MAX,
	//					target_width
	//					);
	//			}
	//			y_off += line_height;
	//		}
	//	}
	//	y_pos  += padding_vertical;
	//}

	paragraph_t::reverse_iterator paragraph_it;
	static LLCachedControl<F32> consoleBackgroundOpacity(*LLUI::getInstance()->mSettingGroups["config"], "ConsoleBackgroundOpacity");
	static LLUIColor cbcolor = LLUIColorTable::instance().getColor("ConsoleBackground");
	LLColor4 color = cbcolor.get();
	color.mV[VALPHA] *= llclamp(consoleBackgroundOpacity(), 0.f, 1.f);

	F32 line_height = mFont->getLineHeight();
	static LLCachedControl<bool> classic_draw_mode(*LLUI::getInstance()->mSettingGroups["config"], "FSConsoleClassicDrawMode");

	if (classic_draw_mode)
	{
		static const F32 padding_vert = 5;
		S32 total_width = 0;
		S32 total_height = 0;
		S32 lines_drawn = 0;

		paragraph_t::reverse_iterator paragraphs_end = mParagraphs.rend();
		for (paragraph_it = mParagraphs.rbegin(); paragraph_it != paragraphs_end; ++paragraph_it)
		{
			if (mSessionSupport)
			{
				// Skip paragraph if visible in floater and make sure we don't
				// exceed the maximum number of lines we want to display
				if (mCurrentSessions.find((*paragraph_it).mSessionID) != mCurrentSessions.end())
				{
					continue;
				}
				lines_drawn += (*paragraph_it).mLines.size();
				if (lines_drawn > mMaxLines)
				{
					break;
				}
			}

			total_height += llfloor( (*paragraph_it).mLines.size() * line_height + padding_vert);
			total_width = llmax(total_width, llfloor( (*paragraph_it).mMaxWidth + padding_horizontal));
		}
		mBackgroundImage->drawSolid(-14, (S32)(y_pos + line_height / 2), total_width, total_height + (line_height - padding_vert) / 2, color);

		lines_drawn = 0;
		for (paragraph_it = mParagraphs.rbegin(); paragraph_it != paragraphs_end; ++paragraph_it)
		{
			if (mSessionSupport)
			{
				// Skip paragraph if visible in floater and make sure we don't
				// exceed the maximum number of lines we want to display
				if (mCurrentSessions.find((*paragraph_it).mSessionID) != mCurrentSessions.end())
				{
					continue;
				}
				lines_drawn += (*paragraph_it).mLines.size();
				if (lines_drawn > mMaxLines)
				{
					break;
				}
			}

			F32 y_off=0;
			F32 alpha;
			S32 target_width =  llfloor( (*paragraph_it).mMaxWidth + padding_horizontal);
			y_pos += ((*paragraph_it).mLines.size()) * line_height;

			if ((mLinePersistTime > 0.f) && ((*paragraph_it).mAddTime < fade_time))
			{
				alpha = ((*paragraph_it).mAddTime - skip_time)/(mLinePersistTime - mFadeTime);
			}
			else
			{
				alpha = 1.0f;
			}

			if( alpha > 0.f )
			{
				LLConsole::lines_t::iterator lines_end_it = (*paragraph_it).mLines.end();
				for (lines_t::iterator line_it=(*paragraph_it).mLines.begin(); 
						line_it != lines_end_it;
						++line_it)
				{
					line_color_segments_t::iterator line_color_segement_end_it = (*line_it).mLineColorSegments.end();
					for (line_color_segments_t::iterator seg_it = (*line_it).mLineColorSegments.begin();
							seg_it != line_color_segement_end_it;
							++seg_it)
					{
						mFont->render((*seg_it).mText, 0, (*seg_it).mXPosition - 8, y_pos -  y_off,
							LLColor4(
								(*seg_it).mColor.mV[VRED], 
								(*seg_it).mColor.mV[VGREEN], 
								(*seg_it).mColor.mV[VBLUE], 
								(*seg_it).mColor.mV[VALPHA]*alpha),
							LLFontGL::LEFT, 
							LLFontGL::BASELINE,
							(*line_it).mStyleFlags,
							LLFontGL::DROP_SHADOW,
							S32_MAX,
							target_width
							);
					}
					y_off += line_height;
				}
			}
			y_pos  += padding_vert;
		}
	}
	else
	{
		S32 lines_drawn = 0;

		paragraph_t::reverse_iterator paragraphs_end = mParagraphs.rend();
		for(paragraph_it = mParagraphs.rbegin(); paragraph_it != paragraphs_end; paragraph_it++)
		{
			if (mSessionSupport)
			{
				// Skip paragraph if visible in floater and make sure we don't
				// exceed the maximum number of lines we want to display
				if (mCurrentSessions.find((*paragraph_it).mSessionID) != mCurrentSessions.end())
				{
					continue;
				}
				lines_drawn += (*paragraph_it).mLines.size();
				if (lines_drawn > mMaxLines)
				{
					break;
				}
			}

			S32 target_height = llfloor( (*paragraph_it).mLines.size() * line_height + padding_vertical);
			S32 target_width =  llfloor( (*paragraph_it).mMaxWidth + padding_horizontal);

			y_pos += ((*paragraph_it).mLines.size()) * line_height;
			mBackgroundImage->drawSolid(-14, (S32)(y_pos + line_height - target_height), target_width, target_height, color);

			F32 y_off=0;

			F32 alpha;

			if ((mLinePersistTime > 0.f) && ((*paragraph_it).mAddTime < fade_time))
			{
				alpha = ((*paragraph_it).mAddTime - skip_time)/(mLinePersistTime - mFadeTime);
			}
			else
			{
				alpha = 1.0f;
			}

			if( alpha > 0.f )
			{
				LLConsole::lines_t::iterator lines_end_it = (*paragraph_it).mLines.end();
				for (lines_t::iterator line_it=(*paragraph_it).mLines.begin(); 
						line_it != lines_end_it;
						++line_it)
				{
					line_color_segments_t::iterator line_color_segement_end_it = (*line_it).mLineColorSegments.end();
					for (line_color_segments_t::iterator seg_it = (*line_it).mLineColorSegments.begin();
							seg_it != line_color_segement_end_it;
							++seg_it)
					{
						mFont->render((*seg_it).mText, 0, (*seg_it).mXPosition - 8, y_pos -  y_off,
							LLColor4(
								(*seg_it).mColor.mV[VRED], 
								(*seg_it).mColor.mV[VGREEN], 
								(*seg_it).mColor.mV[VBLUE], 
								(*seg_it).mColor.mV[VALPHA]*alpha),
							LLFontGL::LEFT, 
							LLFontGL::BASELINE,
							(*line_it).mStyleFlags,
							LLFontGL::DROP_SHADOW,
							S32_MAX,
							target_width
							);
					}
					y_off += line_height;
				}
			}
			y_pos  += padding_vertical;
		}
	}
	// </FS>
}

// <FS:Ansariel> Chat console
void LLConsole::addConsoleLine(const std::string& utf8line, const LLColor4 &color, const LLUUID& session_id, LLFontGL::StyleFlags styleflags)
{
	LLWString wline = utf8str_to_wstring(utf8line);
	addConsoleLine(wline, color, session_id, styleflags);
}

void LLConsole::addConsoleLine(const LLWString& wline, const LLColor4 &color, const LLUUID& session_id, LLFontGL::StyleFlags styleflags)
{
	if (wline.empty())
	{
		return;
	}

	if (!mSessionSupport)
	{
		removeExtraLines();
	}

	mMutex.lock();
	mLines.push_back(wline);
	mLineLengths.push_back((S32)wline.length());
	mAddTimes.push_back(mTimer.getElapsedTimeF32());
	mLineColors.push_back(color);
	mLineStyle.push_back(styleflags);
	mSessionIDs.push_back(session_id);
	mMutex.unlock();
}

void LLConsole::clear()
{
	LL_INFOS() << "Clearing Console..." << LL_ENDL;
	mMutex.lock();
	mLines.clear();
	mAddTimes.clear();
	mLineLengths.clear();
	mLineColors.clear();
	mLineStyle.clear();
	mSessionIDs.clear();
	mMutex.unlock();

	mTimer.reset();
}

void LLConsole::removeExtraLines()
{
	mMutex.lock();
	while ((S32)mLines.size() > llmax((S32)0, (S32)(mMaxLines - 1)))
	{
		mLines.pop_front();
		mAddTimes.pop_front();
		mLineLengths.pop_front();
		if (!mLineColors.empty())
		{
			mLineColors.pop_front();
		}
		if (!mLineStyle.empty())
		{
			mLineStyle.pop_front();
		}
	}
	mMutex.unlock();
}
// </FS:Ansariel> Chat console

//Generate highlight color segments for this paragraph.  Pass in default color of paragraph.
void LLConsole::Paragraph::makeParagraphColorSegments (const LLColor4 &color) 
{
	LLSD paragraph_color_segments;
	paragraph_color_segments[0]["text"] =wstring_to_utf8str(mParagraphText);
	LLSD color_sd = color.getValue();
	paragraph_color_segments[0]["color"]=color_sd;

	for(LLSD::array_const_iterator color_segment_it = paragraph_color_segments.beginArray();
		color_segment_it != paragraph_color_segments.endArray();
		++color_segment_it)
	{			
		LLSD color_llsd = (*color_segment_it)["color"];
		std::string color_str  = (*color_segment_it)["text"].asString();

		ParagraphColorSegment color_segment;
		
		color_segment.mColor.setValue(color_llsd);
		color_segment.mNumChars = color_str.length();
		
		mParagraphColorSegments.push_back(color_segment);
	}
}

//Called when a paragraph is added to the console or window is resized.
// <FS:Ansariel> Added styleflags parameter for style customization
//void LLConsole::Paragraph::updateLines(F32 screen_width, const LLFontGL* font, bool force_resize)
void LLConsole::Paragraph::updateLines(F32 screen_width, const LLFontGL* font, LLFontGL::StyleFlags styleflags, bool force_resize)
// </FS:Ansariel>
{
	if ( !force_resize )
	{
		if ( mMaxWidth >= 0.0f 
		 &&  mMaxWidth < screen_width )
		{
			return;					//No resize required.
		}
	}
	
	screen_width = screen_width - 30;	//Margin for small windows.
	
	if (	mParagraphText.empty() 
		|| mParagraphColorSegments.empty()
		|| font == NULL)
	{
		return;					//Not enough info to complete.
	}
	
	mLines.clear();				//Chuck everything.
	mMaxWidth = 0.0f;
	
	paragraph_color_segments_t::iterator current_color = mParagraphColorSegments.begin();
	U32 current_color_length = (*current_color).mNumChars;	
	
	S32 paragraph_offset = 0;			//Offset into the paragraph text.

	// Wrap lines that are longer than the view is wide.
	while( paragraph_offset < (S32)mParagraphText.length() &&
		   mParagraphText[paragraph_offset] != 0)
	{
		// <FS> FIRE-8257: Sometimes text is cut off on left side of console
		//S32 skip_chars; // skip '\n'
		// Figure out if a word-wrapped line fits here.
		LLWString::size_type line_end = mParagraphText.find_first_of(llwchar('\n'), paragraph_offset);
		// <FS> FIRE-8257: Sometimes text is cut off on left side of console
		//if (line_end != LLWString::npos)
		//{
		//	skip_chars = 1; // skip '\n'
		//}
		//else
		//{
		//	line_end = mParagraphText.size();
		//	skip_chars = 0;
		//}

		//U32 drawable = font->maxDrawableChars(mParagraphText.c_str()+paragraph_offset, screen_width, line_end - paragraph_offset, LLFontGL::WORD_BOUNDARY_IF_POSSIBLE);
		if (line_end == LLWString::npos)
		{
			line_end = mParagraphText.size();
		}

		S32 skip_chars = 0; // skip '\n'
		U32 line_length = line_end - paragraph_offset;
		U32 drawable = font->maxDrawableChars(mParagraphText.c_str()+paragraph_offset, screen_width, line_length, LLFontGL::WORD_BOUNDARY_IF_POSSIBLE);
		// </FS>

		if (drawable != 0)
		{
			// <FS> FIRE-8257: Sometimes text is cut off on left side of console
			if (drawable >= line_length)
			{
				skip_chars = 1;
			}
			// </FS>
			F32 x_position = 0;						//Screen X position of text.
			
			mMaxWidth = llmax( mMaxWidth, (F32)font->getWidth( mParagraphText.substr( paragraph_offset, drawable ).c_str() ) );
			Line line;
			line.mStyleFlags = styleflags; // <FS:Ansariel> Add styleflags to every new line
			
			U32 left_to_draw = drawable;
			U32 drawn = 0;
			
			while (left_to_draw >= current_color_length 
				&& current_color != mParagraphColorSegments.end() )
			{
				LLWString color_text = mParagraphText.substr( paragraph_offset + drawn, current_color_length );
				line.mLineColorSegments.push_back( LineColorSegment( color_text,			//Append segment to line.
												(*current_color).mColor, 
												x_position ) );
												
				x_position += font->getWidth( color_text.c_str() );	//Set up next screen position.
				
				drawn += current_color_length;
				left_to_draw -= current_color_length;
				
				current_color++;							//Goto next paragraph color record.
				
				if (current_color != mParagraphColorSegments.end())
				{
					current_color_length = (*current_color).mNumChars;
				}
			}
			
			if (left_to_draw > 0 && current_color != mParagraphColorSegments.end() )
			{
					LLWString color_text = mParagraphText.substr( paragraph_offset + drawn, left_to_draw );
					
					line.mLineColorSegments.push_back( LineColorSegment( color_text,		//Append segment to line.
													(*current_color).mColor, 
													x_position ) );
																		
					current_color_length -= left_to_draw;
			}
			mLines.push_back(line);								//Append line to paragraph line list.
		}
		// <FS> FIRE-8257: Sometimes text is cut off on left side of console
		else
		{
			mLines.push_back(Line());
			skip_chars = 1;
		}
		// </FS>
		paragraph_offset += (drawable + skip_chars);
	}
}

//Pass in the string and the default color for this block of text.
// <FS:Ansariel> Added styleflags parameter for style customization and session support
//LLConsole::Paragraph::Paragraph (LLWString str, const LLColor4 &color, F32 add_time, const LLFontGL* font, F32 screen_width) 
//:	mParagraphText(str), mAddTime(add_time), mMaxWidth(-1)
LLConsole::Paragraph::Paragraph (LLWString str, const LLColor4 &color, F32 add_time, const LLFontGL* font, F32 screen_width, LLFontGL::StyleFlags styleflags, const LLUUID& session_id, bool parse_urls, LLConsole* console) 
:	mParagraphText(str), mAddTime(add_time), mMaxWidth(-1), mSessionID(session_id)
// </FS:Ansariel>
{
	// <FS:Ansariel> Parse SLURLs
	mSourceText = str;
	mID.generate();

	if (parse_urls)
	{
		LLUrlMatch urlMatch;
		LLWString workLine = str;

		while (LLUrlRegistry::instance().findUrl(workLine, urlMatch, boost::bind(&LLConsole::onUrlLabelCallback, console, mID, _1, _2)) && !urlMatch.getUrl().empty())
		{
			// Special case for LLUrlEntryHTTP, LLUrlEntryHTTPNoProtocol and
			// LLUrlEntrySecondlifeURL: getLabel() only returns host part, but
			// we also want the query part
			std::string label = urlMatch.getLabel();
			std::string query = urlMatch.getQuery();
			if (!query.empty())
			{
				label += query;
			}

			// This works for LLUrlEntryHTTPLabel and LLUrlEntrySLLabel because
			// there is no callback involved for which we are storing the
			// matched Url
			LLWStringUtil::replaceString(mParagraphText, utf8str_to_wstring(urlMatch.getMatchedText()), utf8str_to_wstring(label));
			mUrlLabels[urlMatch.getUrl()] = label;

			// Remove the URL from the work line so we don't end in a loop in case of regular URLs!
			// findUrl will always return the very first URL in a string
			workLine = workLine.erase(0, urlMatch.getEnd() + 1);
		}
	}
	// </FS:Ansariel>

	makeParagraphColorSegments(color);
	// <FS:Ansariel> Added styleflags parameter for style customization
	//updateLines( screen_width, font );
	updateLines( screen_width, font, styleflags );
	// </FS:Ansariel>
}

// <FS:Ansariel> Parse SLURLs
void LLConsole::onUrlLabelCallback(const LLUUID& paragraph_id, const std::string& url, const std::string& label)
{
	for (paragraph_t::reverse_iterator it = mParagraphs.rbegin(); it != mParagraphs.rend(); ++it)
	{
		LLConsole::Paragraph& paragraph = *it;

		if (paragraph.mID == paragraph_id)
		{
			paragraph.mUrlLabels[url] = label;

			LLWString newText = paragraph.mSourceText;
			for (std::map<std::string, std::string>::iterator url_it = paragraph.mUrlLabels.begin(); url_it != paragraph.mUrlLabels.end(); ++url_it)
			{
				LLWStringUtil::replaceString(newText, utf8string_to_wstring(url_it->first), utf8string_to_wstring(url_it->second));
			}
			paragraph.mParagraphText = newText;

			paragraph.makeParagraphColorSegments(paragraph.mLines.front().mLineColorSegments.front().mColor);
			paragraph.updateLines((F32)getRect().getWidth(), mFont, paragraph.mLines.front().mStyleFlags, true);

			break;
		}
	}
}
// </FS:Ansariel>
	
// called once per frame regardless of console visibility
// static
void LLConsole::updateClass()
{	
	for (auto& con : instance_snapshot())
	{
		con.update();
	} 
}

static LLTrace::BlockTimerStatHandle FTM_CONSOLE_UPDATE_PARAGRAPHS("Update Console Paragraphs");
void LLConsole::update()
{
	{
		LLMutexLock lock(&mMutex);

		while (!mLines.empty())
		{
			// <FS:Ansariel> Chat console
			//mParagraphs.push_back(
			//	Paragraph(	mLines.front(), 
			//				LLColor4::white, 
			//				mTimer.getElapsedTimeF32(), 
			//				mFont, 
			//				(F32)getRect().getWidth()));
			//mLines.pop_front();

			mParagraphs.push_back(
				Paragraph(	mLines.front(), 
							(!mLineColors.empty() ? mLineColors.front() : LLColor4::white),
							mTimer.getElapsedTimeF32(), 
							mFont, 
							(F32)getRect().getWidth(),
							(!mLineStyle.empty() ? mLineStyle.front() : LLFontGL::NORMAL),
							(!mSessionIDs.empty() ? mSessionIDs.front(): LLUUID::null),
							mParseUrls,
							this));
			mLines.pop_front();
			if (!mLineColors.empty())
				mLineColors.pop_front();
			if (!mLineStyle.empty())
				mLineStyle.pop_front();
			if (!mSessionIDs.empty())
				mSessionIDs.pop_front();
			// </FS:Ansariel>
		}
	}

	// remove old paragraphs which can't possibly be visible any more.  ::draw() will do something similar but more conservative - we do this here because ::draw() isn't guaranteed to ever be called!  (i.e. the console isn't visible)
	// <FS:Ansariel> Session support
	if (!mSessionSupport)
	{
	// </FS:Ansariel>
	while ((S32)mParagraphs.size() > llmax((S32)0, (S32)(mMaxLines)))
	{
			mParagraphs.pop_front();
	}
	// <FS:Ansariel> Session support
	}
	else
	{
		LL_RECORD_BLOCK_TIME(FTM_CONSOLE_UPDATE_PARAGRAPHS);

		// skip lines added more than mLinePersistTime ago
		F32 skip_time = mTimer.getElapsedTimeF32() - mLinePersistTime;

		paragraph_t temp_para;
		std::map<LLUUID, S32> session_map;
		for (paragraph_t::reverse_iterator it = mParagraphs.rbegin(); it != mParagraphs.rend(); ++it)
		{
			Paragraph& para = *it;
			session_map[para.mSessionID] += (S32)para.mLines.size();
			if (session_map[para.mSessionID] <= mMaxLines && // max lines on a per session basis
				!((mLinePersistTime > 0.f) && (para.mAddTime - skip_time) / (mLinePersistTime - mFadeTime) <= 0.f)) // not expired yet
			{
				temp_para.push_front(para);
			}
		}
		mParagraphs.swap(temp_para);
	}
	// </FS:Ansariel>
}

// <FS:Ansariel> Session support
void LLConsole::addSession(const LLUUID& session_id)
{
	mCurrentSessions.insert(session_id);
}

void LLConsole::removeSession(const LLUUID& session_id)
{
	mCurrentSessions.erase(session_id);
}
// </FS:Ansariel>
