/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

/* $Id$ */

#include <http/URL.h>
#include "commontypes.h"
#include "Errors.h"
#include "daemon/ProfileComponentsHolder.h"
#include "Logger/LoggerMacroses.h"
#include "treemanager/IMOTreeManager.h"
#include "serverexchange/IServerExchangeManager.h"
#include "serverexchange/commands/AccessConfigCommand.h"


#define AUTH_TYPE_MAC	"syncml:auth-MAC"
#define CLCRED			"CLCRED"
#define SRVCRED			"SRVCRED"
#define URI_DMACC        "./DMAcc"

using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_Communication;

const char* const c_LogName = "DMAccountCommand";


DMAccountCommand::DMAccountCommand(ProfileComponentsHolder * pHolder) :
	m_pProfileHolder(pHolder)
{
}


bool DMAccountCommand::Execute()
{
	assert(m_pProfileHolder != NULL);

	IMOTreeManager::ChildrenPaths accounts;
	ConnInfoList connections;
	if (e_Ok == m_pProfileHolder->GetMOTreeManager()->GetChildren(URI_DMACC, accounts, ""))
	{
		for (size_t i=0; i<accounts.size(); ++i)
		{
			String accname = accounts[i];
			FStringBuffer uriAccount0(URI_DMACC);
			uriAccount0.append("/").append(accname.c_str()); // this is uri ./DMAcc/X
			ConnectionInfo *pConnInfo = NULL;
			if (pConnInfo = readAccount(uriAccount0))
			{
				pConnInfo->accname = accname;
				connections.push_back(ConnectionInfoPtr(pConnInfo));
			}
		}


	}
	m_pProfileHolder->GetServerExchangeManager()->SetConnectionsInfo(connections);

    return true;
}


ConnectionInfo * DMAccountCommand::readAccount(FStringBuffer &uriAccount)
{
	GDLDEBUG("  read account '%s'", uriAccount.c_str() ? uriAccount.c_str() : "");
	FStringBuffer outvalue;
	
	FStringBuffer srvaddr;
	FStringBuffer srvaddrtype;
	FStringBuffer srvport;

	FStringBuffer srvCredAddr;
	FStringBuffer clCredAddr;
	FStringBuffer authlevel;
	FStringBuffer authtype;
	FStringBuffer nonce;
	FStringBuffer password;
	FStringBuffer authname;

	bool ret = false;
	ConnectionInfo * pConnInfo = new ConnectionInfo;
	Funambol::AccessConfig &ac = pConnInfo->acconfig;
	ac.setServerAuthRequired(false);
	
	do {
		if (!getLeafValue(uriAccount, "AppID", outvalue))
		{
			GDLERROR("read acc info: failed to retrieve AppID %s", uriAccount.c_str());
			break; // do not tolerate absence of AppID node
		}
		pConnInfo->appID = outvalue.c_str();
		outvalue.reset();
		
		
		if (!getLeafValue(uriAccount, "ServerID",  outvalue))
		{
			GDLERROR("read acc info: failed to retrieve ServerID %s", uriAccount.c_str());
			break; // do not tolerate absence of ServerID node
		}
		pConnInfo->SetServerID(outvalue.c_str());
		outvalue.reset();

		
		getLeafValue(uriAccount, "AAuthPref", outvalue);
		if (outvalue.length())
		{
			pConnInfo->prefferedAuth = outvalue.c_str();
			outvalue.reset();
		}
		
		
		getLeafValue(uriAccount, "Name", outvalue);
		if (outvalue.length())
		{
			pConnInfo->name = outvalue.c_str();
			outvalue.reset();
		}
		
		
		getLeafValue(uriAccount, "PrefConRef", outvalue);
		if (outvalue.length())
		{
			pConnInfo->prefferedConRef = outvalue.c_str();
			outvalue.reset();
		}

		
		if (!getServerNode(uriAccount, m_uriServerNode))
		{
			GDLERROR("read acc info: failed to retrieve server address node in %s", uriAccount.c_str());
			break; // do not tolerate absence of required nodes
		}

		if (!getLeafValue(m_uriServerNode, "Addr", srvaddr))
		{
			GDLERROR("read acc info: required Addr node in %s is absent", m_uriServerNode.c_str());
			break; // do not tolerate absence of required nodes
		}

		if (!getLeafValue(m_uriServerNode, "AddrType", srvaddrtype))
		{
			GDLERROR("read acc info: required AddrType node in %s is absent", m_uriServerNode.c_str());
			break;
		}


		// read client cred node
		if (!getAuthenticationNode(uriAccount, clCredAddr, CLCRED))
		{
			GDLERROR("Failed to retrieve client authentication node in %s", uriAccount.c_str());
			break; // do not tolerate absence of required client cred
		}
		else
		{
			if (getLeafValue(clCredAddr, "AAuthData",   nonce))
				ac.setClientNonce(nonce);

			if (getLeafValue(clCredAddr, "AAuthName",   authname))
				ac.setUsername(authname);

			if (getLeafValue(clCredAddr, "AAuthSecret", password))
				ac.setPassword(password);

			if (getLeafValue(clCredAddr, "AAuthType",   authtype))
			{
				if (!strcmp("BASIC", authtype))
					ac.setClientAuthType(AUTH_TYPE_BASIC);
				else if (!strcmp("DIGEST", authtype))
					ac.setClientAuthType(AUTH_TYPE_MD5);
				else if (!strcmp("HMAC", authtype))
					ac.setClientAuthType(AUTH_TYPE_MAC);
				else
				{
					GDLERROR("Config contains unsupported value of AAuthType: %s", authtype.c_str());
					GDLERROR("Working value set to DIGEST");
					ac.setClientAuthType(AUTH_TYPE_MD5);
				}
			}
		}

		// read server cred node
		if (!getAuthenticationNode(uriAccount, srvCredAddr, SRVCRED))
		{
			GDLERROR("read acc info: : failed to retrieve server authentication node in %s", uriAccount.c_str());
			break; // do not tolerate absence of required server cred
		}
		else
		{
			if (getLeafValue(srvCredAddr, "AAuthData",   nonce))
				ac.setServerNonce(nonce);

			if (getLeafValue(srvCredAddr, "AAuthName",   authname))
				ac.setServerID(authname);

			if (getLeafValue(srvCredAddr, "AAuthSecret", password))
				ac.setServerPWD(password);

			if (getLeafValue(srvCredAddr, "AAuthType",   authtype))
			{
				if (!strcmp("BASIC", authtype))
					ac.setServerAuthType(AUTH_TYPE_BASIC);
				else if (!strcmp("DIGEST", authtype))
					ac.setServerAuthType(AUTH_TYPE_MD5);
				else if (!strcmp("HMAC", authtype))
					ac.setServerAuthType(AUTH_TYPE_MAC);
				else
				{
					GDLERROR("Config contains unsupported value of AAuthType: %s", authtype.c_str());
					GDLERROR("Working value set to DIGEST");
					ac.setServerAuthType(AUTH_TYPE_MD5);
				}
			}
		}

		getServerPort(srvport);

		if (srvport.empty())
		{
			ac.setSyncURL(srvaddr);
		}
		else
		{
			Funambol::URL url;
			url.setURL(srvaddr.c_str());
			url.port = atoi(srvport.c_str());

			FStringBuffer urlstring;
			urlstring.append(url.protocol).append("://").append(url.host);
			if (url.port)
				urlstring.append(":").append(url.port);
			urlstring.append(url.resource);

			ac.setSyncURL(urlstring.c_str());
		}
		ret = true;
	}
	while (false);
	
	if (!ret)
		SAFE_DELETE(pConnInfo);

	return pConnInfo;
}


// @param	uriAccount	is a ./DMAcc/<X> uri
// @result				is a ./DMAcc/<X>/AppAddr/<[0]> uri
bool DMAccountCommand::getServerNode(FStringBuffer & uriAccount, FStringBuffer & result)
{
	FStringBuffer uriAppAddr(uriAccount);//m_uriServerNode);
	uriAppAddr.append("/AppAddr");	// ./DMAcc/<X>/AppAddr

	IMOTreeManager::ChildrenPaths paths;
	if (e_Ok == m_pProfileHolder->GetMOTreeManager()->GetChildren(uriAppAddr.c_str(), paths, NULL)) // perform in root mode
	{
		if (!paths.empty())
		{
			// TODO - first entry is taken; consider criteries of choosing other servers
			result.assign(uriAppAddr).append("/").append(paths[0].c_str());
			return true;
		}
	}
	return false;
}


// @param	uriAccount	is a ./DMAcc/<X> uri
// @result				is a ./DMAcc/<X>/AppAuth/<[node with SRVCRED uri]>
bool DMAccountCommand::getAuthenticationNode(FStringBuffer & uriAccount, FStringBuffer & result, const char *authname)
{
	if (!authname) return false;
	FStringBuffer uriAppAuth(uriAccount);
	uriAppAuth.append("/AppAuth");	// ./DMAcc/<X>/AppAuth

	IMOTreeManager::ChildrenPaths paths;
	if (e_Ok == m_pProfileHolder->GetMOTreeManager()->GetChildren(uriAppAuth.c_str(), paths, NULL))  // perform in root mode
	{
		if (!paths.empty())
		{
			IMOTreeManager::ChildrenPaths::iterator i = paths.begin();
			for (; i != paths.end(); ++i)
			{
				FStringBuffer uri_alevel = uriAppAuth.c_str();
				uri_alevel.append("/").append((*i).c_str());
				uri_alevel.append("/AAuthLevel"); // ./DMAcc/<X>/AppAuth/<X>/AAuthLevel
				String value;
				if (e_Ok == m_pProfileHolder->GetMOTreeManager()->GetValue(uri_alevel.c_str(), value, NULL))
				{
					if (!strcmp(authname, value.c_str()))
					{
						result.assign(uriAppAuth.c_str()).append("/").append((*i).c_str());//i->c_str());
						return true;
					}
				}
			}
		}
	}
	return false;
}


// @param	c_UriServerNode	is a ./DMAcc/<X>/AppAddr/<X> uri
// @result					is a ./DMAcc/<X>/AppAddr/<X>/Port/<[0]>/PortNbr uri
void DMAccountCommand::getServerPort(FStringBuffer &result)
{
	FStringBuffer uriPort(m_uriServerNode);
	uriPort.append("/Port");	// ./DMAcc/<X>/AppAddr/<X>/Port

	IMOTreeManager::ChildrenPaths paths;
	if (e_Ok == m_pProfileHolder->GetMOTreeManager()->GetChildren(uriPort.c_str(), paths, ""))
	{
		if (!paths.empty())
		{
			// TODO - first entry is taken; consider criteries of choosing other servers
			uriPort.append("/").append(paths[0].c_str()).append("/PortNbr");

			String value;
			if (e_Ok == m_pProfileHolder->GetMOTreeManager()->GetValue(uriPort.c_str(), value, NULL)) // perform in root mode
			{
				result.assign(value.c_str());
			}
		}
	}
}


// @param		uriNode some uri in MO tree
// @leafname	name of leaf in  uriNode
// @result		value of the s a uriNode/propname
bool DMAccountCommand::getLeafValue(FStringBuffer & uriNode, const char * leafname, FStringBuffer &result)
{
	FStringBuffer uriAuthProperty(uriNode);
	uriAuthProperty.append("/").append(leafname);	// uriNode/propname

	String value;
	if (e_Ok == m_pProfileHolder->GetMOTreeManager()->GetValue(uriAuthProperty.c_str(), value, NULL)) // perform in root mode
	{
		result.assign(value.c_str());
		return true;
	}
	return false;
}

