/**
 * @file llautoreplace.cpp
 * @brief Auto Replace Manager
 *
 * $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 "llautoreplace.h"
#include "llsdserialize.h"
#include "llboost.h"
#include "llcontrol.h"
#include "llviewercontrol.h"
#include "llnotificationsutil.h"

const char* LLAutoReplace::SETTINGS_FILE_NAME = "autoreplace.xml";

void LLAutoReplace::autoreplaceCallback(S32& replacement_start, S32& replacement_length, LLWString& replacement_string, S32& cursor_pos, const LLWString& input_text)
{
	// make sure these returned values are cleared in case there is no replacement
	replacement_start = 0;
	replacement_length = 0;
	replacement_string.clear();

	static LLCachedControl<bool> perform_autoreplace(gSavedSettings, "AutoReplace", 0);
	if (perform_autoreplace)
	{
		S32 word_end = cursor_pos - 1;

		bool at_space  = (input_text[word_end] == ' ');
		bool have_word = (LLWStringUtil::isPartOfWord(input_text[word_end]));

		if (at_space || have_word)
		{
			if (at_space && word_end > 0)
			{
				// find out if this space immediately follows a word
				word_end--;
				have_word  = (LLWStringUtil::isPartOfWord(input_text[word_end]));
			}
			if (have_word)
			{
				// word_end points to the end of a word, now find the start of the word
				std::string word;
				S32 word_start = word_end;
				for (S32 back_one = word_start - 1;
					 back_one >= 0 && LLWStringUtil::isPartOfWord(input_text[back_one]);
					 back_one--
					)
				{
					word_start--; // walk word_start back to the beginning of the word
				}
				LL_DEBUGS("AutoReplace") << "word_start: " << word_start << " word_end: " << word_end << LL_ENDL;
				std::string str_text  = std::string(input_text.begin(), input_text.end());
				std::string last_word = str_text.substr(word_start, word_end - word_start + 1);
				std::string replacement_word(mSettings.replaceWord(last_word));

				if (replacement_word != last_word)
				{
					// The last word is one for which we have a replacement
					if (at_space)
					{
						// return the replacement string
						replacement_start = word_start;
						replacement_length = last_word.length();
						replacement_string = utf8str_to_wstring(replacement_word);
						LLWString old_string = utf8str_to_wstring(last_word);
						S32 size_change = replacement_string.size() - old_string.size();
						cursor_pos += size_change;
					}
				}
			}
		}
	}
}

std::string LLAutoReplace::getUserSettingsFileName()
{
	std::string path=gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "");

	if (!path.empty())
	{
		path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, SETTINGS_FILE_NAME);
	}
	return path;
}

std::string LLAutoReplace::getAppSettingsFileName()
{
	std::string path=gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "");

	if (!path.empty())
	{
		path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, SETTINGS_FILE_NAME);
	}
	else
	{
		LL_ERRS("AutoReplace") << "Failed to get app settings directory name" << LL_ENDL;
	}
	return path;
}

LLAutoReplaceSettings LLAutoReplace::getSettings()
{
	return mSettings;
}

void LLAutoReplace::setSettings(const LLAutoReplaceSettings& newSettings)
{
	mSettings.set(newSettings);
	/// Make the newSettings active and write them to user storage
	saveToUserSettings();
}

LLAutoReplace::LLAutoReplace()
{
}

void LLAutoReplace::initSingleton()
{
    loadFromSettings();
}

void LLAutoReplace::loadFromSettings()
{
	std::string filename=getUserSettingsFileName();
	if (filename.empty())
	{
		LL_INFOS("AutoReplace") << "no valid user settings directory." << LL_ENDL;
	}
	if(gDirUtilp->fileExists(filename))
	{
		LLSD userSettings;
		llifstream file;
		file.open(filename.c_str());
		if (file.is_open())
		{
			LLSDSerialize::fromXML(userSettings, file);
		}
		file.close();
		if ( mSettings.setFromLLSD(userSettings) )
		{
			LL_INFOS("AutoReplace") << "settings loaded from '" << filename << "'" << LL_ENDL;
		}
		else
		{
			LL_WARNS("AutoReplace") << "invalid settings found in '" << filename << "'" << LL_ENDL;
		}
	}
	else // no user settings found, try application settings
	{
		std::string defaultName = getAppSettingsFileName();
		LL_INFOS("AutoReplace") << " user settings file '" << filename << "' not found"<< LL_ENDL;

		bool gotSettings = false;
		if(gDirUtilp->fileExists(defaultName))
		{
			LLSD appDefault;
			llifstream file;
			file.open(defaultName.c_str());
			if (file.is_open())
			{
				LLSDSerialize::fromXMLDocument(appDefault, file);
			}
			file.close();

			if ( mSettings.setFromLLSD(appDefault) )
			{
				LL_INFOS("AutoReplace") << "settings loaded from '" << defaultName.c_str() << "'" << LL_ENDL;
				gotSettings = true;
			}
			else
			{
				LL_WARNS("AutoReplace") << "invalid settings found in '" << defaultName.c_str() << "'" << LL_ENDL;
			}
		}
		
		if ( ! gotSettings )
		{
			if (mSettings.setFromLLSD(mSettings.getExampleLLSD()))
			{
				LL_WARNS("AutoReplace") << "no settings found; loaded example." << LL_ENDL;
			}
			else
			{
				LL_WARNS("AutoReplace") << "no settings found and example invalid!" << LL_ENDL;
			}
		}
	}
}

void LLAutoReplace::saveToUserSettings()
{
	std::string filename=getUserSettingsFileName();
	llofstream file;
	file.open(filename.c_str());
	LLSDSerialize::toPrettyXML(mSettings.asLLSD(), file);
	file.close();
	LL_INFOS("AutoReplace") << "settings saved to '" << filename << "'" << LL_ENDL;
}

// ================================================================
// LLAutoReplaceSettings
// ================================================================

const std::string LLAutoReplaceSettings::AUTOREPLACE_LIST_NAME         = "name";    	 ///< key for looking up list names
const std::string LLAutoReplaceSettings::AUTOREPLACE_LIST_REPLACEMENTS = "replacements"; ///< key for looking up replacement map

LLAutoReplaceSettings::LLAutoReplaceSettings()
{
}

LLAutoReplaceSettings::LLAutoReplaceSettings(const LLAutoReplaceSettings& settings)
{
	// copy all values through fundamental type intermediates for thread safety
	mLists = LLSD::emptyArray();

	for ( LLSD::array_const_iterator list = settings.mLists.beginArray(), listEnd = settings.mLists.endArray();
		  list != listEnd;
		  list++
		 )
	{
		if ( (*list).isMap() ) // can fail due to LLSD-30: ignore it
		{
			LLSD listMap = LLSD::emptyMap();
			std::string listName = (*list)[AUTOREPLACE_LIST_NAME];
			listMap[AUTOREPLACE_LIST_NAME] = listName;
			listMap[AUTOREPLACE_LIST_REPLACEMENTS] = LLSD::emptyMap();

			for ( LLSD::map_const_iterator
					  entry = (*list)[AUTOREPLACE_LIST_REPLACEMENTS].beginMap(),
					  entriesEnd = (*list)[AUTOREPLACE_LIST_REPLACEMENTS].endMap();
				  entry != entriesEnd;
				  entry++
				 )
			{
				std::string keyword = entry->first;
				std::string replacement = entry->second.asString();
				listMap[AUTOREPLACE_LIST_REPLACEMENTS].insert(keyword, LLSD(replacement));
			}

			mLists.append(listMap);
		}
	}
}

void LLAutoReplaceSettings::set(const LLAutoReplaceSettings& newSettings)
{
	mLists = newSettings.mLists;
}

bool LLAutoReplaceSettings::setFromLLSD(const LLSD& settingsFromLLSD)
{
	bool settingsValid = true;

	if ( settingsFromLLSD.isArray() )
	{
		for ( LLSD::array_const_iterator
				  list = settingsFromLLSD.beginArray(),
				  listEnd = settingsFromLLSD.endArray();
			  settingsValid && list != listEnd;
			  list++
			 )
		{
			if ( (*list).isDefined() ) // can be undef due to LLSD-30: ignore it
			{
				settingsValid = listIsValid(*list);
			}
		}
	}
	else
	{
		settingsValid = false;
		LL_WARNS("AutoReplace") << "settings are not an array" << LL_ENDL;
	}

	if ( settingsValid )
	{
		mLists = settingsFromLLSD;
	}
	else
	{
		LL_WARNS("AutoReplace") << "invalid settings discarded; using hard coded example" << LL_ENDL;
	}

	return settingsValid;
}

bool LLAutoReplaceSettings::listNameMatches( const LLSD& list, const std::string name )
{
	return list.isMap()
		&& list.has(AUTOREPLACE_LIST_NAME)
		&& list[AUTOREPLACE_LIST_NAME].asString() == name;
}

const LLSD* LLAutoReplaceSettings::getListEntries(std::string listName)
{
	const LLSD* returnedEntries = NULL;
	for( LLSD::array_const_iterator list = mLists.beginArray(), endList = mLists.endArray();
		 returnedEntries == NULL && list != endList;
		 list++
		)
	{
		const LLSD& thisList = *list;
		if ( listNameMatches(thisList, listName) )
		{
			returnedEntries = &thisList[AUTOREPLACE_LIST_REPLACEMENTS];
		}
	}
	return returnedEntries;
}

std::string LLAutoReplaceSettings::replacementFor(std::string keyword, std::string listName)
{
	std::string replacement;
	bool foundList = false;
	for( LLSD::array_const_iterator list = mLists.beginArray(), endList = mLists.endArray();
		 ! foundList && list != endList;
		 list++
		)
	{
		const LLSD& thisList = *list;
		if ( listNameMatches(thisList, listName) )
		{
			foundList = true; // whether there is a replacement or not, we're done
			if (   thisList.isMap()
				&& thisList.has(AUTOREPLACE_LIST_REPLACEMENTS)
				&& thisList[AUTOREPLACE_LIST_REPLACEMENTS].has(keyword)
				)
			{
				replacement = thisList[AUTOREPLACE_LIST_REPLACEMENTS][keyword].asString();
				LL_DEBUGS("AutoReplace")<<"'"<<keyword<<"' -> '"<<replacement<<"'"<<LL_ENDL;
			}
		}
		if (!foundList)
		{
			LL_WARNS("AutoReplace")<<"failed to find list '"<<listName<<"'"<<LL_ENDL;
		}
	}
	if (replacement.empty())
	{
		LL_WARNS("AutoReplace")<<"failed to find '"<<keyword<<"'"<<LL_ENDL;
	}
	return replacement;
}

LLSD LLAutoReplaceSettings::getListNames()
{
	LL_DEBUGS("AutoReplace")<<"====="<<LL_ENDL;
	LLSD toReturn = LLSD::emptyArray();
	S32 counter=0;
	for( LLSD::array_const_iterator list = mLists.beginArray(), endList = mLists.endArray();
		 list != endList;
		 list++
		)
	{
		const LLSD& thisList = *list;
		if ( thisList.isMap() )
		{
			if ( thisList.has(AUTOREPLACE_LIST_NAME) )
			{
				std::string name = thisList[AUTOREPLACE_LIST_NAME].asString();
				LL_DEBUGS("AutoReplace")<<counter<<" '"<<name<<"'"<<LL_ENDL;
				toReturn.append(LLSD(name));
			}
			else
			{
				LL_ERRS("AutoReplace") <<counter<<" ! MISSING "<<AUTOREPLACE_LIST_NAME<< LL_ENDL;
			}
		}
		else
		{
			LL_WARNS("AutoReplace")<<counter<<" ! not a map: "<<LLSD::typeString(thisList.type())<< LL_ENDL;
		}
		counter++;
	}
	LL_DEBUGS("AutoReplace")<<"^^^^^^"<<LL_ENDL;
	return toReturn;
}

bool LLAutoReplaceSettings::listIsValid(const LLSD& list)
{
	bool listValid = true;
	if ( ! list.isMap() )
	{
		listValid = false;
		LL_WARNS("AutoReplace") << "list is not a map" << LL_ENDL;
	}
	else if (   ! list.has(AUTOREPLACE_LIST_NAME)
			 || ! list[AUTOREPLACE_LIST_NAME].isString()
			 || list[AUTOREPLACE_LIST_NAME].asString().empty()
			 )
	{
		listValid = false;
		LL_WARNS("AutoReplace")
			<< "list found without " << AUTOREPLACE_LIST_NAME
			<< " (or it is empty)"
			<< LL_ENDL;
	}
	else if ( ! list.has(AUTOREPLACE_LIST_REPLACEMENTS) || ! list[AUTOREPLACE_LIST_REPLACEMENTS].isMap() )
	{
		listValid = false;
		LL_WARNS("AutoReplace") << "list '" << list[AUTOREPLACE_LIST_NAME].asString() << "' without " << AUTOREPLACE_LIST_REPLACEMENTS << LL_ENDL;
	}
	else
	{
		for ( LLSD::map_const_iterator
				  entry = list[AUTOREPLACE_LIST_REPLACEMENTS].beginMap(),
				  entriesEnd = list[AUTOREPLACE_LIST_REPLACEMENTS].endMap();
			  listValid && entry != entriesEnd;
			  entry++
			 )
		{
			if ( ! entry->second.isString() )
			{
				listValid = false;
				LL_WARNS("AutoReplace")
					<< "non-string replacement value found in list '"
					<< list[AUTOREPLACE_LIST_NAME].asString() << "'"
					<< LL_ENDL;
			}
		}
	}

	return listValid;
}

const LLSD* LLAutoReplaceSettings::exportList(std::string listName)
{
	const LLSD* exportedList = NULL;
	for ( LLSD::array_const_iterator list = mLists.beginArray(), listEnd = mLists.endArray();
		  exportedList == NULL && list != listEnd;
		  list++
		 )
	{
		if ( listNameMatches(*list, listName) )
		{
			const LLSD& namedList = (*list);
			exportedList = &namedList;
		}
	}
	return exportedList;
}

bool LLAutoReplaceSettings::listNameIsUnique(const LLSD& newList)
{
	bool nameIsUnique = true;
	// this must always be called with a valid list, so it is safe to assume it has a name
	std::string newListName = newList[AUTOREPLACE_LIST_NAME].asString();
	for ( LLSD::array_const_iterator list = mLists.beginArray(), listEnd = mLists.endArray();
		  nameIsUnique && list != listEnd;
		  list++
		 )
	{
		if ( listNameMatches(*list, newListName) )
		{
			LL_WARNS("AutoReplace")<<"duplicate list name '"<<newListName<<"'"<<LL_ENDL;
			nameIsUnique = false;
		}
	}
	return nameIsUnique;
}

/* static */
void LLAutoReplaceSettings::createEmptyList(LLSD& emptyList)
{
	emptyList = LLSD::emptyMap();
	emptyList[AUTOREPLACE_LIST_NAME] = "Empty";
	emptyList[AUTOREPLACE_LIST_REPLACEMENTS] = LLSD::emptyMap();
}

/* static */
void LLAutoReplaceSettings::setListName(LLSD& list, const std::string& newName)
{
	list[AUTOREPLACE_LIST_NAME] = newName;
}

/* static */
std::string LLAutoReplaceSettings::getListName(LLSD& list)
{
	std::string name;
	if ( list.isMap() && list.has(AUTOREPLACE_LIST_NAME) && list[AUTOREPLACE_LIST_NAME].isString() )
	{
		name = list[AUTOREPLACE_LIST_NAME].asString();
	}
	return name;
}

LLAutoReplaceSettings::AddListResult LLAutoReplaceSettings::addList(const LLSD& newList)
{
	AddListResult result;
	if ( listIsValid( newList ) )
	{
		if ( listNameIsUnique( newList ) )
		{
			mLists.append(newList);
			result = AddListOk;
		}
		else
		{
			LL_WARNS("AutoReplace") << "attempt to add duplicate name" << LL_ENDL;
			result = AddListDuplicateName;
		}
	}
	else
	{
		LL_WARNS("AutoReplace") << "attempt to add invalid list" << LL_ENDL;
		result = AddListInvalidList;
	}
	return result;
}

LLAutoReplaceSettings::AddListResult LLAutoReplaceSettings::replaceList(const LLSD& newList)
{
	AddListResult result = AddListInvalidList;
	if ( listIsValid( newList ) )
	{
		std::string listName = newList[AUTOREPLACE_LIST_NAME].asString();
		bool listFound = false;
		S32 search_index;
		LLSD targetList;
		// The following is working around the fact that LLSD arrays containing maps also seem to have undefined entries... see LLSD-30
		for ( search_index = 0, targetList = mLists[0];
			  !listFound && search_index < mLists.size();
			  search_index += 1, targetList = mLists[search_index]
			 )
		{
			if ( targetList.isMap() )
			{
				if ( listNameMatches( targetList, listName) )
				{
					LL_DEBUGS("AutoReplace")<<"list to replace found at "<<search_index<<LL_ENDL;
					mLists.erase(search_index);
					mLists.insert(search_index, newList);
					listFound = true;
					result = AddListOk;
				}
			}
		}
		
		if ( ! listFound )
		{
			LL_WARNS("AutoReplace") << "attempt to replace unconfigured list" << LL_ENDL;
		}
	}
	else
	{
		LL_WARNS("AutoReplace") << "attempt to add invalid list" << LL_ENDL;
	}
	return result;
}

bool LLAutoReplaceSettings::removeReplacementList(std::string listName)
{
	bool found = false;
	for( S32 index = 0; !found && mLists[index].isDefined(); index++ )
	{
		if( listNameMatches(mLists.get(index), listName) )
		{
			LL_DEBUGS("AutoReplace")<<"list '"<<listName<<"'"<<LL_ENDL;
			mLists.erase(index);
			found = true;
		}
	}
	return found;
}

/// Move the named list up in the priority order
bool LLAutoReplaceSettings::increaseListPriority(std::string listName)
{
	LL_DEBUGS("AutoReplace")<<listName<<LL_ENDL;
	bool found = false;
	S32 search_index, previous_index;
	LLSD targetList;
	// The following is working around the fact that LLSD arrays containing maps also seem to have undefined entries... see LLSD-30
	previous_index = -1;
	for ( search_index = 0, targetList = mLists[0];
		  !found && search_index < mLists.size();
		  search_index += 1, targetList = mLists[search_index]
		 )
	{
		if ( targetList.isMap() )
		{
			if ( listNameMatches( targetList, listName) )
			{
				LL_DEBUGS("AutoReplace")<<"found at "<<search_index<<", previous is "<<previous_index<<LL_ENDL;
				found = true;
				if (previous_index >= 0)
				{
					LL_DEBUGS("AutoReplace") << "erase "<<search_index<<LL_ENDL;
					mLists.erase(search_index);
					LL_DEBUGS("AutoReplace") << "insert at "<<previous_index<<LL_ENDL;
					mLists.insert(previous_index, targetList);
				} 
				else
				{
					LL_WARNS("AutoReplace") << "attempted to move top list up" << LL_ENDL;
				}
			}
			else
			{
				previous_index = search_index;
			}
		}
		else
		{
			LL_DEBUGS("AutoReplace") << search_index<<" is "<<LLSD::typeString(targetList.type())<<LL_ENDL;
		}		
	}
	return found;
}

/// Move the named list down in the priority order
bool LLAutoReplaceSettings::decreaseListPriority(std::string listName)
{
	LL_DEBUGS("AutoReplace")<<listName<<LL_ENDL;
	S32 found_index = -1;	
	S32 search_index;
	for ( search_index = 0;
		  found_index == -1 && search_index < mLists.size();
		  search_index++
		 )
	{
		if ( listNameMatches( mLists[search_index], listName) )
		{
			LL_DEBUGS("AutoReplace")<<"found at index "<<search_index<<LL_ENDL;
			found_index = search_index;
		}
	}
	if (found_index != -1)
	{
		S32 next_index;
		for ( next_index = found_index+1;
			  next_index < mLists.size() && ! mLists[next_index].isMap();
			  next_index++
			 )
		{
			// skipping over any undefined slots (see LLSD-30)
			LL_WARNS("AutoReplace")<<next_index<<" ! not a map: "<<LLSD::typeString(mLists[next_index].type())<< LL_ENDL;
		}
		if ( next_index < mLists.size() )
		{
			LLSD next_list = mLists[next_index];
			LL_DEBUGS("AutoReplace") << "erase "<<next_index<<LL_ENDL;
			mLists.erase(next_index);
			LL_DEBUGS("AutoReplace") << "insert at "<<found_index<<LL_ENDL;
			mLists.insert(found_index, next_list);
		}
		else
		{
			LL_WARNS("AutoReplace") << "attempted to move bottom list down" << LL_ENDL;
		}
	}
	else
	{
		LL_WARNS("AutoReplace") << "not found" << LL_ENDL;
	}
	return (found_index != -1);
}


std::string LLAutoReplaceSettings::replaceWord(const std::string currentWord)
{
	std::string returnedWord = currentWord; // in case no replacement is found
	static LLCachedControl<bool> autoreplace_enabled(gSavedSettings, "AutoReplace", false);
	if ( autoreplace_enabled )
	{
		LL_DEBUGS("AutoReplace")<<"checking '"<<currentWord<<"'"<< LL_ENDL;
		//loop through lists in order
		bool found = false;
		for( LLSD::array_const_iterator list = mLists.beginArray(), endLists = mLists.endArray();
			 ! found && list != endLists;
			 list++
			)
		{
			const LLSD& checkList = *list;
			const LLSD& replacements = checkList[AUTOREPLACE_LIST_REPLACEMENTS];

			if ( replacements.has(currentWord) )
			{
				found = true;
				LL_DEBUGS("AutoReplace")
					<< "  found in list '" << checkList[AUTOREPLACE_LIST_NAME].asString()
					<< " => '" << replacements[currentWord].asString() << "'"
					<< LL_ENDL;
				returnedWord = replacements[currentWord].asString();
			}
		}
	}
	return returnedWord;
}

bool LLAutoReplaceSettings::addEntryToList(LLWString keyword, LLWString replacement, std::string listName)
{
	bool added = false;

	if ( ! keyword.empty() && ! replacement.empty() )
	{
		bool isOneWord = true;
		for (S32 character = 0; isOneWord && character < keyword.size(); character++ )
		{
			if ( ! LLWStringUtil::isPartOfWord(keyword[character]) )
			{
				LL_WARNS("AutoReplace") << "keyword '" << wstring_to_utf8str(keyword) << "' not a single word (len "<<keyword.size()<<" '"<<character<<"')" << LL_ENDL;
				isOneWord = false;
			}
		}

		if ( isOneWord )
		{
			bool listFound = false;
			for( LLSD::array_iterator list = mLists.beginArray(), endLists = mLists.endArray();
				 ! listFound && list != endLists;
				 list++
				)
			{
				if ( listNameMatches(*list, listName) )
				{
					listFound = true;
					(*list)[AUTOREPLACE_LIST_REPLACEMENTS][wstring_to_utf8str(keyword)]=wstring_to_utf8str(replacement);
				}
			}
			if (listFound)
			{
				added = true;
			}
			else
			{
				LL_WARNS("AutoReplace") << "list '" << listName << "' not found" << LL_ENDL;
			}
		}
	}

	return added;
}

bool LLAutoReplaceSettings::removeEntryFromList(std::string keyword, std::string listName)
{
	bool found = false;
	for( LLSD::array_iterator list = mLists.beginArray(), endLists = mLists.endArray();
		 ! found && list != endLists;
		 list++
		)
	{
		if ( listNameMatches(*list, listName) )
		{
			found = true;
			(*list)[AUTOREPLACE_LIST_REPLACEMENTS].erase(keyword);
		}
	}
	if (!found)
	{
		LL_WARNS("AutoReplace") << "list '" << listName << "' not found" << LL_ENDL;
	}
	return found;
}

LLSD LLAutoReplaceSettings::getExampleLLSD()
{
	LL_DEBUGS("AutoReplace")<<LL_ENDL;
	LLSD example = LLSD::emptyArray();

	example[0] = LLSD::emptyMap();
	example[0][AUTOREPLACE_LIST_NAME]    = "Example List 1";
	example[0][AUTOREPLACE_LIST_REPLACEMENTS]    = LLSD::emptyMap();
	example[0][AUTOREPLACE_LIST_REPLACEMENTS]["keyword1"] = "replacement string 1";
	example[0][AUTOREPLACE_LIST_REPLACEMENTS]["keyword2"] = "replacement string 2";

	example[1] = LLSD::emptyMap();
	example[1][AUTOREPLACE_LIST_NAME]    = "Example List 2";
	example[1][AUTOREPLACE_LIST_REPLACEMENTS]    = LLSD::emptyMap();
	example[1][AUTOREPLACE_LIST_REPLACEMENTS]["mistake1"] = "correction 1";
	example[1][AUTOREPLACE_LIST_REPLACEMENTS]["mistake2"] = "correction 2";

	return example;
}

const LLSD& LLAutoReplaceSettings::asLLSD()
{
	return mLists;
}

LLAutoReplaceSettings::~LLAutoReplaceSettings()
{
}