/*
 * 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 "Logger/LoggerMacroses.h"
#include "serverexchange/Connection.h"
#include "serverexchange/ConnectionInfo.h"
#include "serverexchange/session/Session.h"
#include "serverexchange/wrappers/SAlertCommand.h"
#include "serverexchange/wrappers/SCommandFactory.h"
#include "serverexchange/wrappers/SReplaceCommand.h"
#include "serverexchange/wrappers/SStatusCommand.h"
#include "serverexchange/ICommandsSink.h"
#include "serverexchange/LOSendingStrategy.h"
#include "serverexchange/ResponseProcessor.h"

#include <base/errors.h>
#include <ctime>
#include <fstream>
#include <wbxml.h>
#include <wbxml_mem.h>
#include "hash.h"
#include "Utils.h"
#include "WBXMLUtils.h"


using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_Communication;
using namespace NS_DM_Client::NS_SyncMLCommand;
using namespace NS_DM_Client::NS_Common;
	
static const char * c_LogName= "Session";

#define PROPERTY_ACCEPT        "Accept"
#define PROPERTY_CONTENT_TYPE  "Content-Type"
#define ACCEPT_BOTH_MIMES      "application/vnd.syncml.dm+xml, application/vnd.syncml.dm+wbxml"


Session::Session(ConnectionInfo &ci) : 
	m_firstMessage(true),
	m_commandsAreResent(false),
	m_connectionInfo(ci),
	m_pTransportAgent(NULL),
	m_responseMsg(NULL),
	m_pLOStrategy(NULL),
	m_pResponseProcessor(new ResponseProcessor(ci, NULL, NULL))
{
	m_pResponseProcessor->SetSession(*this);
}


Session::~Session()
{
	SAFE_DELETE(m_responseMsg);
	SAFE_DELETE(m_pLOStrategy);
	SAFE_DELETE(m_pResponseProcessor);
	SAFE_DELETE(m_pTransportAgent);
	
	m_connectionInfo.Reset();
}


bool Session::IsAuthenticationPassed(Funambol::Status &status0)
{
	const char *data = "";
	if (status0.getData())
		data = status0.getData()->getData();
	else
		return false;
	
	int code = atoi(data);
	GDLDEBUG("status on SyncHdr: %d", code);

	return false;
}


void Session::Connect(NS_SyncMLCommand::CommandStorage & semcommands)
{
	if (0 == semcommands.TryLock())
	{
		GDLDEBUG("%d command%s waiting to be sent",
				 semcommands.Size(), semcommands.Size() == 1 ? " is" : "s are");
		
		// insert Alert, Replace
		if (semcommands.Size() || m_pLOStrategy)
		{
			m_commands.Lock(); //// 2011.05.27
			for (int i=0; i<semcommands.Size(); ++i)
			{
				m_commands.Add(semcommands[i]);
			}
			m_commands.Unlock();
			semcommands.Clear();
		}
		semcommands.Unlock();

		if (m_pLOStrategy)
		{
			GDLDEBUG("send with LOStrategy [%x]\thas to send next chunk: %s",
					 m_pLOStrategy, m_connectionInfo.hasToSendNextChunk ? "yes" : "no");
			if (m_connectionInfo.hasToSendNextChunk)
			{
				SendingStrategy::Status status;
				
				GDLDEBUG("Send next LO chunk");
				status = m_pLOStrategy->Send();
				if (SendingStrategy::NeedToSendLO != status)
					processResponse();
				// TODO - rework to check status from Send()
				if (m_pLOStrategy->Finished())
				{
					GDLDEBUG("LO sending finished; delete LOStrategy");
					SAFE_DELETE(m_pLOStrategy);
					m_connectionInfo.hasToSendNextChunk = false;
					m_commands.Lock(); //// 2011.05.27
					m_commands.Clear();
					m_commands.Unlock();
					m_commandsToSend.Lock(); //// 2011.05.27
					m_commandsToSend.Clear();
					m_commandsToSend.Unlock();
				}
			}
			else
			{
				GDLDEBUG("\tdelete LOStrategy (either all LO sent"
						 " or server did not confirmed further chunks transmission)");
				SAFE_DELETE(m_pLOStrategy);
				m_connectionInfo.hasToSendNextChunk = false;
			}
		}
		else
		{
			insertSessionInitiationCommands(m_state.InitialAlertCode);
			m_commandsToSend.Lock(); //// 2011.05.27
			for (int i=0; i<m_commands.Size(); ++i)
			{
				m_commandsToSend.Add(m_commands[i]);
			}
			m_commandsToSend.Unlock();

			SendingStrategy strategy(m_connectionInfo, *this);
			SendingStrategy::Status status;
			
			status = strategy.Send(m_commandsToSend);

			if (SendingStrategy::NeedToSendLO != status)
				processResponse();
			
			semcommands.Lock();
			GDLDEBUG("has to resend last commands: %s; need to send LO: %s",
					 m_connectionInfo.hasToResendLastCommands ? "yes" : "no",
					 SendingStrategy::NeedToSendLO == status ? "yes" : "no");
			
			if (m_connectionInfo.hasToResendLastCommands)
			{
				for (int i=0; i<m_commands.Size(); ++i)
				{
					semcommands.Add(m_commands[i]);
					GDLDEBUG("\t%s", m_commands[i]->Internal()->getName());
				}
GDLDEBUG("Begin m_commands.Clear() #1");
GDLDEBUG("m_commands.Size()= %d", m_commands.Size());
				m_commands.Lock(); //// 2011.05.27
				m_commands.Clear();
				m_commands.Unlock();
GDLDEBUG("End m_commands.Clear() #1");				
				m_commandsToSend.Lock(); //// 2011.05.27
				m_commandsToSend.Clear();
				m_commandsToSend.Unlock(); //// 2011.05.27
			}

			m_commandsAreResent = m_connectionInfo.hasToResendLastCommands;
			if (SendingStrategy::NeedToSendLO != status)
			{
GDLDEBUG("Begin m_commands.Clear() #2");
GDLDEBUG("m_commands.Size()= %d", m_commands.Size());
				m_commands.Lock(); //// 2011.05.27
				m_commands.Clear();
				m_commands.Unlock();
GDLDEBUG("End m_commands.Clear() #2");
				m_commandsToSend.Lock(); //// 2011.05.27
				m_commandsToSend.Clear();
				m_commandsToSend.Unlock(); //// 2011.05.27
GDLDEBUG("End m_commands.Clear() #2");
			}
GDLDEBUG("Begin semcommands.Unlock()");		
			semcommands.Unlock();
GDLDEBUG("End semcommands.Unlock()");		
			if (SendingStrategy::NeedToSendLO == status)
			{
				m_pLOStrategy = new LOSendingStrategy(m_connectionInfo, m_commands, *this);
				m_commandsAreResent = false;
				m_connectionInfo.hasToSendNextChunk = true;
				m_connectionInfo.connectionFinished = false;
				GDLDEBUG("");
			}
		}
	}	
}


bool Session::IsResponseValid(const char *, uint resplength)
{
	return false;
}


bool Session::IsSendingLO()
{
	return m_pLOStrategy != NULL;
}


void Session::SendMessage(FStringBuffer &message)
{
	Funambol::URL url(m_connectionInfo.GetSessionURL());
	createTransport();
	m_pTransportAgent->setURL(url);

	const char *xml = message.c_str();
	removeEndLineChars(message);
	PrintMessage(c_LogName, "send message", xml);
	
	if (m_connectionInfo.settings.UseWBXML)
	{
		const char *wbxml = NULL;
		unsigned int wbxmllength = 0;
		int result;
		if (WBXML_OK == (result = WBXMLUtils::FormatToWBXML(xml, &wbxml, &wbxmllength)))
		{
			prepareTransport(wbxml, wbxmllength);
			// TODO - merge dump and printmsg
			dumpMessage(wbxml, wbxmllength, "send message - wbxml");
			//PrintMessage(c_LogName, "send message", xml);
			m_responseMsg = m_pTransportAgent->sendMessage(wbxml, wbxmllength);
			wbxml_free((void*)wbxml);
		}
		else
		{
			GDLERROR("FAILED to convert syncml to wbxml, err code %d", result);
			return;
		}
	}
	else
	{
		int length = strlen(xml);
		prepareTransport(xml, length);
		// TODO - merge dump and printmsg
		dumpMessage(xml, length, "send message - plain syncml");
		//PrintMessage(c_LogName, "send message", xml);
		m_responseMsg = m_pTransportAgent->sendMessage(xml, length);
	}
}


void Session::SendMessage(const char *xml)
{
	Funambol::URL url(m_connectionInfo.GetSessionURL());
	createTransport();
	m_pTransportAgent->setURL(url);
	
	if (m_connectionInfo.settings.UseWBXML)
	{
		const char *wbxml = NULL;
		unsigned int wbxmllength = 0;
		int result;
		if (WBXML_OK == (result = WBXMLUtils::FormatToWBXML(xml, &wbxml, &wbxmllength)))
		{
			prepareTransport(wbxml, wbxmllength);
			// TODO - merge dump and printmsg
			dumpMessage(wbxml, wbxmllength, "send message - wbxml");
			PrintMessage(c_LogName, "send message", xml);
			m_responseMsg = m_pTransportAgent->sendMessage(wbxml, wbxmllength);
			wbxml_free((void*)wbxml);
		}
		else
		{
			GDLERROR("FAILED to convert syncml to wbxml, err code %d", result);
			return;
		}
	}
	else
	{
		int length = strlen(xml);
		prepareTransport(xml, length);
		// TODO - merge dump and printmsg
		dumpMessage(xml, length, "send message - plain syncml");
		PrintMessage(c_LogName, "send message", xml);
		m_responseMsg = m_pTransportAgent->sendMessage(xml, length);
	}
}


void Session::SetCommandsSink(ICommandsSink &cs)
{
	m_pResponseProcessor->SetCommandsSink(cs);
}


void Session::SetPCH(ProfileComponentsHolder &pch)
{
	m_pResponseProcessor->SetPCH(pch);
}


void Session::checkAcceptTypes()
{
	const char * accept = m_pTransportAgent->getResponseProperty("Accept");
	
	if (accept)
	{
		if (strstr(accept, MIMETYPE_SYNCMLDM_XML))
			m_connectionInfo.settings.UseWBXML = false;
		else if (strstr(accept, MIMETYPE_SYNCMLDM_WBXML))
			m_connectionInfo.settings.UseWBXML = true;
	
		SAFE_DELETE_ARR(accept);
	}
}


void Session::createTransport()
{
	Funambol::URL url("");
	if (m_pTransportAgent == NULL)
	{
		// TODO no proxy is used currently
		Funambol::Proxy proxy;
		m_pTransportAgent =
		Funambol::TransportAgentFactory::getTransportAgent(url,
														   proxy);
		m_pTransportAgent->setCompression(false);
		m_pTransportAgent->setReadBufferSize(5000);
		if (!m_connectionInfo.settings.CertificatesLocation.empty())
		{
			m_pTransportAgent->setSSLServerCertificates(m_connectionInfo.settings.CertificatesLocation.c_str());
			m_pTransportAgent->setSSLVerifyServer(m_connectionInfo.settings.SSLVerifyServer);
			m_pTransportAgent->setSSLVerifyHost(m_connectionInfo.settings.SSLVerifyHost);
		}
//		m_pTransportAgent->setUserAgent("");
	}
}


void Session::dumpMessage(const char *msg, size_t size, const char *prefix)
{
	if (!m_connectionInfo.messagesDumpPath.empty())
	{
		time_t rawtime;
		struct tm * timeinfo;
		time ( &rawtime );
		timeinfo = localtime ( &rawtime );
		
		std::ofstream out(m_connectionInfo.messagesDumpPath.c_str(), std::ios_base::app);
		out << asctime (timeinfo) << " " << prefix << ", size " << size << " bytes" << std::endl;
		out.write(msg, size);
		out << std::endl << std::endl;
		out.close();
	}
}


void Session::prepareTransport(const char *, uint)
{
	if (m_connectionInfo.settings.UseWBXML)
	{
		m_pTransportAgent->setProperty(PROPERTY_ACCEPT, ACCEPT_BOTH_MIMES);
		m_pTransportAgent->setProperty(PROPERTY_CONTENT_TYPE, MIMETYPE_SYNCMLDM_WBXML);
	}
	else
	{
		m_pTransportAgent->setProperty(PROPERTY_ACCEPT, MIMETYPE_SYNCMLDM_XML);
		m_pTransportAgent->setProperty(PROPERTY_CONTENT_TYPE, MIMETYPE_SYNCMLDM_XML);
	}
}


void Session::insertAlertReplace(int code)
{
	SCommandPtr ptrReplaceDevInf = SCommandFactory::CreateReplaceDevInf(*m_connectionInfo.devinf);
	
	if (1)//m_state.AddInitialAlert)
	{
// 		SAlertCommandPtr SCommandFactory
// 		SCommandPtr ptrAlert
// 		SCommandFactory::CreateAlert(AC_GENERIC_ALERT, NULL, const char *reqtype, int data)
		SCommandPtr ptrAlert(new SAlertCommand(code));
		m_commandsToSend.Lock(); //// 2011.05.27
		m_commandsToSend.InsertFirst(ptrReplaceDevInf);
		m_commandsToSend.InsertFirst(ptrAlert);
		m_commandsToSend.Unlock();
GDLDEBUG("ptrReplaceDevInf : %p",ptrReplaceDevInf);
	}
	else
	{
		m_commandsToSend.Lock(); //// 2011.05.27
		m_commandsToSend.Add(ptrReplaceDevInf);
		m_commandsToSend.Unlock();
	}
}


void Session::processResponse()
{
GDLDEBUG("Start Session::processResponse()");
	const char * mime   = m_pTransportAgent->getResponseProperty(PROPERTY_CONTENT_TYPE);
GDLDEBUG("mime :%s", mime);
	int resplength      = m_pTransportAgent->getResponseSize();
	
	if (resplength == 0 || m_responseMsg == NULL || m_responseMsg[0] == '\0')
	{
		GDLDEBUG("no response from server {len, respmsg} = {%d, %x}", resplength, m_responseMsg);
		GDLDEBUG("transport error: %d", getLastErrorCode());
		m_connectionInfo.connectionFinished = true;
		return;
	}

	bool checkForCred = !IsResponseValid(m_responseMsg, resplength);
	SetServerAuthenticated(!checkForCred);

	m_pResponseProcessor->SetCheckForCred(checkForCred);

	if (mime && (strstr(mime, MIMETYPE_SYNCMLDM_WBXML)))
	{
		m_connectionInfo.settings.UseWBXML = true; // switch session to wbxml
		GDLDEBUG(" Server response - WBXML [%d bytes]", resplength);
		dumpMessage(m_responseMsg, resplength, "Server response - wbxml");
	
		char *xml = NULL;
		int result;
		if (WBXML_OK == (result = WBXMLUtils::Parse(m_responseMsg, resplength, &xml)))
		{
			PrintMessage(c_LogName, "Server response - decoded wbxml", xml);
			m_pResponseProcessor->Process(xml);
			wbxml_free(xml);
		}
		else
		{
			GDLERROR("FAILED to decode wbxml response, err code %d", result);
			// todo - error status - same as no answer ?
		}
	}
	else if ((mime && (strstr(mime, MIMETYPE_SYNCMLDM_XML))) || !mime)
	{
		dumpMessage(m_responseMsg, strlen(m_responseMsg), "Server response - plain syncml");
		PrintMessage(c_LogName, "Server response - plain syncml", m_responseMsg);
		m_pResponseProcessor->Process(m_responseMsg);
	}
	else
	{
		GDLDEBUG("Server replied with type %s; skip reply", mime);
		GDLDEBUG("\tmessage:\n\n%s", m_responseMsg);
		m_connectionInfo.connectionFinished = true;
		// todo - error status - same as no answer ?
	}
	
	checkAcceptTypes();
	
	SAFE_DELETE_ARR(m_responseMsg);
//	SAFE_DELETE_ARR(mime);
GDLDEBUG("End Session::processResponse()");
}


void Session::removeEndLineChars(FStringBuffer &message)
{
	if (!m_connectionInfo.settings.KeepNewLines)
		message.replaceAll("\n", "");
}
