// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
/** 
 * @file llpanelteleporthistory.cpp
 * @brief Teleport history represented by a scrolling list
 *
 * $LicenseInfo:firstyear=2009&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 "llfloaterreg.h"
#include "llmenubutton.h"

#include "llfloaterworldmap.h"
#include "llpanelteleporthistory.h"
#include "llworldmap.h"
#include "llteleporthistorystorage.h"
#include "lltextutil.h"

#include "llaccordionctrl.h"
#include "llaccordionctrltab.h"
#include "llflatlistview.h"
#include "llfloatersidepanelcontainer.h"
#include "llnotificationsutil.h"
#include "lltextbox.h"
#include "lltoggleablemenu.h"
#include "llviewermenu.h"
#include "lllandmarkactions.h"
#include "llclipboard.h"
#include "lltrans.h"

// Maximum number of items that can be added to a list in one pass.
// Used to limit time spent for items list update per frame.
static const U32 ADD_LIMIT = 50;

static const std::string COLLAPSED_BY_USER = "collapsed_by_user";

class LLTeleportHistoryFlatItem : public LLPanel
{
public:
	LLTeleportHistoryFlatItem(S32 index, LLTeleportHistoryPanel::ContextMenu *context_menu, const std::string &region_name,
										 	 LLDate date, const std::string &hl);
	virtual ~LLTeleportHistoryFlatItem();

	BOOL postBuild() override;

	/*virtual*/ S32 notify(const LLSD& info) override;

	S32 getIndex() { return mIndex; }
	void setIndex(S32 index) { mIndex = index; }
	const std::string& getRegionName() { return mRegionName;}
	void setRegionName(const std::string& name);
	void setDate(LLDate date);
	void setHighlightedText(const std::string& text);
	void updateTitle();
	void updateTimestamp();
	std::string getTimestamp();

	/*virtual*/ void setValue(const LLSD& value) override;

	void onMouseEnter(S32 x, S32 y, MASK mask) override;
	void onMouseLeave(S32 x, S32 y, MASK mask) override;
	BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override;

	static void showPlaceInfoPanel(S32 index);

	LLHandle<LLTeleportHistoryFlatItem> getItemHandle()	{ mItemHandle.bind(this); return mItemHandle; }

private:
	void onProfileBtnClick();

	LLButton* mProfileBtn;
	LLTextBox* mTitle;
	LLTextBox* mTimeTextBox;
	
	LLTeleportHistoryPanel::ContextMenu *mContextMenu;

	S32 mIndex;
	std::string mRegionName;
	std::string mHighlight;
	LLDate 		mDate;
	LLRootHandle<LLTeleportHistoryFlatItem> mItemHandle;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class LLTeleportHistoryFlatItemStorage: public LLSingleton<LLTeleportHistoryFlatItemStorage>
{
	LLSINGLETON_EMPTY_CTOR(LLTeleportHistoryFlatItemStorage);
protected:
	typedef std::vector< LLHandle<LLTeleportHistoryFlatItem> > flat_item_list_t;

public:
	LLTeleportHistoryFlatItem* getFlatItemForPersistentItem (
		LLTeleportHistoryPanel::ContextMenu *context_menu,
		const LLTeleportHistoryPersistentItem& persistent_item,
		const S32 cur_item_index,
		const std::string &hl);

	void removeItem(LLTeleportHistoryFlatItem* item);

	void purge();

private:

	flat_item_list_t mItems;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

LLTeleportHistoryFlatItem::LLTeleportHistoryFlatItem(S32 index, LLTeleportHistoryPanel::ContextMenu *context_menu, const std::string &region_name,
																LLDate date, const std::string &hl)
:	LLPanel(),
	mContextMenu(context_menu),
	mIndex(index),
	mRegionName(region_name),
	mHighlight(hl),
	mDate(date)
{
	buildFromFile( "panel_teleport_history_item.xml");
}

LLTeleportHistoryFlatItem::~LLTeleportHistoryFlatItem()
{
}

//virtual
BOOL LLTeleportHistoryFlatItem::postBuild()
{
	mTitle = getChild<LLTextBox>("region");

	mTimeTextBox = getChild<LLTextBox>("timestamp");

	mProfileBtn = getChild<LLButton>("profile_btn");
        
	mProfileBtn->setClickedCallback(boost::bind(&LLTeleportHistoryFlatItem::onProfileBtnClick, this));

	updateTitle();
	updateTimestamp();

	return true;
}

S32 LLTeleportHistoryFlatItem::notify(const LLSD& info)
{
	if(info.has("detach"))
	{
		delete mMouseDownSignal;
		mMouseDownSignal = nullptr;
		delete mRightMouseDownSignal;
		mRightMouseDownSignal = nullptr;
		return 1;
	}
	return 0;
}

void LLTeleportHistoryFlatItem::setValue(const LLSD& value)
{
	if (!value.isMap()) return;;
	if (!value.has("selected")) return;
	getChildView("selected_icon")->setVisible( value["selected"]);
}

void LLTeleportHistoryFlatItem::setHighlightedText(const std::string& text)
{
	mHighlight = text;
}

void LLTeleportHistoryFlatItem::setRegionName(const std::string& name)
{
	mRegionName = name;
}

void LLTeleportHistoryFlatItem::setDate(LLDate date)
{
	mDate = date;
}

std::string LLTeleportHistoryFlatItem::getTimestamp()
{
	const LLDate &date = mDate;
	std::string timestamp = "";

	LLDate now = LLDate::now();
	S32 now_year, now_month, now_day, now_hour, now_min, now_sec;
	now.split(&now_year, &now_month, &now_day, &now_hour, &now_min, &now_sec);

	const S32 seconds_in_day = 24 * 60 * 60;
	S32 seconds_today = now_hour * 60 * 60 + now_min * 60 + now_sec;
	S32 time_diff = (S32) now.secondsSinceEpoch() - (S32) date.secondsSinceEpoch();

	// Only show timestamp for today and yesterday
	if(time_diff < seconds_today + seconds_in_day)
	{
		timestamp = "[" + LLTrans::getString("TimeHour12")+"]:["
						+ LLTrans::getString("TimeMin")+"] ["+ LLTrans::getString("TimeAMPM")+"]";
		LLSD substitution;
		substitution["datetime"] = (S32) date.secondsSinceEpoch();
		LLStringUtil::format(timestamp, substitution);
	}

	return timestamp;

}

void LLTeleportHistoryFlatItem::updateTitle()
{
	static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", LLColor4U(255, 255, 255));

	LLTextUtil::textboxSetHighlightedVal(
		mTitle,
		LLStyle::Params().color(sFgColor),
		mRegionName,
		mHighlight);
}

void LLTeleportHistoryFlatItem::updateTimestamp()
{
	static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", LLColor4U(255, 255, 255));

	LLTextUtil::textboxSetHighlightedVal(
		mTimeTextBox,
		LLStyle::Params().color(sFgColor),
		getTimestamp(),
		mHighlight);
}

void LLTeleportHistoryFlatItem::onMouseEnter(S32 x, S32 y, MASK mask)
{
	getChildView("hovered_icon")->setVisible( true);
	mProfileBtn->setVisible(true);

	LLPanel::onMouseEnter(x, y, mask);
}

void LLTeleportHistoryFlatItem::onMouseLeave(S32 x, S32 y, MASK mask)
{
	getChildView("hovered_icon")->setVisible( false);
	mProfileBtn->setVisible(false);

	LLPanel::onMouseLeave(x, y, mask);
}

// virtual
BOOL LLTeleportHistoryFlatItem::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
	if (mContextMenu)
		mContextMenu->show(this, mIndex, x, y);

	return LLPanel::handleRightMouseDown(x, y, mask);
}

void LLTeleportHistoryFlatItem::showPlaceInfoPanel(S32 index)
{
	LLSD params;
	params["id"] = index;
	params["type"] = "teleport_history";

	LLFloaterSidePanelContainer::showPanel("places", params);
}

void LLTeleportHistoryFlatItem::onProfileBtnClick()
{
	LLTeleportHistoryFlatItem::showPlaceInfoPanel(mIndex);
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

LLTeleportHistoryFlatItem*
LLTeleportHistoryFlatItemStorage::getFlatItemForPersistentItem (
	LLTeleportHistoryPanel::ContextMenu *context_menu,
	const LLTeleportHistoryPersistentItem& persistent_item,
	const S32 cur_item_index,
	const std::string &hl)
{
	LLTeleportHistoryFlatItem* item = nullptr;
	if ( cur_item_index < (S32) mItems.size() )
	{
		item = mItems[cur_item_index].get();
		if (item->getParent() == nullptr)
		{
			item->setIndex(cur_item_index);
			item->setRegionName(persistent_item.mTitle);
			item->setDate(persistent_item.mDate);
			item->setHighlightedText(hl);
			item->setVisible(TRUE);
			item->updateTitle();
			item->updateTimestamp();
		}
		else
		{
			// Item already added to parent
			item = nullptr;
		}
	}

	if ( !item )
	{
		item = new LLTeleportHistoryFlatItem(cur_item_index,
											 context_menu,
											 persistent_item.mTitle,
											 persistent_item.mDate,
											 hl);
		mItems.push_back(item->getItemHandle());
	}

	return item;
}

void LLTeleportHistoryFlatItemStorage::removeItem(LLTeleportHistoryFlatItem* item)
{
	if (item)
	{
		flat_item_list_t::iterator item_iter = std::find(mItems.begin(),
														 mItems.end(),
														 item->getItemHandle());
		if (item_iter != mItems.end())
		{
			mItems.erase(item_iter);
		}
	}
}

void LLTeleportHistoryFlatItemStorage::purge()
{
	for ( flat_item_list_t::iterator
			  it = mItems.begin(),
			  it_end = mItems.end();
		  it != it_end; ++it )
	{
		LLHandle <LLTeleportHistoryFlatItem> item_handle = *it;
		if ( !item_handle.isDead() && item_handle.get()->getParent() == nullptr )
		{
			item_handle.get()->die();
		}
	}
	mItems.clear();
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

LLTeleportHistoryPanel::ContextMenu::ContextMenu() :
	mMenuHandle(), mIndex(0)
{
}

LLTeleportHistoryPanel::ContextMenu::~ContextMenu()
{
	if (auto menu = mMenuHandle.get())
	{
		menu->die();
		mMenuHandle.markDead();
	}
}

void LLTeleportHistoryPanel::ContextMenu::show(LLView* spawning_view, S32 index, S32 x, S32 y)
{
	if (auto menu = mMenuHandle.get())
	{
		menu->die();
		mMenuHandle.markDead();
	}

	mIndex = index;
	mMenuHandle = createMenu();

	auto menu = mMenuHandle.get();
	{
		menu->show(x, y);
		LLMenuGL::showPopup(spawning_view, menu, x, y);
	}
}

LLHandle<LLContextMenu> LLTeleportHistoryPanel::ContextMenu::createMenu()
{
	// set up the callbacks for all of the avatar menu items
	// (N.B. callbacks don't take const refs as mID is local scope)
	LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;

	registrar.add("TeleportHistory.Teleport",	boost::bind(&LLTeleportHistoryPanel::ContextMenu::onTeleport, this));
	registrar.add("TeleportHistory.MoreInformation",boost::bind(&LLTeleportHistoryPanel::ContextMenu::onInfo, this));
	registrar.add("TeleportHistory.CopyToClipboard",boost::bind(&LLTeleportHistoryPanel::ContextMenu::onCopyToClipboard, this));

	// create the context menu from the XUI
	llassert(LLMenuGL::sMenuContainer != NULL);
	return LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(
		"menu_teleport_history_item.xml", LLMenuGL::sMenuContainer, LLViewerMenuHolderGL::child_registry_t::instance())->getHandle();
}

void LLTeleportHistoryPanel::ContextMenu::onTeleport()
{
	confirmTeleport(mIndex);
}

void LLTeleportHistoryPanel::ContextMenu::onInfo()
{
	LLTeleportHistoryFlatItem::showPlaceInfoPanel(mIndex);
}

//static
void LLTeleportHistoryPanel::ContextMenu::gotSLURLCallback(const std::string& slurl)
{
	LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl),0,slurl.size());

	LLSD args;
	args["SLURL"] = slurl;

	LLNotificationsUtil::add("CopySLURL", args);
}

void LLTeleportHistoryPanel::ContextMenu::onCopyToClipboard()
{
	LLVector3d globalPos = LLTeleportHistoryStorage::getInstance()->getItems()[mIndex].mGlobalPos;
	LLLandmarkActions::getSLURLfromPosGlobal(globalPos,
		boost::bind(&LLTeleportHistoryPanel::ContextMenu::gotSLURLCallback, _1));
}

// Not yet implemented; need to remove buildPanel() from constructor when we switch
//static LLRegisterPanelClassWrapper<LLTeleportHistoryPanel> t_teleport_history("panel_teleport_history");

LLTeleportHistoryPanel::LLTeleportHistoryPanel()
	:	LLPanelPlacesTab(),
		mTeleportHistory(nullptr),
		mHistoryAccordion(nullptr),
		mLastSelectedFlatlList(nullptr),
		mLastSelectedItemIndex(-1),
		mDirty(true),
		mCurrentItem(0),
		mAccordionTabMenuHandle(),
		mMenuGearButton(nullptr)
{
	buildFromFile( "panel_teleport_history.xml");
}

LLTeleportHistoryPanel::~LLTeleportHistoryPanel()
{
	LLTeleportHistoryFlatItemStorage::instance().purge();
	mTeleportHistoryChangedConnection.disconnect();
	auto menu = mAccordionTabMenuHandle.get();
	if (menu)
	{
		menu->die();
		mAccordionTabMenuHandle.markDead();
	}
}

BOOL LLTeleportHistoryPanel::postBuild()
{
	mTeleportHistory = LLTeleportHistoryStorage::getInstance();
	if (mTeleportHistory)
	{
		mTeleportHistoryChangedConnection = mTeleportHistory->setHistoryChangedCallback(boost::bind(&LLTeleportHistoryPanel::onTeleportHistoryChange, this, _1));
	}

	mHistoryAccordion = getChild<LLAccordionCtrl>("history_accordion");

	if (mHistoryAccordion)
	{
		for (child_list_const_iter_t iter = mHistoryAccordion->beginChild(); iter != mHistoryAccordion->endChild(); iter++)
		{
			if (dynamic_cast<LLAccordionCtrlTab*>(*iter))
			{
				LLAccordionCtrlTab* tab = (LLAccordionCtrlTab*)*iter;
				tab->setRightMouseDownCallback(boost::bind(&LLTeleportHistoryPanel::onAccordionTabRightClick, this, _1, _2, _3, _4));
				tab->setDisplayChildren(false);
				tab->setDropDownStateChangedCallback(boost::bind(&LLTeleportHistoryPanel::onAccordionExpand, this, _1, _2));

				// All accordion tabs are collapsed initially
				setAccordionCollapsedByUser(tab, true);

				mItemContainers.push_back(tab);

				LLFlatListView* fl = getFlatListViewFromTab(tab);
				if (fl)
				{
					fl->setCommitOnSelectionChange(true);
					fl->setDoubleClickCallback(boost::bind(&LLTeleportHistoryPanel::onDoubleClickItem, this));
					fl->setCommitCallback(boost::bind(&LLTeleportHistoryPanel::handleItemSelect, this, fl));
					fl->setReturnCallback(boost::bind(&LLTeleportHistoryPanel::onReturnKeyPressed, this));
				}
			}
		}

		// Open first 2 accordion tabs
		if (mItemContainers.size() > 1)
		{
			LLAccordionCtrlTab* tab = mItemContainers.at(mItemContainers.size() - 1);
			tab->setDisplayChildren(true);
			setAccordionCollapsedByUser(tab, false);
		}

		if (mItemContainers.size() > 2)
		{
			LLAccordionCtrlTab* tab = mItemContainers.at(mItemContainers.size() - 2);
			tab->setDisplayChildren(true);
			setAccordionCollapsedByUser(tab, false);
		}
	}

	LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;

	registrar.add("TeleportHistory.ExpandAllFolders",  boost::bind(&LLTeleportHistoryPanel::onExpandAllFolders,  this));
	registrar.add("TeleportHistory.CollapseAllFolders",  boost::bind(&LLTeleportHistoryPanel::onCollapseAllFolders,  this));
	registrar.add("TeleportHistory.ClearTeleportHistory",  boost::bind(&LLTeleportHistoryPanel::onClearTeleportHistory,  this));
	mEnableCallbackRegistrar.add("TeleportHistory.GearMenu.Enable", boost::bind(&LLTeleportHistoryPanel::isActionEnabled, this, _2));

	mMenuGearButton = getChild<LLMenuButton>("gear_btn");
	mMenuGearButton->setMenu("menu_teleport_history_gear.xml");

	return TRUE;
}

// virtual
void LLTeleportHistoryPanel::draw()
{
	if (mDirty)
		refresh();

	LLPanelPlacesTab::draw();
}

// virtual
void LLTeleportHistoryPanel::onSearchEdit(const std::string& string)
{
	sFilterSubString = string;
	showTeleportHistory();
}

// virtual
bool LLTeleportHistoryPanel::isSingleItemSelected()
{
	return mLastSelectedFlatlList && mLastSelectedFlatlList->getSelectedItem();
}

// virtual
void LLTeleportHistoryPanel::onShowOnMap()
{
	if (!mLastSelectedFlatlList)
		return;

	LLTeleportHistoryFlatItem* itemp = dynamic_cast<LLTeleportHistoryFlatItem *> (mLastSelectedFlatlList->getSelectedItem());

	if(!itemp)
		return;

	LLVector3d global_pos = mTeleportHistory->getItems()[itemp->getIndex()].mGlobalPos;

	if (!global_pos.isExactlyZero())
	{
		LLFloaterWorldMap::getInstance()->trackLocation(global_pos);
		LLFloaterReg::showInstance("world_map", "center");
	}
}

//virtual
void LLTeleportHistoryPanel::onShowProfile()
{
	if (!mLastSelectedFlatlList)
		return;

	LLTeleportHistoryFlatItem* itemp = dynamic_cast<LLTeleportHistoryFlatItem *> (mLastSelectedFlatlList->getSelectedItem());

	if(!itemp)
		return;

	LLTeleportHistoryFlatItem::showPlaceInfoPanel(itemp->getIndex());
}

// virtual
void LLTeleportHistoryPanel::onTeleport()
{
	if (!mLastSelectedFlatlList)
		return;

	LLTeleportHistoryFlatItem* itemp = dynamic_cast<LLTeleportHistoryFlatItem *> (mLastSelectedFlatlList->getSelectedItem());
	if(!itemp)
		return;

	// teleport to existing item in history, so we don't add it again
	confirmTeleport(itemp->getIndex());
}

/*
// virtual
void LLTeleportHistoryPanel::onCopySLURL()
{
	LLScrollListItem* itemp = mHistoryItems->getFirstSelected();
	if(!itemp)
		return;

	S32 index = itemp->getColumn(LIST_INDEX)->getValue().asInteger();

	const LLTeleportHistory::slurl_list_t& hist_items = mTeleportHistory->getItems();

	LLVector3d global_pos = hist_items[index].mGlobalPos;

	U64 new_region_handle = to_region_handle(global_pos);

	LLWorldMapMessage::url_callback_t cb = boost::bind(
			&LLPanelPlacesTab::onRegionResponse, this,
			global_pos, _1, _2, _3, _4);

	LLWorldMap::getInstance()->sendHandleRegionRequest(new_region_handle, cb, std::string("unused"), false);
}
*/

// virtual
void LLTeleportHistoryPanel::updateVerbs()
{
	if (!isTabVisible())
		return;

	if (!mLastSelectedFlatlList)
	{
		mTeleportBtn->setEnabled(false);
		mShowProfile->setEnabled(false);
		mShowOnMapBtn->setEnabled(false);
		return;
	}

	LLTeleportHistoryFlatItem* itemp = dynamic_cast<LLTeleportHistoryFlatItem *> (mLastSelectedFlatlList->getSelectedItem());

	mTeleportBtn->setEnabled(nullptr != itemp);
	mShowProfile->setEnabled(nullptr != itemp);
	mShowOnMapBtn->setEnabled(nullptr != itemp);
}

void LLTeleportHistoryPanel::getNextTab(const LLDate& item_date, S32& tab_idx, LLDate& tab_date)
{
	const U32 seconds_in_day = 24 * 60 * 60;

	S32 tabs_cnt = mItemContainers.size();
	S32 curr_year = 0, curr_month = 0, curr_day = 0;

	tab_date = LLDate::now();
	tab_date.split(&curr_year, &curr_month, &curr_day);
	tab_date.fromYMDHMS(curr_year, curr_month, curr_day); // Set hour, min, and sec to 0
	tab_date.secondsSinceEpoch(tab_date.secondsSinceEpoch() + seconds_in_day);

	tab_idx = -1;

	while (tab_idx < tabs_cnt - 1 && item_date < tab_date)
	{
		tab_idx++;

		if (tab_idx <= tabs_cnt - 4)
		{
			// All tabs, except last three, are tabs for one day, so just push tab_date back by one day
			tab_date.secondsSinceEpoch(tab_date.secondsSinceEpoch() - seconds_in_day);
		}
		else if (tab_idx == tabs_cnt - 3) // 6 day and older, low boundary is 1 month
		{
			tab_date =  LLDate::now();
			tab_date.split(&curr_year, &curr_month, &curr_day);
			curr_month--;
			if (0 == curr_month)
			{
				curr_month = 12;
				curr_year--;
			}
			tab_date.fromYMDHMS(curr_year, curr_month, curr_day);
		}
		else if (tab_idx == tabs_cnt - 2) // 1 month and older, low boundary is 6 months
		{
			tab_date =  LLDate::now();
			tab_date.split(&curr_year, &curr_month, &curr_day);
			if (curr_month > 6)
			{
				curr_month -= 6;
			}
			else
			{
				curr_month += 6;
				curr_year--;
			}
			tab_date.fromYMDHMS(curr_year, curr_month, curr_day);
		}
		else // 6 months and older
		{
			tab_date.secondsSinceEpoch(0);
		}
	}
}

// Called to add items, no more, than ADD_LIMIT at time
void LLTeleportHistoryPanel::refresh()
{
	if (!mHistoryAccordion)
	{
		mDirty = false;
		return;
	}

	const LLTeleportHistoryStorage::slurl_list_t& items = mTeleportHistory->getItems();

	// Setting tab_boundary_date to "now", so date from any item would be earlier, than boundary.
	// That leads to call to getNextTab to get right tab_idx in first pass
	LLDate tab_boundary_date =  LLDate::now();

	LLFlatListView* curr_flat_view = nullptr;
	std::string filter_string = sFilterSubString;
	LLStringUtil::toUpper(filter_string);

	U32 added_items = 0;
	while (mCurrentItem >= 0)
	{
		// Filtering
		if (!filter_string.empty())
		{
			std::string landmark_title(items[mCurrentItem].mTitle);
			LLStringUtil::toUpper(landmark_title);
			if( std::string::npos == landmark_title.find(filter_string) )
			{
				mCurrentItem--;
				continue;
			}
		}

		// Checking whether date of item is earlier, than tab_boundary_date.
		// In that case, item should be added to another tab
		const LLDate &date = items[mCurrentItem].mDate;

		if (date < tab_boundary_date)
		{
			// Getting apropriate tab_idx for this and subsequent items,
			// tab_boundary_date would be earliest possible date for this tab
			S32 tab_idx = 0;
			getNextTab(date, tab_idx, tab_boundary_date);
			tab_idx = mItemContainers.size() - 1 - tab_idx;
			if (tab_idx >= 0)
			{
				LLAccordionCtrlTab* tab = mItemContainers.at(tab_idx);
				tab->setVisible(true);

				// Expand all accordion tabs when filtering
				if(!sFilterSubString.empty())
				{
					//store accordion tab state when filter is not empty
					tab->notifyChildren(LLSD().with("action","store_state"));
				
					tab->setDisplayChildren(true);
				}
				// Restore each tab's expand state when not filtering
				else
				{
					bool collapsed = isAccordionCollapsedByUser(tab);
					tab->setDisplayChildren(!collapsed);
			
					//restore accordion state after all those accodrion tabmanipulations
					tab->notifyChildren(LLSD().with("action","restore_state"));
				}

				curr_flat_view = getFlatListViewFromTab(tab);
			}
		}

		if (curr_flat_view)
		{
			LLTeleportHistoryFlatItem* item =
				LLTeleportHistoryFlatItemStorage::instance()
				.getFlatItemForPersistentItem(&mContextMenu,
											  items[mCurrentItem],
											  mCurrentItem,
											  filter_string);
			if ( !curr_flat_view->addItem(item, LLUUID::null, ADD_BOTTOM, false) )
				LL_ERRS() << "Couldn't add flat item to teleport history." << LL_ENDL;
			if (mLastSelectedItemIndex == mCurrentItem)
				curr_flat_view->selectItem(item, true);
		}

		mCurrentItem--;

		if (++added_items >= ADD_LIMIT)
			break;
	}

	for (S32 n = mItemContainers.size() - 1; n >= 0; --n)
	{
		LLAccordionCtrlTab* tab = mItemContainers.at(n);
		LLFlatListView* fv = getFlatListViewFromTab(tab);
		if (fv)
		{
			fv->notify(LLSD().with("rearrange", LLSD()));
		}
	}

	mHistoryAccordion->setFilterSubString(sFilterSubString);

	mHistoryAccordion->arrange();

	updateVerbs();

	if (mCurrentItem < 0)
		mDirty = false;
}

void LLTeleportHistoryPanel::onTeleportHistoryChange(S32 removed_index)
{
	mLastSelectedItemIndex = -1;

	if (-1 == removed_index)
		showTeleportHistory(); // recreate all items
	else
	{
		replaceItem(removed_index); // replace removed item by most recent
		updateVerbs();
	}
}

void LLTeleportHistoryPanel::replaceItem(S32 removed_index)
{
	// Flat list for 'Today' (mItemContainers keeps accordion tabs in reverse order)
	LLFlatListView* fv = nullptr;
	
	if (mItemContainers.size() > 0)
	{
		fv = getFlatListViewFromTab(mItemContainers[mItemContainers.size() - 1]);
	}

	// Empty flat list for 'Today' means that other flat lists are empty as well,
	// so all items from teleport history should be added.
	if (!fv || fv->size() == 0)
	{
		showTeleportHistory();
		return;
	}

	const LLTeleportHistoryStorage::slurl_list_t& history_items = mTeleportHistory->getItems();
	LLTeleportHistoryFlatItem* item = LLTeleportHistoryFlatItemStorage::instance()
		.getFlatItemForPersistentItem(&mContextMenu,
									  history_items[history_items.size() - 1], // Most recent item, it was added instead of removed
									  history_items.size(), // index will be decremented inside loop below
									  sFilterSubString);

	fv->addItem(item, LLUUID::null, ADD_TOP);

	// Index of each item, from last to removed item should be decremented
	// to point to the right item in LLTeleportHistoryStorage
	for (S32 tab_idx = mItemContainers.size() - 1; tab_idx >= 0; --tab_idx)
	{
		LLAccordionCtrlTab* tab = mItemContainers.at(tab_idx);
		if (!tab->getVisible())
			continue;

		fv = getFlatListViewFromTab(tab);
		if (!fv)
		{
			showTeleportHistory();
			return;
		}

		std::vector<LLPanel*> items;
		fv->getItems(items);

		S32 items_cnt = items.size();
		for (S32 n = 0; n < items_cnt; ++n)
		{
			LLTeleportHistoryFlatItem *item = (LLTeleportHistoryFlatItem*) items[n];

			if (item->getIndex() == removed_index)
			{
				LLTeleportHistoryFlatItemStorage::instance().removeItem(item);

				fv->removeItem(item);

				// If flat list becames empty, then accordion tab should be hidden
				if (fv->size() == 0)
					tab->setVisible(false);

				mHistoryAccordion->arrange();

				return; // No need to decrement idexes for the rest of items
			}

			item->setIndex(item->getIndex() - 1);
		}
	}
}

void LLTeleportHistoryPanel::showTeleportHistory()
{
	mDirty = true;

	// Starting to add items from last one, in reverse order,
	// since TeleportHistory keeps most recent item at the end
	if (!mTeleportHistory)
	{
		mTeleportHistory = LLTeleportHistoryStorage::getInstance();
	}

	mCurrentItem = mTeleportHistory->getItems().size() - 1;

	for (S32 n = mItemContainers.size() - 1; n >= 0; --n)
	{
		LLAccordionCtrlTab* tab = mItemContainers.at(n);
		if (tab)
		{
			tab->setVisible(false);

			LLFlatListView* fv = getFlatListViewFromTab(tab);
			if (fv)
			{
				// Detached panels are managed by LLTeleportHistoryFlatItemStorage
				std::vector<LLPanel*> detached_items;
				fv->detachItems(detached_items);
			}
		}
	}
}

void LLTeleportHistoryPanel::handleItemSelect(LLFlatListView* selected)
{
	mLastSelectedFlatlList = selected;
	LLTeleportHistoryFlatItem* item = dynamic_cast<LLTeleportHistoryFlatItem *> (mLastSelectedFlatlList->getSelectedItem());
	if (item)
		mLastSelectedItemIndex = item->getIndex();

	S32 tabs_cnt = mItemContainers.size();

	for (S32 n = 0; n < tabs_cnt; n++)
	{
		LLAccordionCtrlTab* tab = mItemContainers.at(n);

		if (!tab->getVisible())
			continue;

		LLFlatListView *flv = getFlatListViewFromTab(tab);
		if (!flv)
			continue;

		if (flv == selected)
			continue;

		flv->resetSelection(true);
	}

	updateVerbs();
}

void LLTeleportHistoryPanel::onReturnKeyPressed()
{
	// Teleport to selected region as default action on return key pressed
	onTeleport();
}

void LLTeleportHistoryPanel::onDoubleClickItem()
{
	// If item got doubleclick, then that item is already selected
	onTeleport();
}

void LLTeleportHistoryPanel::onAccordionTabRightClick(LLView *view, S32 x, S32 y, MASK mask)
{
	LLAccordionCtrlTab *tab = (LLAccordionCtrlTab *) view;

	// If click occurred below the header, don't show this menu
	if (y < tab->getRect().getHeight() - tab->getHeaderHeight() - tab->getPaddingBottom())
		return;

	if (!mAccordionTabMenuHandle.isDead())
	{
		auto menu = mAccordionTabMenuHandle.get();
		if (menu)
		{
			menu->die();
			mAccordionTabMenuHandle.markDead();
		}
	}

	// set up the callbacks for all of the avatar menu items
	// (N.B. callbacks don't take const refs as mID is local scope)
	LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;

	registrar.add("TeleportHistory.TabOpen",	boost::bind(&LLTeleportHistoryPanel::onAccordionTabOpen, this, tab));
	registrar.add("TeleportHistory.TabClose",	boost::bind(&LLTeleportHistoryPanel::onAccordionTabClose, this, tab));

	// create the context menu from the XUI
	llassert(LLMenuGL::sMenuContainer != NULL);
	auto menu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(
		"menu_teleport_history_tab.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
	if (menu)
	{
		mAccordionTabMenuHandle = menu->getHandle();
		menu->setItemVisible("TabOpen", !tab->isExpanded() ? true : false);
		menu->setItemVisible("TabClose", tab->isExpanded() ? true : false);

		menu->show(x, y);
		LLMenuGL::showPopup(tab, menu, x, y);
	}
}

void LLTeleportHistoryPanel::onAccordionTabOpen(LLAccordionCtrlTab *tab)
{
	tab->setDisplayChildren(true);
	mHistoryAccordion->arrange();
}

void LLTeleportHistoryPanel::onAccordionTabClose(LLAccordionCtrlTab *tab)
{
	tab->setDisplayChildren(false);
	mHistoryAccordion->arrange();
}

void LLTeleportHistoryPanel::onExpandAllFolders()
{
	S32 tabs_cnt = mItemContainers.size();

	for (S32 n = 0; n < tabs_cnt; n++)
	{
		mItemContainers.at(n)->setDisplayChildren(true);
	}
	mHistoryAccordion->arrange();
}

void LLTeleportHistoryPanel::onCollapseAllFolders()
{
	S32 tabs_cnt = mItemContainers.size();

	for (S32 n = 0; n < tabs_cnt; n++)
	{
		mItemContainers.at(n)->setDisplayChildren(false);
	}
	mHistoryAccordion->arrange();

	if (mLastSelectedFlatlList)
	{
		mLastSelectedFlatlList->resetSelection();
	}
}

void LLTeleportHistoryPanel::onClearTeleportHistory()
{
	LLNotificationsUtil::add("ConfirmClearTeleportHistory", LLSD(), LLSD(), boost::bind(&LLTeleportHistoryPanel::onClearTeleportHistoryDialog, this, _1, _2));
}

bool LLTeleportHistoryPanel::onClearTeleportHistoryDialog(const LLSD& notification, const LLSD& response)
{

	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);

	if (0 == option)
	{
		// order does matter, call this first or teleport history will contain one record(current location)
		LLTeleportHistory::getInstance()->purgeItems();

		LLTeleportHistoryStorage *th = LLTeleportHistoryStorage::getInstance();
		th->purgeItems();
		th->save();
	}

	return false;
}

LLFlatListView* LLTeleportHistoryPanel::getFlatListViewFromTab(LLAccordionCtrlTab *tab)
{
	for (child_list_const_iter_t iter = tab->beginChild(); iter != tab->endChild(); iter++)
	{
		if (dynamic_cast<LLFlatListView*>(*iter))
		{
			return (LLFlatListView*)*iter; // There should be one scroll list per tab.
		}
	}

	return nullptr;
}

bool LLTeleportHistoryPanel::isActionEnabled(const LLSD& userdata) const
{
	S32 tabs_cnt = mItemContainers.size();

	bool has_expanded_tabs = false;
	bool has_collapsed_tabs = false;

	for (S32 n = 0; n < tabs_cnt; n++)
	{
		LLAccordionCtrlTab* tab = mItemContainers.at(n);
		if (!tab->getVisible())
			continue;

		if (tab->getDisplayChildren())
		{
			has_expanded_tabs = true;
		}
		else
		{
			has_collapsed_tabs = true;
		}

		if (has_expanded_tabs && has_collapsed_tabs)
		{
			break;
		}
	}

	std::string command_name = userdata.asString();

	if (has_expanded_tabs && command_name == "collapse_all")
	{
		return true;
	}

	if (has_collapsed_tabs && command_name ==  "expand_all")
	{
		return true;
	}

	return false;
}

void LLTeleportHistoryPanel::setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed)
{
	LLSD param = acc_tab->getValue();
	param[COLLAPSED_BY_USER] = collapsed;
	acc_tab->setValue(param);
}

bool LLTeleportHistoryPanel::isAccordionCollapsedByUser(LLUICtrl* acc_tab)
{
	LLSD param = acc_tab->getValue();
	if(!param.has(COLLAPSED_BY_USER))
	{
		return false;
	}
	return param[COLLAPSED_BY_USER].asBoolean();
}

void LLTeleportHistoryPanel::onAccordionExpand(LLUICtrl* ctrl, const LLSD& param)
{
	bool expanded = param.asBoolean();
	// Save accordion tab state to restore it in refresh()
	setAccordionCollapsedByUser(ctrl, !expanded);

	// Reset selection upon accordion being collapsed
	// to disable "Teleport" and "Map" buttons for hidden item.
	if (!expanded && mLastSelectedFlatlList)
	{
		mLastSelectedFlatlList->resetSelection();
	}
}

// static
void LLTeleportHistoryPanel::confirmTeleport(S32 hist_idx)
{
	LLSD args;
	args["HISTORY_ENTRY"] = LLTeleportHistoryStorage::getInstance()->getItems()[hist_idx].mTitle;
	LLNotificationsUtil::add("TeleportToHistoryEntry", args, LLSD(),
		boost::bind(&LLTeleportHistoryPanel::onTeleportConfirmation, _1, _2, hist_idx));
}

// Called when user reacts upon teleport confirmation dialog.
// static
bool LLTeleportHistoryPanel::onTeleportConfirmation(const LLSD& notification, const LLSD& response, S32 hist_idx)
{
	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);

	if (0 == option)
	{
		// Teleport to given history item.
		LLTeleportHistoryStorage::getInstance()->goToItem(hist_idx);
	}

	return false;
}
