/** 
 * @file llfloaterautoreplacesettings.cpp
 * @brief Auto Replace List floater
 *
 * $LicenseInfo:firstyear=2012&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; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * 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
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llfloaterautoreplacesettings.h"

#include "llagentdata.h"
#include "llcommandhandler.h"
#include "llfloater.h"
#include "lluictrlfactory.h"
#include "llagent.h"
#include "llpanel.h"
#include "llbutton.h"
#include "llcolorswatch.h"
#include "llcombobox.h"
#include "llview.h"
#include "llbufferstream.h"
#include "llcheckboxctrl.h"
#include "llviewercontrol.h"

#include "llui.h"
#include "llcontrol.h"
#include "llscrollingpanellist.h"
#include "llautoreplace.h"
#include "llfilepicker.h"
#include "llfile.h"
#include "llsdserialize.h"
#include "llsdutil.h"

#include "llchat.h"
#include "llinventorymodel.h"
#include "llhost.h"
#include "llassetstorage.h"
#include "roles_constants.h"
#include "llviewertexteditor.h"
#include <boost/tokenizer.hpp>

#include <iosfwd>
#include "llfloaterreg.h"
#include "llinspecttoast.h"
#include "llnotificationhandler.h"
#include "llnotificationmanager.h"
#include "llnotificationsutil.h"


LLFloaterAutoReplaceSettings::LLFloaterAutoReplaceSettings(const LLSD& key)
 : LLFloater(key)
 , mSelectedListName("")
 , mListNames(NULL)
 , mReplacementsList(NULL)
 , mKeyword(NULL)
 , mPreviousKeyword("")
 , mReplacement(NULL)
{
}

void LLFloaterAutoReplaceSettings::onClose(bool app_quitting)
{
	cleanUp();
}

BOOL LLFloaterAutoReplaceSettings::postBuild(void)
{
	// get copies of the current settings that we will operate on
	mEnabled  = gSavedSettings.getBOOL("AutoReplace");
	LL_DEBUGS("AutoReplace") << ( mEnabled ? "enabled" : "disabled") << LL_ENDL;

	mSettings = LLAutoReplace::getInstance()->getSettings();
	
	// global checkbox for whether or not autoreplace is active
	LLUICtrl* enabledCheckbox = getChild<LLUICtrl>("autoreplace_enable");
	enabledCheckbox->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onAutoReplaceToggled, this));
	enabledCheckbox->setValue(LLSD(mEnabled));

	// top row list creation and deletion
	getChild<LLUICtrl>("autoreplace_import_list")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onImportList,this));
	getChild<LLUICtrl>("autoreplace_export_list")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onExportList,this));
	getChild<LLUICtrl>("autoreplace_new_list")->setCommitCallback(   boost::bind(&LLFloaterAutoReplaceSettings::onNewList,this));
	getChild<LLUICtrl>("autoreplace_delete_list")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onDeleteList,this));

	// the list of keyword->replacement lists
	mListNames = getChild<LLScrollListCtrl>("autoreplace_list_name");
	mListNames->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSelectList, this));
	mListNames->setCommitOnSelectionChange(true);
	
	// list ordering
	getChild<LLUICtrl>("autoreplace_list_up")->setCommitCallback(  boost::bind(&LLFloaterAutoReplaceSettings::onListUp,this));
	getChild<LLUICtrl>("autoreplace_list_down")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onListDown,this));

	// keyword->replacement entry add / delete
	getChild<LLUICtrl>("autoreplace_add_entry")->setCommitCallback(   boost::bind(&LLFloaterAutoReplaceSettings::onAddEntry,this));
	getChild<LLUICtrl>("autoreplace_delete_entry")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onDeleteEntry,this));

	// entry edits
	mKeyword     = getChild<LLLineEditor>("autoreplace_keyword");
	mReplacement = getChild<LLLineEditor>("autoreplace_replacement");
	getChild<LLUICtrl>("autoreplace_save_entry")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSaveEntry, this));

	// dialog termination ( Save Changes / Cancel )
	getChild<LLUICtrl>("autoreplace_save_changes")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSaveChanges, this));
	getChild<LLUICtrl>("autoreplace_cancel")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onCancel, this));

	// the list of keyword->replacement pairs
	mReplacementsList = getChild<LLScrollListCtrl>("autoreplace_list_replacements");
	mReplacementsList->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSelectEntry, this));
	mReplacementsList->setCommitOnSelectionChange(true);

	center();

	mSelectedListName.clear();
	updateListNames();
	updateListNamesControls();
	updateReplacementsList();

	return true;
}


void LLFloaterAutoReplaceSettings::updateListNames()
{
	mListNames->deleteAllItems(); // start from scratch

	LLSD listNames = mSettings.getListNames(); // Array of Strings
   
	for ( LLSD::array_const_iterator entry = listNames.beginArray(), end = listNames.endArray();
		  entry != end;
		  ++entry
		 )
	{
		const std::string& listName = entry->asString();
		mListNames->addSimpleElement(listName);
	}

	if (!mSelectedListName.empty())
	{
		mListNames->setSelectedByValue( LLSD(mSelectedListName), true );
	}
}

void LLFloaterAutoReplaceSettings::updateListNamesControls()
{
	if ( mSelectedListName.empty() )
	{
		// There is no selected list

		// Disable all controls that operate on the selected list
		getChild<LLButton>("autoreplace_export_list")->setEnabled(false);
		getChild<LLButton>("autoreplace_delete_list")->setEnabled(false);
		getChild<LLButton>("autoreplace_list_up")->setEnabled(false);
		getChild<LLButton>("autoreplace_list_down")->setEnabled(false);

		mReplacementsList->deleteAllItems();
	}
	else
	{
		// Enable the controls that operate on the selected list
		getChild<LLButton>("autoreplace_export_list")->setEnabled(true);
		getChild<LLButton>("autoreplace_delete_list")->setEnabled(true);
		getChild<LLButton>("autoreplace_list_up")->setEnabled(!selectedListIsFirst());
		getChild<LLButton>("autoreplace_list_down")->setEnabled(!selectedListIsLast());
	}
}

void LLFloaterAutoReplaceSettings::onSelectList()
{
	std::string previousSelectedListName = mSelectedListName;
	// only one selection allowed
	LLSD selected = mListNames->getSelectedValue();
	if (selected.isDefined())
	{
		mSelectedListName = selected.asString();
		LL_DEBUGS("AutoReplace")<<"selected list '"<<mSelectedListName<<"'"<<LL_ENDL;
	}
	else
	{
		mSelectedListName.clear();
		LL_DEBUGS("AutoReplace")<<"unselected"<<LL_ENDL;
	}

	updateListNamesControls();

	if ( previousSelectedListName != mSelectedListName )
	{
		updateReplacementsList();
	}
}

void LLFloaterAutoReplaceSettings::onSelectEntry()
{
    LLSD selectedRow = mReplacementsList->getSelectedValue();
	if (selectedRow.isDefined())
	{
		mPreviousKeyword = selectedRow.asString();
		LL_DEBUGS("AutoReplace")<<"selected entry '"<<mPreviousKeyword<<"'"<<LL_ENDL;	
		mKeyword->setValue(selectedRow);
		std::string replacement = mSettings.replacementFor(mPreviousKeyword, mSelectedListName );
		mReplacement->setValue(replacement);
		enableReplacementEntry();		
		mReplacement->setFocus(true);
	}
	else
	{
		// no entry selection, so the entry panel should be off
		disableReplacementEntry();		
		LL_DEBUGS("AutoReplace")<<"no row selected"<<LL_ENDL;
	}
}

void LLFloaterAutoReplaceSettings::updateReplacementsList()
{
	// start from scratch, since this should only be called when the list changes
	mReplacementsList->deleteAllItems();

	if ( mSelectedListName.empty() )
	{
		mReplacementsList->setEnabled(false);
		getChild<LLButton>("autoreplace_add_entry")->setEnabled(false);
		disableReplacementEntry();		
	}
	else
	{
		// Populate the keyword->replacement list from the selected list
		const LLSD* mappings = mSettings.getListEntries(mSelectedListName);
		for ( LLSD::map_const_iterator entry = mappings->beginMap(), end = mappings->endMap();
			  entry != end;
			  entry++
			 )
		{
			LLSD row;
			row["id"] = entry->first;
			row["columns"][0]["column"] = "keyword";
			row["columns"][0]["value"]  = entry->first;
			row["columns"][1]["column"] = "replacement";
			row["columns"][1]["value"]  = entry->second;

			mReplacementsList->addElement(row, ADD_BOTTOM);
		}

		mReplacementsList->deselectAllItems(false /* don't call commit */);
		mReplacementsList->setEnabled(true);

		getChild<LLButton>("autoreplace_add_entry")->setEnabled(true);
		disableReplacementEntry();		
	}
}

void LLFloaterAutoReplaceSettings::enableReplacementEntry()
{
	LL_DEBUGS("AutoReplace")<<LL_ENDL;
	mKeyword->setEnabled(true);
	mReplacement->setEnabled(true);
	getChild<LLButton>("autoreplace_save_entry")->setEnabled(true);
	getChild<LLButton>("autoreplace_delete_entry")->setEnabled(true);
}

void LLFloaterAutoReplaceSettings::disableReplacementEntry()
{
	LL_DEBUGS("AutoReplace")<<LL_ENDL;
	mPreviousKeyword.clear();
	mKeyword->clear();
	mKeyword->setEnabled(false);
	mReplacement->clear();
	mReplacement->setEnabled(false);
	getChild<LLButton>("autoreplace_save_entry")->setEnabled(false);
	getChild<LLButton>("autoreplace_delete_entry")->setEnabled(false);
}

// called when the global settings checkbox is changed
void LLFloaterAutoReplaceSettings::onAutoReplaceToggled()
{
	// set our local copy of the flag, copied to the global preference in onOk
	mEnabled = childGetValue("autoreplace_enable").asBoolean();
	LL_DEBUGS("AutoReplace")<< "autoreplace_enable " << ( mEnabled ? "on" : "off" ) << LL_ENDL;
}

// called when the List Up button is pressed
void LLFloaterAutoReplaceSettings::onListUp()
{
	S32 selectedRow = mListNames->getFirstSelectedIndex();
	LLSD selectedName = mListNames->getSelectedValue().asString();

	if ( mSettings.increaseListPriority(selectedName) )
	{
		updateListNames();
		updateListNamesControls();
	}
	else
	{
		LL_WARNS("AutoReplace")
			<< "invalid row ("<<selectedRow<<") selected '"<<selectedName<<"'"
			<<LL_ENDL;
	}
}

// called when the List Down button is pressed
void LLFloaterAutoReplaceSettings::onListDown()
{
	S32 selectedRow = mListNames->getFirstSelectedIndex();
	std::string selectedName = mListNames->getSelectedValue().asString();

	if ( mSettings.decreaseListPriority(selectedName) )
	{
		updateListNames();
		updateListNamesControls();
	}
	else
	{
		LL_WARNS("AutoReplace")
			<< "invalid row ("<<selectedRow<<") selected '"<<selectedName<<"'"
			<<LL_ENDL;
	}
}

// called when the Delete Entry button is pressed
void LLFloaterAutoReplaceSettings::onDeleteEntry()
{
    LLSD selectedRow = mReplacementsList->getSelectedValue();
	if (selectedRow.isDefined())
	{	
		std::string keyword = selectedRow.asString();
		mReplacementsList->deleteSelectedItems(); // delete from the control
		mSettings.removeEntryFromList(keyword, mSelectedListName); // delete from the local settings copy
		disableReplacementEntry(); // no selection active, so turn off the buttons
	}
}

// called when the Import List button is pressed
void LLFloaterAutoReplaceSettings::onImportList()
{
	LLFilePicker& picker = LLFilePicker::instance();
	if( picker.getOpenFile( LLFilePicker::FFLOAD_XML) )
	{
		llifstream file;
		file.open(picker.getFirstFile().c_str());
		LLSD newList;
		if (file.is_open())
		{
			LLSDSerialize::fromXMLDocument(newList, file);
		}
		file.close();

		switch ( mSettings.addList(newList) )
		{
		case LLAutoReplaceSettings::AddListOk:
			mSelectedListName = LLAutoReplaceSettings::getListName(newList);
			
			updateListNames();
			updateListNamesControls();
			updateReplacementsList();
			break;

		case LLAutoReplaceSettings::AddListDuplicateName:
			{
				std::string newName = LLAutoReplaceSettings::getListName(newList);
				LL_WARNS("AutoReplace")<<"name '"<<newName<<"' is in use; prompting for new name"<<LL_ENDL;
				LLSD newPayload;
				newPayload["list"] = newList;
				LLSD args;
				args["DUPNAME"] = newName;
	
				LLNotificationsUtil::add("RenameAutoReplaceList", args, newPayload,
										 boost::bind(&LLFloaterAutoReplaceSettings::callbackListNameConflict, this, _1, _2));
			}
			break;

		case LLAutoReplaceSettings::AddListInvalidList:
			LLNotificationsUtil::add("InvalidAutoReplaceList");
			LL_WARNS("AutoReplace") << "imported list was invalid" << LL_ENDL;

			mSelectedListName.clear();
			updateListNames();
			updateListNamesControls();
			updateReplacementsList();
			break;

		default:
			LL_ERRS("AutoReplace") << "invalid AddListResult" << LL_ENDL;

		}
		
	}
	else
	{
		LL_DEBUGS("AutoReplace") << "file selection failed for import list" << LL_ENDL;
	}		
}

void LLFloaterAutoReplaceSettings::onNewList()
{
	LLSD payload;
	LLSD emptyList;
	LLAutoReplaceSettings::createEmptyList(emptyList);
	payload["list"] = emptyList;
    LLSD args;
	
	LLNotificationsUtil::add("AddAutoReplaceList", args, payload,
							 boost::bind(&LLFloaterAutoReplaceSettings::callbackNewListName, this, _1, _2));
}

bool LLFloaterAutoReplaceSettings::callbackNewListName(const LLSD& notification, const LLSD& response)
{
	LL_DEBUGS("AutoReplace")<<"called"<<LL_ENDL;
	
	LLSD newList = notification["payload"]["list"];

	if ( response.has("listname") && response["listname"].isString() )
	{
		std::string newName = response["listname"].asString();
		LLAutoReplaceSettings::setListName(newList, newName);

		switch ( mSettings.addList(newList) )
		{
		case LLAutoReplaceSettings::AddListOk:
			LL_INFOS("AutoReplace") << "added new list '"<<newName<<"'"<<LL_ENDL;
			mSelectedListName = newName;
			updateListNames();
			updateListNamesControls();
			updateReplacementsList();
			break;

		case LLAutoReplaceSettings::AddListDuplicateName:
			{
				LL_WARNS("AutoReplace")<<"name '"<<newName<<"' is in use; prompting for new name"<<LL_ENDL;
				LLSD newPayload;
				newPayload["list"] = notification["payload"]["list"];
				LLSD args;
				args["DUPNAME"] = newName;
	
				LLNotificationsUtil::add("RenameAutoReplaceList", args, newPayload,
										 boost::bind(&LLFloaterAutoReplaceSettings::callbackListNameConflict, this, _1, _2));
			}
			break;

		case LLAutoReplaceSettings::AddListInvalidList:
			LLNotificationsUtil::add("InvalidAutoReplaceList");

			mSelectedListName.clear();
			updateListNames();
			updateListNamesControls();
			updateReplacementsList();
			break;

		default:
			LL_ERRS("AutoReplace") << "invalid AddListResult" << LL_ENDL;
		}
	}
	else
	{
		LL_ERRS("AutoReplace") << "adding notification response" << LL_ENDL;
	}
	return false;
}

// callback for the RenameAutoReplaceList notification
bool LLFloaterAutoReplaceSettings::callbackListNameConflict(const LLSD& notification, const LLSD& response)
{
	LLSD newList = notification["payload"]["list"];
	std::string listName = LLAutoReplaceSettings::getListName(newList);
	
	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
	switch ( option )
	{
	case 0:
		// Replace current list
		if ( LLAutoReplaceSettings::AddListOk == mSettings.replaceList(newList) )
		{
			LL_INFOS("AutoReplace") << "replaced list '"<<listName<<"'"<<LL_ENDL;
			mSelectedListName = listName;
			updateListNames();
			updateListNamesControls();
			updateReplacementsList();
		}
		else
		{
			LL_WARNS("AutoReplace")<<"failed to replace list '"<<listName<<"'"<<LL_ENDL;
		}
		break;

	case 1:
		// Use New Name
		LL_INFOS("AutoReplace")<<"option 'use new name' selected"<<LL_ENDL;
		callbackNewListName(notification, response);
		break;

	default:
		LL_ERRS("AutoReplace")<<"invalid selected option "<<option<<LL_ENDL;
	}

	return false;
}

void LLFloaterAutoReplaceSettings::onDeleteList()
{
	std::string listName = mListNames->getSelectedValue().asString();
	if ( ! listName.empty() )
	{
		if ( mSettings.removeReplacementList(listName) )
		{
			LL_INFOS("AutoReplace")<<"deleted list '"<<listName<<"'"<<LL_ENDL;
			mReplacementsList->deleteSelectedItems();   // remove from the scrolling list
			mSelectedListName.clear();
			updateListNames();
			updateListNamesControls();
			updateReplacementsList();
		}
		else
		{
			LL_WARNS("AutoReplace")<<"failed to delete list '"<<listName<<"'"<<LL_ENDL;
		}
	}
	else
	{
		LL_DEBUGS("AutoReplace")<<"no list selected for delete"<<LL_ENDL;
	}
}

void LLFloaterAutoReplaceSettings::onExportList()
{
	std::string listName=mListNames->getFirstSelected()->getColumn(0)->getValue().asString();
	const LLSD* list = mSettings.exportList(listName);
	std::string listFileName = listName + ".xml";
	LLFilePicker& picker = LLFilePicker::instance();
	if( picker.getSaveFile( LLFilePicker::FFSAVE_XML, listFileName) )
	{
		llofstream file;
		file.open(picker.getFirstFile().c_str());
		LLSDSerialize::toPrettyXML(*list, file);
		file.close();
	}
}

void LLFloaterAutoReplaceSettings::onAddEntry()
{
	mPreviousKeyword.clear();
	mReplacementsList->deselectAllItems(false /* don't call commit */);
	mKeyword->clear();
	mReplacement->clear();
	enableReplacementEntry();
	mKeyword->setFocus(true);
}

void LLFloaterAutoReplaceSettings::onSaveEntry()
{
	LL_DEBUGS("AutoReplace")<<"called"<<LL_ENDL;
	
	if ( ! mPreviousKeyword.empty() )
	{
		// delete any existing value for the key that was editted
		LL_INFOS("AutoReplace")
			<< "list '" << mSelectedListName << "' "
			<< "removed '" << mPreviousKeyword
			<< "'" << LL_ENDL;
		mSettings.removeEntryFromList( mPreviousKeyword, mSelectedListName );
	}

	LLWString keyword     = mKeyword->getWText();
	LLWString replacement = mReplacement->getWText();
	if ( mSettings.addEntryToList(keyword, replacement, mSelectedListName) )
	{
		// insert the new keyword->replacement pair
		LL_INFOS("AutoReplace")
			<< "list '" << mSelectedListName << "' "
			<< "added '" << wstring_to_utf8str(keyword)
			<< "' -> '" << wstring_to_utf8str(replacement)
			<< "'" << LL_ENDL;

		updateReplacementsList();
	}
	else
	{
		LLNotificationsUtil::add("InvalidAutoReplaceEntry");
		LL_WARNS("AutoReplace")<<"invalid entry "
							   << "keyword '" << wstring_to_utf8str(keyword)
							   << "' replacement '" << wstring_to_utf8str(replacement)
							   << "'" << LL_ENDL;
	}
}

void LLFloaterAutoReplaceSettings::onCancel()
{
	cleanUp();
	closeFloater(false /* not quitting */);
}

void LLFloaterAutoReplaceSettings::onSaveChanges()
{
	// put our local copy of the settings into the active copy
	LLAutoReplace::getInstance()->setSettings( mSettings );
	// save our local copy of the global feature enable/disable value
	gSavedSettings.setBOOL("AutoReplace", mEnabled);
	cleanUp();
	closeFloater(false /* not quitting */);
}

void LLFloaterAutoReplaceSettings::cleanUp()
{

}

bool LLFloaterAutoReplaceSettings::selectedListIsFirst()
{
	bool isFirst = false;
	
	if (!mSelectedListName.empty())
	{
		LLSD lists = mSettings.getListNames(); // an Array of Strings
		LLSD first = lists.get(0);
		if ( first.isString() && first.asString() == mSelectedListName )
		{
			isFirst = true;
		}
	}
	return isFirst;
}

bool LLFloaterAutoReplaceSettings::selectedListIsLast()
{
	bool isLast = false;
	
	if (!mSelectedListName.empty())
	{
		LLSD last;
		LLSD lists = mSettings.getListNames(); // an Array of Strings
		for ( LLSD::array_const_iterator list = lists.beginArray(), listEnd = lists.endArray();
			  list != listEnd;
			  list++
			 )
		{
			last = *list;
		}
		if ( last.isString() && last.asString() == mSelectedListName )
		{
			isLast = true;
		}
	}
	return isLast;
}

/* TBD
mOldText = getChild<LLLineEditor>("autoreplace_old_text");
mNewText = getChild<LLLineEditor>("autoreplace_new_text");
*/