/** 
 * @file llchatitemscontainer.cpp
 * @brief chat history scrolling panel implementation
 *
 * $LicenseInfo:firstyear=2009&license=viewergpl$
 * 
 * Copyright (c) 2009, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llchatitemscontainerctrl.h"
#include "lltextbox.h"

#include "llchatmsgbox.h"
#include "llavatariconctrl.h"
#include "llfloaterreg.h"
#include "lltrans.h"

#include "llviewercontrol.h"
#include "llagentdata.h"

static const S32 BORDER_MARGIN = 2;
static const S32 PARENT_BORDER_MARGIN = 0;

static const S32 HORIZONTAL_MULTIPLE = 8;
static const S32 VERTICAL_MULTIPLE = 16;
static const F32 MIN_AUTO_SCROLL_RATE = 120.f;
static const F32 MAX_AUTO_SCROLL_RATE = 500.f;
static const F32 AUTO_SCROLL_RATE_ACCEL = 120.f;

static const S32 msg_left_offset = 30;
static const S32 msg_right_offset = 10;

#define MAX_CHAT_HISTORY 100


static LLDefaultChildRegistry::Register<LLChatItemsContainerCtrl>	t2("chat_items_container");



//*******************************************************************************************************************
//LLChatItemCtrl
//*******************************************************************************************************************

LLChatItemCtrl* LLChatItemCtrl::createInstance()
{
	LLChatItemCtrl* item = new LLChatItemCtrl();
	LLUICtrlFactory::getInstance()->buildPanel(item, "panel_chat_item.xml");
	return item;
}

void	LLChatItemCtrl::draw()
{
	LLPanel::draw();
}

void	LLChatItemCtrl::reshape		(S32 width, S32 height, BOOL called_from_parent )
{
	LLPanel::reshape(width, height,called_from_parent);

	LLPanel* caption = getChild<LLPanel>("msg_caption",false,false);
	LLChatMsgBox* msg_text = getChild<LLChatMsgBox>("msg_text",false,false);
	if(caption && msg_text)
	{
		LLRect caption_rect = caption->getRect();
		caption_rect.setLeftTopAndSize( 2, height, width - 4, caption_rect.getHeight());
		caption->reshape( width - 4, caption_rect.getHeight(), 1);
		caption->setRect(caption_rect);

		
		LLRect msg_text_rect = msg_text->getRect();
		msg_text_rect.setLeftTopAndSize( msg_left_offset, height - caption_rect.getHeight() , width - msg_left_offset - msg_right_offset, height - caption_rect.getHeight());
		msg_text->reshape( width - msg_left_offset - msg_right_offset, height - caption_rect.getHeight(), 1);
		msg_text->setRect(msg_text_rect);
	}
		
}

BOOL LLChatItemCtrl::postBuild()
{
	return LLPanel::postBuild();
}


std::string LLChatItemCtrl::appendTime()
{
	time_t utc_time;
	utc_time = time_corrected();
	std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:["
		+LLTrans::getString("TimeMin")+"] ";

	LLSD substitution;

	substitution["datetime"] = (S32) utc_time;
	LLStringUtil::format (timeStr, substitution);

	return timeStr;
}



void	LLChatItemCtrl::addText		(const std::string& message)
{
	LLChatMsgBox* msg_text = getChild<LLChatMsgBox>("msg_text",false,false);
	if(msg_text)
		msg_text->addText(message);
	mMessages.push_back(message);
}

void	LLChatItemCtrl::setMessage	(const LLChat& msg)
{
	LLPanel* caption = getChild<LLPanel>("msg_caption",false,false);
	if(!caption)
		return;

	std::string str_sender;

	
	if(gAgentID != msg.mFromID)
		str_sender = msg.mFromName;
	else
		str_sender = LLTrans::getString("You");;

	caption->getChild<LLTextBox>("sender_name",false,false)->setText(str_sender);
	
	std::string tt = appendTime();
	
	caption->getChild<LLTextBox>("msg_time",false,false)->setText(tt);


	caption->getChild<LLAvatarIconCtrl>("avatar_icon",false,false)->setValue(msg.mFromID);

	mOriginalMessage = msg;

	LLChatMsgBox* msg_text = getChild<LLChatMsgBox>("msg_text",false,false);
	if(msg_text)
		msg_text->setText(msg.mText);

	LLUICtrl* msg_inspector = caption->getChild<LLUICtrl>("msg_inspector");
	if(mOriginalMessage.mSourceType != CHAT_SOURCE_AGENT)
		msg_inspector->setVisible(false);

	mMessages.clear();

}

void	LLChatItemCtrl::snapToMessageHeight	()
{
	LLChatMsgBox* text_box = getChild<LLChatMsgBox>("msg_text",false,false);
	if(!text_box)
		return;///actually assert fits better
	S32 new_height = text_box->getTextPixelHeight();
	LLRect panel_rect = getRect();

	S32 caption_height = 0;
	LLPanel* caption = getChild<LLPanel>("msg_caption",false,false);
	if(caption)
		caption_height = caption->getRect().getHeight();


	panel_rect.setLeftTopAndSize( panel_rect.mLeft, panel_rect.mTop, panel_rect.getWidth()	, caption_height + new_height);
	
	reshape( getRect().getWidth(), caption_height + new_height, 1);
	
	setRect(panel_rect);

}


void	LLChatItemCtrl::setWidth(S32 width)
{
	LLChatMsgBox* text_box = getChild<LLChatMsgBox>("msg_text",false,false);
	if(!text_box)
		return;///actually assert fits better

	text_box->reshape(width - msg_left_offset - msg_right_offset,100/*its not magic number, we just need any number*/);

	LLChatMsgBox* msg_text = getChild<LLChatMsgBox>("msg_text",false,false);
	if(msg_text && mOriginalMessage.mText.length())
		msg_text->setText(mOriginalMessage.mText);
	
	for(size_t i=0;i<mMessages.size();++i)
		msg_text->addText(mMessages[i]);

	setRect(LLRect(getRect().mLeft, getRect().mTop, getRect().mLeft + width	, getRect().mBottom));
	snapToMessageHeight	();
}

void LLChatItemCtrl::onMouseLeave			(S32 x, S32 y, MASK mask)
{
	LLPanel* caption = getChild<LLPanel>("msg_caption",false,false);
	if(!caption)
		return;
	LLUICtrl* msg_inspector = caption->getChild<LLUICtrl>("msg_inspector");
	if(msg_inspector)
		msg_inspector->setVisible(false);
	
}
void LLChatItemCtrl::onMouseEnter				(S32 x, S32 y, MASK mask)
{
	if(mOriginalMessage.mSourceType != CHAT_SOURCE_AGENT)
		return;
	LLPanel* caption = getChild<LLPanel>("msg_caption",false,false);
	if(!caption)
		return;
	LLUICtrl* msg_inspector = caption->getChild<LLUICtrl>("msg_inspector");
	if(msg_inspector)
		msg_inspector->setVisible(true);
}

BOOL	LLChatItemCtrl::handleMouseDown	(S32 x, S32 y, MASK mask)
{
	if(mOriginalMessage.mSourceType != CHAT_SOURCE_AGENT)
		return LLPanel::handleMouseDown(x,y,mask);
	LLPanel* caption = getChild<LLPanel>("msg_caption",false,false);
	if(caption)
	{
		LLUICtrl* msg_inspector = caption->getChild<LLUICtrl>("msg_inspector");
		if(msg_inspector)
		{
			S32 local_x = x - msg_inspector->getRect().mLeft - caption->getRect().mLeft;
			S32 local_y = y - msg_inspector->getRect().mBottom - caption->getRect().mBottom;
			if(msg_inspector->pointInView(local_x, local_y))
			{
				LLFloaterReg::showInstance("mini_inspector", mOriginalMessage.mFromID);
			}
		}
	}
	return LLPanel::handleMouseDown(x,y,mask);
}

void	LLChatItemCtrl::setHeaderVisibility(EShowItemHeader e)
{
	LLPanel* caption = getChild<LLPanel>("msg_caption",false,false);
	if(!caption)
		return;

	LLUICtrl* icon = caption->getChild<LLUICtrl>("avatar_icon",false,false);
	LLUICtrl* name = caption->getChild<LLUICtrl>("sender_name",false,false);

	if(icon == 0 || name == 0)
		return;

	icon->setVisible(e == CHATITEMHEADER_SHOW_ONLY_ICON || e==CHATITEMHEADER_SHOW_BOTH);
	name->setVisible(e == CHATITEMHEADER_SHOW_ONLY_NAME || e==CHATITEMHEADER_SHOW_BOTH);

}

bool	LLChatItemCtrl::canAddText	()
{
	LLChatMsgBox* msg_text = getChild<LLChatMsgBox>("msg_text",false,false);
	if(!msg_text )
		return false;
	return msg_text->getTextLinesNum()<10;
}

BOOL	LLChatItemCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
	LLPanel* caption = getChild<LLPanel>("msg_caption",false,false);
	if(!caption)
		return LLPanel::handleRightMouseDown(x,y,mask);
	LLUICtrl* avatar_icon = caption->getChild<LLUICtrl>("avatar_icon",false,false);
	if(!avatar_icon)
		return LLPanel::handleRightMouseDown(x,y,mask);
	S32 local_x = x - avatar_icon->getRect().mLeft - caption->getRect().mLeft;
	S32 local_y = y - avatar_icon->getRect().mBottom - caption->getRect().mBottom;

	//eat message for avatar icon if msg was from object
	if(avatar_icon->pointInView(local_x, local_y) && mOriginalMessage.mSourceType != CHAT_SOURCE_AGENT)
		return TRUE;
	return LLPanel::handleRightMouseDown(x,y,mask);
}


//*******************************************************************************************************************
//LLChatItemsContainerCtrl
//*******************************************************************************************************************

LLChatItemsContainerCtrl::LLChatItemsContainerCtrl(const Params& params):LLPanel(params)
{
	mEShowItemHeader = CHATITEMHEADER_SHOW_BOTH;
}


void	LLChatItemsContainerCtrl::addMessage(const LLChat& msg)
{
	/*
	if(msg.mChatType == CHAT_TYPE_DEBUG_MSG)
		return;
	*/
	if(mItems.size() >= MAX_CHAT_HISTORY)
	{
		LLChatItemCtrl* item = mItems[0];
		removeChild(item);
		delete item;
		mItems.erase(mItems.begin());
	}

	
	if(mItems.size() > 0 
		&& msg.mFromID == mItems[mItems.size()-1]->getMessage().mFromID 
		&& (msg.mTime-mItems[mItems.size()-1]->getMessage().mTime)<60
		&& mItems[mItems.size()-1]->canAddText()
		)
	{
		mItems[mItems.size()-1]->addText(msg.mText);
		mItems[mItems.size()-1]->snapToMessageHeight();
	}
	else
	{
		LLChatItemCtrl* item = LLChatItemCtrl::createInstance();
		mItems.push_back(item);
		addChild(item,0);
		item->setWidth(getRect().getWidth() - 16);
		item->setMessage(msg);
		item->snapToMessageHeight();
		
		item->setHeaderVisibility((EShowItemHeader)gSavedSettings.getS32("nearbychat_showicons_and_names"));

		item->setVisible(true);
	}

	arrange(getRect().getWidth(),getRect().getHeight());
	updateLayout(getRect().getWidth(),getRect().getHeight());
	scrollToBottom();
}

void	LLChatItemsContainerCtrl::scrollToBottom	()
{
	if(mScrollbar->getVisible())
	{
		mScrollbar->setDocPos(mScrollbar->getDocPosMax());
		onScrollPosChangeCallback(0,0);
	}
}

void	LLChatItemsContainerCtrl::draw()
{
	LLLocalClipRect clip(getRect());
	LLPanel::draw();
}

void	LLChatItemsContainerCtrl::reshape					(S32 width, S32 height, BOOL called_from_parent )
{
	S32 delta_width = width - getRect().getWidth();
	S32 delta_height = height - getRect().getHeight();

	if (delta_width || delta_height || sForceReshape)
	{
		arrange(width, height);
	}

	updateBoundingRect();
}

void	LLChatItemsContainerCtrl::arrange					(S32 width, S32 height)
{
	S32 delta_width = width - getRect().getWidth();
	if(delta_width)//width changed...too bad. now we need to reformat all items
		reformatHistoryScrollItems(width);

	calcRecuiredHeight();

	show_hide_scrollbar(width,height);

	updateLayout(width,height);
}

void	LLChatItemsContainerCtrl::reformatHistoryScrollItems(S32 width)
{
	for(std::vector<LLChatItemCtrl*>::iterator it = mItems.begin(); it != mItems.end();++it)
	{
		(*it)->setWidth(width);
	}
}

S32		LLChatItemsContainerCtrl::calcRecuiredHeight	()
{
	S32 rec_height = 0;
	
	std::vector<LLChatItemCtrl*>::iterator it;
	for(it=mItems.begin(); it!=mItems.end(); ++it)
	{
		rec_height += (*it)->getRect().getHeight();
	}

	mInnerRect.setLeftTopAndSize(0,rec_height + BORDER_MARGIN*2,getRect().getWidth(),rec_height + BORDER_MARGIN);

	return mInnerRect.getHeight();
}


void	LLChatItemsContainerCtrl::updateLayout				(S32 width, S32 height)
{
	S32 panel_top = height - BORDER_MARGIN ;
	S32 panel_width = width;
	if(mScrollbar->getVisible())
	{
		static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
		
		panel_top+=mScrollbar->getDocPos();
		panel_width-=scrollbar_size;
	}


	//set sizes for first panels and dragbars
	for(size_t i=0;i<mItems.size();++i)
	{
		LLRect panel_rect = mItems[i]->getRect();
		panelSetLeftTopAndSize(mItems[i],panel_rect.mLeft,panel_top,panel_width,panel_rect.getHeight());
		panel_top-=panel_rect.getHeight();
	}
}

void	LLChatItemsContainerCtrl::show_hide_scrollbar		(S32 width, S32 height)
{
	calcRecuiredHeight();
	if(getRecuiredHeight() > height )
		showScrollbar(width, height);
	else
		hideScrollbar(width, height);
}

void	LLChatItemsContainerCtrl::showScrollbar			(S32 width, S32 height)
{
	bool was_visible = mScrollbar->getVisible();

	mScrollbar->setVisible(true);

	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
	
	panelSetLeftTopAndSize(mScrollbar,width-scrollbar_size
		,height-PARENT_BORDER_MARGIN,scrollbar_size,height-2*PARENT_BORDER_MARGIN);
	
	mScrollbar->setPageSize(height);
	mScrollbar->setDocParams(mInnerRect.getHeight(),mScrollbar->getDocPos());

	if(was_visible)
	{
		S32 scroll_pos = llmin(mScrollbar->getDocPos(), getRecuiredHeight() - height - 1);
		mScrollbar->setDocPos(scroll_pos);
		updateLayout(width,height);
		return;
	}
}

void	LLChatItemsContainerCtrl::hideScrollbar			(S32 width, S32 height)
{
	if(mScrollbar->getVisible() == false)
		return;
	mScrollbar->setVisible(false);

	mScrollbar->setDocPos(0);

	if(mItems.size()>0)
	{
		S32 panel_top = height - BORDER_MARGIN;		  // Top coordinate of the first panel
		S32 diff = panel_top - mItems[0]->getRect().mTop;
		shiftPanels(diff);
	}
}

//---------------------------------------------------------------------------------
void LLChatItemsContainerCtrl::panelSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S32 width, S32 height)
{
	if(!panel)
		return;
	LLRect panel_rect = panel->getRect();
	panel_rect.setLeftTopAndSize( left, top, width, height);
	panel->reshape( width, height, 1);
	panel->setRect(panel_rect);
}

void LLChatItemsContainerCtrl::panelShiftVertical(LLView* panel,S32 delta)
{
	if(!panel)
		return;
	panel->translate(0,delta);
}

void LLChatItemsContainerCtrl::shiftPanels(S32 delta)
{
	//Arrange panels
	for(std::vector<LLChatItemCtrl*>::iterator it = mItems.begin(); it != mItems.end();++it)
	{
		panelShiftVertical((*it),delta);
	}	

}

//---------------------------------------------------------------------------------

void	LLChatItemsContainerCtrl::onScrollPosChangeCallback(S32, LLScrollbar*)
{
	updateLayout(getRect().getWidth(),getRect().getHeight());
}

BOOL LLChatItemsContainerCtrl::postBuild()
{
	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);

	LLRect scroll_rect;
	scroll_rect.setOriginAndSize( 
		getRect().getWidth() - scrollbar_size,
		1,
		scrollbar_size,
		getRect().getHeight() - 1);
	

	LLScrollbar::Params sbparams;
	sbparams.name("scrollable vertical");
	sbparams.rect(scroll_rect);
	sbparams.orientation(LLScrollbar::VERTICAL);
	sbparams.doc_size(mInnerRect.getHeight());
	sbparams.doc_pos(0);
	sbparams.page_size(mInnerRect.getHeight());
	sbparams.step_size(VERTICAL_MULTIPLE);
	sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM);
	sbparams.change_callback(boost::bind(&LLChatItemsContainerCtrl::onScrollPosChangeCallback, this, _1, _2));
	
	mScrollbar = LLUICtrlFactory::create<LLScrollbar> (sbparams);
	LLView::addChild( mScrollbar );
	mScrollbar->setVisible( true );
	mScrollbar->setFollowsRight();
	mScrollbar->setFollowsTop();
	mScrollbar->setFollowsBottom();

	reformatHistoryScrollItems(getRect().getWidth());
	arrange(getRect().getWidth(),getRect().getHeight());

	return LLPanel::postBuild();
}
BOOL	LLChatItemsContainerCtrl::handleMouseDown	(S32 x, S32 y, MASK mask)
{
	return LLPanel::handleMouseDown(x,y,mask);
}
BOOL LLChatItemsContainerCtrl::handleKeyHere			(KEY key, MASK mask)
{
	if( mScrollbar->getVisible() && mScrollbar->handleKeyHere( key,mask ) )
		return TRUE;
	return LLPanel::handleKeyHere(key,mask);
}
BOOL LLChatItemsContainerCtrl::handleScrollWheel		( S32 x, S32 y, S32 clicks )
{
	if( mScrollbar->getVisible() && mScrollbar->handleScrollWheel( 0, 0, clicks ) )
		return TRUE;
	return false;
}

void	LLChatItemsContainerCtrl::setHeaderVisibility(EShowItemHeader e)
{
	if(e == mEShowItemHeader)
		return;
	mEShowItemHeader = e;
	for(std::vector<LLChatItemCtrl*>::iterator it = mItems.begin(); it != mItems.end();++it)
	{
		(*it)->setHeaderVisibility(e);
	}
}