/**
 * @file llviewernetwork.cpp
 * @author James Cook, Richard Nelson
 * @brief Networking constants and globals for viewer.
 *
 * $LicenseInfo:firstyear=2006&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 "llviewernetwork.h"
#include "llviewercontrol.h"
#include "llsdserialize.h"
#include "llsecapi.h"
#include "lltrans.h"
#include "llweb.h"


/// key used to store the grid, and the name attribute in the grid data
const std::string  GRID_VALUE = "keyname";
/// the value displayed in the grid selector menu, and other human-oriented text
const std::string  GRID_LABEL_VALUE = "label";
/// the value used on the --grid command line argument
const std::string  GRID_ID_VALUE = "grid_login_id";
/// the url for the login cgi script
const std::string  GRID_LOGIN_URI_VALUE = "login_uri";
/// url base for update queries
const std::string  GRID_UPDATE_SERVICE_URL = "update_query_url_base";
///
const std::string  GRID_HELPER_URI_VALUE = "helper_uri";
/// the splash page url
const std::string  GRID_LOGIN_PAGE_VALUE = "login_page";
/// url for the web profile site
const std::string  GRID_WEB_PROFILE_VALUE = "web_profile_url";
/// internal data on system grids
const std::string  GRID_IS_SYSTEM_GRID_VALUE = "system_grid";
/// whether this is single or double names
const std::string  GRID_LOGIN_IDENTIFIER_TYPES = "login_identifier_types";

// defines slurl formats associated with various grids.
// we need to continue to support existing forms, as slurls
// are shared between viewers that may not understand newer
// forms.
const std::string GRID_SLURL_BASE = "slurl_base";
const std::string GRID_APP_SLURL_BASE = "app_slurl_base";

const std::string DEFAULT_LOGIN_PAGE = "https://viewer-splash.secondlife.com/";

const std::string MAIN_GRID_LOGIN_URI = "https://login.agni.lindenlab.com/cgi-bin/login.cgi";

const std::string SL_UPDATE_QUERY_URL = "https://update.secondlife.com/update";

const std::string MAIN_GRID_SLURL_BASE = "http://maps.secondlife.com/secondlife/";
const std::string SYSTEM_GRID_APP_SLURL_BASE = "secondlife:///app";

const std::string MAIN_GRID_WEB_PROFILE_URL = "https://my.secondlife.com/";

const char* SYSTEM_GRID_SLURL_BASE = "secondlife://%s/secondlife/";
const char* DEFAULT_SLURL_BASE = "https://%s/region/";
const char* DEFAULT_APP_SLURL_BASE = "x-grid-location-info://%s/app";

LLGridManager::LLGridManager()
:	mIsInProductionGrid(false)
{
	// by default, we use the 'grids.xml' file in the user settings directory
	// this file is an LLSD file containing multiple grid definitions.
	// This file does not contain definitions for secondlife.com grids,
	// as that would be a security issue when they are overwritten by
	// an attacker.  Don't want someone snagging a password.
	std::string grid_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,
														   "grids.xml");
	LL_DEBUGS("GridManager")<<LL_ENDL;

	initialize(grid_file);

}


//
// LLGridManager - class for managing the list of known grids, and the current
// selection
//


//
// LLGridManager::initialze - initialize the list of known grids based
// on the fixed list of linden grids (fixed for security reasons)
// and the grids.xml file
void LLGridManager::initialize(const std::string& grid_file)
{
	// default grid list.
	// Don't move to a modifiable file for security reasons,
	mGrid.clear() ;

	// set to undefined
	mGridList = LLSD();
	mGridFile = grid_file;
	// as we don't want an attacker to override our grid list
	// to point the default grid to an invalid grid
  	addSystemGrid(LLTrans::getString("AgniGridLabel"),
				  MAINGRID,
				  MAIN_GRID_LOGIN_URI,
				  "https://secondlife.com/helpers/",
				  DEFAULT_LOGIN_PAGE,
				  SL_UPDATE_QUERY_URL,
				  MAIN_GRID_WEB_PROFILE_URL,
				  "Agni");
	addSystemGrid(LLTrans::getString("AditiGridLabel"),
				  "util.aditi.lindenlab.com",
				  "https://login.aditi.lindenlab.com/cgi-bin/login.cgi",
				  "https://secondlife.aditi.lindenlab.com/helpers/",
				  DEFAULT_LOGIN_PAGE,
				  SL_UPDATE_QUERY_URL,
				  "https://my.secondlife-beta.com/",
				  "Aditi");

	LLSD other_grids;
	llifstream llsd_xml;
	if (!grid_file.empty())
	{
		LL_INFOS("GridManager")<<"Grid configuration file '"<<grid_file<<"'"<<LL_ENDL;
		llsd_xml.open( grid_file.c_str(), std::ios::in | std::ios::binary );

		// parse through the gridfile, inserting grids into the list unless
		// they overwrite an existing grid.
		if( llsd_xml.is_open())
		{
			LLSDSerialize::fromXMLDocument( other_grids, llsd_xml );
			if(other_grids.isMap())
			{
				for(LLSD::map_iterator grid_itr = other_grids.beginMap();
					grid_itr != other_grids.endMap();
					++grid_itr)
				{
					LLSD::String key_name = grid_itr->first;
					LLSD grid = grid_itr->second;

					std::string existingGrid = getGrid(grid);
					if (mGridList.has(key_name) || !existingGrid.empty())
					{
						LL_WARNS("GridManager") << "Cannot override existing grid '" << key_name << "'; ignoring definition from '"<<grid_file<<"'" << LL_ENDL;
					}
					else if ( addGrid(grid) )
					{
						LL_INFOS("GridManager") << "added grid '"<<key_name<<"'"<<LL_ENDL;
					}
					else
					{
						LL_WARNS("GridManager") << "failed to add invalid grid '"<<key_name<<"'"<<LL_ENDL;
					}
				}
				llsd_xml.close();
			}
			else
			{
				LL_WARNS("GridManager")<<"Failed to parse grid configuration '"<<grid_file<<"'"<<LL_ENDL;
			}
		}
		else
		{
			LL_WARNS("GridManager")<<"Failed to open grid configuration '"<<grid_file<<"'"<<LL_ENDL;
		}
	}
	else
	{
		LL_DEBUGS("GridManager")<<"no grid file specified"<<LL_ENDL;
	}

	// load a grid from the command line.
	// if the actual grid name is specified from the command line,
	// set it as the 'selected' grid.
	std::string cmd_line_grid = gSavedSettings.getString("CmdLineGridChoice");
	if(!cmd_line_grid.empty())
	{
		// try to find the grid assuming the command line parameter is
		// the case-insensitive 'label' of the grid.  ie 'Agni'
		mGrid = getGrid(cmd_line_grid);
		if(mGrid.empty())
		{
			LL_WARNS("GridManager")<<"Unknown grid '"<<cmd_line_grid<<"'"<<LL_ENDL;
		}
		else
		{
			LL_INFOS("GridManager")<<"Command line specified '"<<cmd_line_grid<<"': "<<mGrid<<LL_ENDL;
		}
	}
	else
	{
		// if a grid was not passed in via the command line, grab it from the CurrentGrid setting.
		// if there's no current grid, that's ok as it'll be either set by the value passed
		// in via the login uri if that's specified, or will default to maingrid
		std::string last_grid = gSavedSettings.getString("CurrentGrid");
		if ( ! getGrid(last_grid).empty() )
		{
			LL_INFOS("GridManager")<<"Using last grid: "<<last_grid<<LL_ENDL;
			mGrid = last_grid;
		}
		else
		{
			LL_INFOS("GridManager")<<"Last grid '"<<last_grid<<"' not configured"<<LL_ENDL;
		}
	}

	if(mGrid.empty())
	{
		// no grid was specified so default to maingrid
		LL_INFOS("GridManager") << "Default grid to "<<MAINGRID<< LL_ENDL;
		mGrid = MAINGRID;
	}

	LLControlVariablePtr grid_control = gSavedSettings.getControl("CurrentGrid");
	if (grid_control.notNull())
	{
		grid_control->getSignal()->connect(boost::bind(&LLGridManager::updateIsInProductionGrid, this));
	}

	// since above only triggers on changes, trigger the callback manually to initialize state
	updateIsInProductionGrid();

	setGridChoice(mGrid);
}

LLGridManager::~LLGridManager()
{
}

//
// LLGridManager::addGrid - add a grid to the grid list, populating the needed values
// if they're not populated yet.
//

bool LLGridManager::addGrid(LLSD& grid_data)
{
	bool added = false;
	if (grid_data.isMap() && grid_data.has(GRID_VALUE))
	{
		std::string grid = utf8str_tolower(grid_data[GRID_VALUE].asString());

		if ( getGrid(grid_data[GRID_VALUE]).empty() && getGrid(grid).empty() )
		{
			std::string grid_id = grid_data.has(GRID_ID_VALUE) ? grid_data[GRID_ID_VALUE].asString() : "";
			if ( getGrid(grid_id).empty() )
			{
				// populate the other values if they don't exist
				if (!grid_data.has(GRID_LABEL_VALUE))
				{
					grid_data[GRID_LABEL_VALUE] = grid;
				}
				if (!grid_data.has(GRID_ID_VALUE))
				{
					grid_data[GRID_ID_VALUE] = grid;
				}

				// if the grid data doesn't include any of the URIs, then
				// generate them from the grid, which should be a dns address
				if (!grid_data.has(GRID_LOGIN_URI_VALUE))
				{
					grid_data[GRID_LOGIN_URI_VALUE] = LLSD::emptyArray();
					grid_data[GRID_LOGIN_URI_VALUE].append(std::string("https://") +
														   grid + "/cgi-bin/login.cgi");
				}
				// Populate to the default values
				if (!grid_data.has(GRID_LOGIN_PAGE_VALUE))
				{
					grid_data[GRID_LOGIN_PAGE_VALUE] = std::string("http://") + grid + "/app/login/";
				}
				if (!grid_data.has(GRID_HELPER_URI_VALUE))
				{
					grid_data[GRID_HELPER_URI_VALUE] = std::string("https://") + grid + "/helpers/";
				}
				if (!grid_data.has(GRID_WEB_PROFILE_VALUE))
				{
					grid_data[GRID_WEB_PROFILE_VALUE] = std::string("https://") + grid + "/";
				}

				if (!grid_data.has(GRID_LOGIN_IDENTIFIER_TYPES))
				{
					// non system grids and grids that haven't already been configured with values
					// get both types of credentials.
					grid_data[GRID_LOGIN_IDENTIFIER_TYPES] = LLSD::emptyArray();
					grid_data[GRID_LOGIN_IDENTIFIER_TYPES].append(CRED_IDENTIFIER_TYPE_AGENT);
					grid_data[GRID_LOGIN_IDENTIFIER_TYPES].append(CRED_IDENTIFIER_TYPE_ACCOUNT);
				}

				LL_DEBUGS("GridManager") <<grid<<"\n"
										 <<"  id:          "<<grid_data[GRID_ID_VALUE].asString()<<"\n"
										 <<"  label:       "<<grid_data[GRID_LABEL_VALUE].asString()<<"\n"
										 <<"  login page:  "<<grid_data[GRID_LOGIN_PAGE_VALUE].asString()<<"\n"
										 <<"  helper page: "<<grid_data[GRID_HELPER_URI_VALUE].asString()<<"\n"
										 <<"  web profile: "<<grid_data[GRID_WEB_PROFILE_VALUE].asString()<<"\n";
				/* still in LL_DEBUGS */ 
				for (LLSD::array_const_iterator login_uris = grid_data[GRID_LOGIN_URI_VALUE].beginArray();
					 login_uris != grid_data[GRID_LOGIN_URI_VALUE].endArray();
					 login_uris++)
				{
					LL_CONT << "  login uri:   "<<login_uris->asString()<<"\n";
				}
				LL_CONT << LL_ENDL;
				mGridList[grid] = grid_data;
				added = true;
			}
			else
			{
				LL_WARNS("GridManager")<<"duplicate grid id'"<<grid_id<<"' ignored"<<LL_ENDL;
			}
		}
		else
		{
			LL_WARNS("GridManager")<<"duplicate grid name '"<<grid<<"' ignored"<<LL_ENDL;
		}
	}
	else
	{
		LL_WARNS("GridManager")<<"invalid grid definition ignored"<<LL_ENDL;
	}
	return added;
}

//
// LLGridManager::addSystemGrid - helper for adding a system grid.
void LLGridManager::addSystemGrid(const std::string& label,
								  const std::string& name,
								  const std::string& login_uri,
								  const std::string& helper,
								  const std::string& login_page,
								  const std::string& update_url_base,
								  const std::string& web_profile_url,
								  const std::string& login_id)
{
	LLSD grid = LLSD::emptyMap();
	grid[GRID_VALUE] = name;
	grid[GRID_LABEL_VALUE] = label;
	grid[GRID_HELPER_URI_VALUE] = helper;
	grid[GRID_LOGIN_URI_VALUE] = LLSD::emptyArray();
	grid[GRID_LOGIN_URI_VALUE].append(login_uri);
	grid[GRID_LOGIN_PAGE_VALUE] = login_page;
	grid[GRID_UPDATE_SERVICE_URL] = update_url_base;
	grid[GRID_WEB_PROFILE_VALUE] = web_profile_url;
	grid[GRID_IS_SYSTEM_GRID_VALUE] = true;
	grid[GRID_LOGIN_IDENTIFIER_TYPES] = LLSD::emptyArray();
	grid[GRID_LOGIN_IDENTIFIER_TYPES].append(CRED_IDENTIFIER_TYPE_AGENT);

	grid[GRID_APP_SLURL_BASE] = SYSTEM_GRID_APP_SLURL_BASE;
	if (login_id.empty())
	{
		grid[GRID_ID_VALUE] = name;
	}
	else
	{
		grid[GRID_ID_VALUE] = login_id;
	}

	if (name == std::string(MAINGRID))
	{
		grid[GRID_SLURL_BASE] = MAIN_GRID_SLURL_BASE;
	}
	else
	{
		grid[GRID_SLURL_BASE] = llformat(SYSTEM_GRID_SLURL_BASE, grid[GRID_ID_VALUE].asString().c_str());
	}

	addGrid(grid);
}

// return a list of grid name -> grid label mappings for UI purposes
std::map<std::string, std::string> LLGridManager::getKnownGrids()
{
	std::map<std::string, std::string> result;
	for(LLSD::map_iterator grid_iter = mGridList.beginMap();
		grid_iter != mGridList.endMap();
		grid_iter++)
	{
		result[grid_iter->first] = grid_iter->second[GRID_LABEL_VALUE].asString();
	}

	return result;
}

void LLGridManager::setGridChoice(const std::string& grid)
{
	// Set the grid choice based on a string.
	LL_DEBUGS("GridManager")<<"requested "<<grid<<LL_ENDL;
 	std::string grid_name = getGrid(grid); // resolved either the name or the id to the name

	if(!grid_name.empty())
	{
		LL_INFOS("GridManager")<<"setting "<<grid_name<<LL_ENDL;
		mGrid = grid_name;
		gSavedSettings.setString("CurrentGrid", grid_name);
		
		updateIsInProductionGrid();
	}
	else
	{
		// the grid was not in the list of grids.
		LL_WARNS("GridManager")<<"unknown grid "<<grid<<LL_ENDL;
	}
}

std::string LLGridManager::getGrid( const std::string &grid )
{
	std::string grid_name;

	if (mGridList.has(grid))
	{
		// the grid was the long name, so we're good, return it
		grid_name = grid;
	}
	else
	{
		// search the grid list for a grid with a matching id
		for(LLSD::map_iterator grid_iter = mGridList.beginMap();
			grid_name.empty() && grid_iter != mGridList.endMap();
			grid_iter++)
		{
			if (grid_iter->second.has(GRID_ID_VALUE))
			{
				if (0 == (LLStringUtil::compareInsensitive(grid,
														   grid_iter->second[GRID_ID_VALUE].asString())))
				{
					// found a matching label, return this name
					grid_name = grid_iter->first;
				}
			}
		}
	}
	return grid_name;
}

std::string LLGridManager::getGridLabel(const std::string& grid)
{
	std::string grid_label;
	std::string grid_name = getGrid(grid);
	if (!grid.empty())
	{
		grid_label = mGridList[grid_name][GRID_LABEL_VALUE].asString();
	}
	else
	{
		LL_WARNS("GridManager")<<"invalid grid '"<<grid<<"'"<<LL_ENDL;
	}
	LL_DEBUGS("GridManager")<<"returning "<<grid_label<<LL_ENDL;
	return grid_label;
}

std::string LLGridManager::getGridId(const std::string& grid)
{
	std::string grid_id;
	std::string grid_name = getGrid(grid);
	if (!grid.empty())
	{
		grid_id = mGridList[grid_name][GRID_ID_VALUE].asString();
	}
	else
	{
		LL_WARNS("GridManager")<<"invalid grid '"<<grid<<"'"<<LL_ENDL;
	}
	LL_DEBUGS("GridManager")<<"returning "<<grid_id<<LL_ENDL;
	return grid_id;
}

void LLGridManager::getLoginURIs(const std::string& grid, std::vector<std::string>& uris)
{
	uris.clear();
	std::string grid_name = getGrid(grid);
	if (!grid_name.empty())
	{
        if (mGridList[grid_name][GRID_LOGIN_URI_VALUE].isArray())
        {
		    for (LLSD::array_iterator llsd_uri = mGridList[grid_name][GRID_LOGIN_URI_VALUE].beginArray();
			     llsd_uri != mGridList[grid_name][GRID_LOGIN_URI_VALUE].endArray();
			     llsd_uri++)
		    {
			    uris.push_back(llsd_uri->asString());
		    }
        }
        else
        {
            uris.push_back(mGridList[grid_name][GRID_LOGIN_URI_VALUE].asString());
        }
	}
	else
	{
		LL_WARNS("GridManager")<<"invalid grid '"<<grid<<"'"<<LL_ENDL;
	}
}

void LLGridManager::getLoginURIs(std::vector<std::string>& uris)
{
	getLoginURIs(mGrid, uris);
}

std::string LLGridManager::getHelperURI(const std::string& grid)
{
	std::string helper_uri;
	std::string grid_name = getGrid(grid);
	if (!grid_name.empty())
	{
		helper_uri = mGridList[grid_name][GRID_HELPER_URI_VALUE].asString();
	}
	else
	{
		LL_WARNS("GridManager")<<"invalid grid '"<<grid<<"'"<<LL_ENDL;
	}

	LL_DEBUGS("GridManager")<<"returning "<<helper_uri<<LL_ENDL;
	return helper_uri;
}

std::string LLGridManager::getLoginPage(const std::string& grid)
{
	std::string grid_login_page;
	std::string grid_name = getGrid(grid);
	if (!grid_name.empty())
	{
		grid_login_page = mGridList[grid_name][GRID_LOGIN_PAGE_VALUE].asString();
	}
	else
	{
		LL_WARNS("GridManager")<<"invalid grid '"<<grid<<"'"<<LL_ENDL;
	}
	return grid_login_page;
}

std::string LLGridManager::getLoginPage()
{
	std::string login_page = mGridList[mGrid][GRID_LOGIN_PAGE_VALUE].asString();
	LL_DEBUGS("GridManager")<<"returning "<<login_page<<LL_ENDL;
	return login_page;
}

std::string LLGridManager::getWebProfileURL(const std::string& grid)
{
	std::string web_profile_url;
	std::string grid_name = getGrid(grid);
	if (!grid_name.empty())
	{
		web_profile_url = mGridList[grid_name][GRID_WEB_PROFILE_VALUE].asString();
	}
	else
	{
		LL_WARNS("GridManager")<<"invalid grid '"<<grid<<"'"<<LL_ENDL;
	}
	return web_profile_url;
}

void LLGridManager::getLoginIdentifierTypes(LLSD& idTypes)
{
	idTypes = mGridList[mGrid][GRID_LOGIN_IDENTIFIER_TYPES];
}

std::string LLGridManager::getGridLoginID()
{
	return mGridList[mGrid][GRID_ID_VALUE];
}

std::string LLGridManager::getUpdateServiceURL()
{
	std::string update_url_base = gSavedSettings.getString("CmdLineUpdateService");;
	if ( !update_url_base.empty() )
	{
		LL_INFOS("UpdaterService","GridManager")
			<< "Update URL base overridden from command line: " << update_url_base
			<< LL_ENDL;
	}
	else if ( mGridList[mGrid].has(GRID_UPDATE_SERVICE_URL) )
	{
		update_url_base = mGridList[mGrid][GRID_UPDATE_SERVICE_URL].asString();
	}
	else
	{
		LL_WARNS("UpdaterService","GridManager")
			<< "The grid property '" << GRID_UPDATE_SERVICE_URL
			<< "' is not defined for the grid '" << mGrid << "'"
			<< LL_ENDL;
	}
			
	return update_url_base;
}

void LLGridManager::updateIsInProductionGrid()
{
	mIsInProductionGrid = false;

	// *NOTE:Mani This used to compare GRID_INFO_AGNI to gGridChoice,
	// but it seems that loginURI trumps that.
	std::vector<std::string> uris;
	getLoginURIs(uris);
	if (uris.empty())
	{
		mIsInProductionGrid = true;
	}
	else
	{
		for ( std::vector<std::string>::iterator uri_it = uris.begin();
			  ! mIsInProductionGrid && uri_it != uris.end();
			  uri_it++
			 )
		{
			if( MAIN_GRID_LOGIN_URI == *uri_it )
			{
				mIsInProductionGrid = true;
			}
		}
	}
}

bool LLGridManager::isInProductionGrid()
{
	return mIsInProductionGrid;
}

bool LLGridManager::isSystemGrid(const std::string& grid)
{
	std::string grid_name = getGrid(grid);

	return (   !grid_name.empty()
			&& mGridList.has(grid)
			&& mGridList[grid].has(GRID_IS_SYSTEM_GRID_VALUE)
			&& mGridList[grid][GRID_IS_SYSTEM_GRID_VALUE].asBoolean()
			);
}

// build a slurl for the given region within the selected grid
std::string LLGridManager::getSLURLBase(const std::string& grid)
{
	std::string grid_base = "";
	std::string grid_name = getGrid(grid);
	if( ! grid_name.empty() && mGridList.has(grid_name) )
	{
		if (mGridList[grid_name].has(GRID_SLURL_BASE))
		{
			grid_base = mGridList[grid_name][GRID_SLURL_BASE].asString();
		}
		else
		{
			grid_base = llformat(DEFAULT_SLURL_BASE, grid_name.c_str());
		}
	}
	LL_DEBUGS("GridManager")<<"returning '"<<grid_base<<"'"<<LL_ENDL;
	return grid_base;
}

// build a slurl for the given region within the selected grid
std::string LLGridManager::getAppSLURLBase(const std::string& grid)
{
	std::string grid_base = "";
	std::string grid_name = getGrid(grid);
	if(!grid_name.empty() && mGridList.has(grid))
	{
		if (mGridList[grid].has(GRID_APP_SLURL_BASE))
		{
			grid_base = mGridList[grid][GRID_APP_SLURL_BASE].asString();
		}
		else
		{
			grid_base = llformat(DEFAULT_APP_SLURL_BASE, grid_name.c_str());
		}
	}
	LL_DEBUGS("GridManager")<<"returning '"<<grid_base<<"'"<<LL_ENDL;
	return grid_base;
}