/*
 * 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 <base/util/utils.h>
#include <spds/SyncMLProcessor.h>
#include <syncml/core/NextNonce.h>
#include <syncml/core/TagNames.h>
#include <syncml/core/SyncHdr.h>

#include "commontypes.h"
#include "Errors.h"
#include "Logger/LoggerMacroses.h"
#include "Utils.h"

#include "executionqueue/IExecutionQueue.h"
#include "treemanager/AlertCommand.h"
#include "treemanager/MOTreeAddCommand.h"
#include "treemanager/MOTreeAtomicCommand.h"
#include "treemanager/MOTreeCopyCommand.h"
#include "treemanager/MOTreeDeleteCommand.h"
#include "treemanager/MOTreeExecCommand.h"
#include "treemanager/MOTreeGetCommand.h"
#include "treemanager/MOTreeReplaceCommand.h"
#include "treemanager/MOTreeSequenceCommand.h"

#include "serverexchange/LOCollector.h"
#include "serverexchange/ResponseProcessor.h"
#include "serverexchange/ICommandsSink.h"
#include "serverexchange/IServerExchangeManager.h"
#include "serverexchange/wrappers/SCommandFactory.h"
#include "serverexchange/wrappers/SAlertCommand.h"
#include "serverexchange/wrappers/SStatusCommand.h"

#define HTTP_PREFIX		"http://"
#define HTTPS_PREFIX	"https://"


using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_Communication;
using namespace NS_DM_Client::NS_SyncMLCommand;

static const char * c_LogName = "ResponseProcessor";


ResponseProcessor::ResponseProcessor(ConnectionInfo &ci, NS_SyncMLCommand::ICommandsSink *pCS, ProfileComponentsHolder *pPCH) :
	m_sessionFinished(false),
	m_checkForCred(false),
	m_changeAuthType(false),
	m_cServerID(NULL),
	m_responseCommandsCount(0),
	m_pConnInfo(&ci),
	m_pSyncML(NULL),
	m_pLOCollector(NULL),
	m_pPCH(pPCH)
{
	m_pCommandsSink = pCS;
//	TODO - rework
// 	if (m_pConnection)
// 		m_pConnInfo = &(m_pConnection->Info());
// 	else
// 		m_pConnInfo = new ConnectionInfo();
}


ResponseProcessor::~ResponseProcessor()
{
	SAFE_DELETE_ARR(m_cServerID);
	SAFE_DELETE(m_pSyncML);
	SAFE_DELETE(m_pLOCollector);
}


ResponseProcessor::ProcessingStatus ResponseProcessor::Process(const char * xmlresponse, bool sumode)
{
GDLDEBUG("Start ResponseProcessor::Process");
	if (!xmlresponse) return PS_InvalidParam;

	ProcessingStatus result = PS_Successful;
	Funambol::SyncMLProcessor processor;
	m_pSyncML = processor.processMsg(const_cast<char*>(xmlresponse));
	m_changeAuthType = m_checkForCred = false;
	m_sessionFinished = m_pConnInfo->connectionFinished = true;
	
	do
	{
		if (!m_pSyncML || !m_pSyncML->getSyncHdr() || !m_pSyncML->getSyncBody())
		{
			result = PS_MalformedMessage;
//			m_pConnInfo->connectionFinished = true;
			break;
		}

		if (!sumode && !m_pSession->IsSendingLO())
		{
			if (m_pSession->State().ServerAuthenticated)
			{
				GDLINFO("Server authenticated in prev message, skip check");
			}
			else
			{
				if (m_pSession->IsServerCredValid(m_pSyncML->getSyncHdr()->getCred()))
				{
					m_pSession->SetServerAuthenticated(true);
					m_checkForCred = false;
					GDLINFO("Server authenticated");
				}
				else
				{
					GDLDEBUG("Server not authenticated (Cred info is absent or invalid)");

					// TODO - review
					if (!m_pSyncML->getSyncHdr()->getCred() ||
						(m_pSyncML->getSyncHdr()->getCred() && 
						 strcmp(m_pSession->GetTypeName(), m_pSyncML->getSyncHdr()->getCred()->getType())))
					{
						m_changeAuthType = true;
						m_checkForCred = true;
					}
					else if (m_pSession->AcceptsNonces())
					{
						m_pConnInfo->hasToResendLastCommands = true;
						m_checkForCred = true;
					}
					else
					{
						result = PS_InvalidServerCred;
						m_pConnInfo->connectionFinished = true;
						m_pConnInfo->hasToResendLastCommands = false;
						break;
					}
				}
			}
		}

		Funambol::ArrayList *pCommands = m_pSyncML->getSyncBody()->getCommands();
		if (!pCommands || !pCommands->size())
		{
			result = PS_MalformedMessage; break;
		}

		processHeader();
		m_pConnInfo->SetLOMode(!m_pSyncML->isLastMessage());
		GDLDEBUG("Final tag is %s", m_pSyncML->isLastMessage() ? "set" : "not set");
		GDLDEBUG("");
		
		if (!m_pSyncML->isLastMessage() && !m_pConnInfo->hasLOSending)
		{
			if (!m_pLOCollector)
			{
				m_pLOCollector = new LOCollector();
				GDLDEBUG("Create LOCollector %x", m_pLOCollector);
			}
		}

		if (m_pConnInfo->IsWaitingForNextChunk())
		{
			// check whether alert status is ok
			checkForNextChunkData();
		}

		GDLDEBUG("Server response contains   %d command%s", pCommands->size(), pCommands->size()==1 ? "" : "s");

		initProcessing(sumode, pCommands->size());

		m_pConnInfo->repeatAuthentication = false;
//		m_sessionFinished = m_pConnInfo->connectionFinished = true;

		int statusCommandsCount = 0;
		for (int i=0; i<pCommands->size(); ++i)
		{
			Funambol::AbstractCommand *command = dynamic_cast<Funambol::AbstractCommand*>(pCommands->get(i));
			if (command)
			{
				processCommand(command);
				if (!strcmp(STATUS_COMMAND_NAME, command->getName()))
					statusCommandsCount++;
			}
			else
				GDLDEBUG("Unknow command type");
		}
		
//		repeatAuthentication
		if (!sumode && !m_pSession->State().ServerAuthenticated && !m_pSession->CredentialsSent())
		{	
			m_pConnInfo->repeatAuthentication = true;
			m_pConnInfo->connectionFinished = false;
		}
	} while (0);

	SAFE_DELETE(m_pSyncML);
GDLDEBUG("End ResponseProcessor::Process");
	return result;
}


// Check received response for the status on the alert 1222.
void ResponseProcessor::checkForNextChunkData()
{
	Funambol::ArrayList *pCommands = m_pSyncML->getSyncBody()->getCommands();
	for (int i=0; i<pCommands->size(); ++i)
	{
		Funambol::AbstractCommand *pCommand = dynamic_cast<Funambol::AbstractCommand*>(pCommands->get(i));
		if (pCommand && !strcmp(STATUS_COMMAND_NAME, pCommand->getName()))
		{
			Funambol::Status *pStatus = (Funambol::Status*)pCommand;

			char bufOK[16] = {0};
			__sprintf(bufOK, "%d", e_Ok);

			if (pStatus && pStatus->getCmd() && pStatus->getData() && pStatus->getData()->getData() &&
				!strcmp(ALERT_COMMAND_NAME, pStatus->getCmd()) &&
				!strcmp(bufOK, pStatus->getData()->getData()))
			{
				// message from server contains chunk with LO data
				m_pConnInfo->SetLOMode(true);
				return;
			}
		}
	}
	m_pConnInfo->SetLOMode(false);
}


bool ResponseProcessor::commandHasLOChunk(Funambol::ItemizedCommand *cmd)
{
	if (!cmd || !cmd->getItems())
		return false;

	Funambol::ArrayList *pItems = cmd->getItems();
	if (pItems->size() == 1)
	{
		Funambol::Item *pItem = (Funambol::Item *) pItems->get(0);
		return (pItem && pItem->getMoreData());
	}
	return false;
}


void ResponseProcessor::enqueueExCommand(ActionCommand &cmd)
{
	if (m_pCommandsSink)
		cmd.SetCommandsSink(*m_pCommandsSink);
	m_pPCH->GetExecutionQueue()->Add(cmd);
}


void ResponseProcessor::processCommand(Funambol::AbstractCommand *cmd)
{
GDLDEBUG("Start ResponseProcessor::processCommand");
	if (!cmd) return;
	
	const char *name = cmd->getName();
	if (m_checkForCred && strcmp(name, STATUS_COMMAND_NAME))
	{
		m_pConnInfo->msgState.DecCommandsCount();
		return;
	}

	GDLDEBUG("\tprocess command '%s', id=%s cr only - %s", 
		name, 
		cmd->getCmdID() ? cmd->getCmdID()->getCmdID() : "[CmdID is absent in command's body]", 
		m_checkForCred ? "yes" : "no");

	if (m_pConnInfo->IsInLOMode() && (!strcmp(name, ADD_COMMAND_NAME) || !strcmp(name, REPLACE_COMMAND_NAME)))
	{
		GDLDEBUG("before process chunk in command '%s' Meta - %x", name, cmd->getMeta());
		Funambol::ItemizedCommand *pICmd = dynamic_cast<Funambol::ItemizedCommand*>(cmd);
		if (0 <= processLOChunk(pICmd))
		{
			GDLDEBUG("LO chunk processed");
			return;
		}
		// negative value means passed command does not carry chunked data
		// and should be processed in ordinal way
	}
	

	if (name && !strcmp(name, STATUS_COMMAND_NAME))
	{
		processStatus(dynamic_cast<Funambol::Status*>(cmd));
	}
	else if (name && !strcmp(name, ALERT_COMMAND_NAME))
	{
		processAlert(dynamic_cast<Funambol::Alert*>(cmd));
	}
	else if (name && !strcmp(name, ADD_COMMAND_NAME))
	{
		processAdd(dynamic_cast<Funambol::Add*>(cmd));
	}
	else if (name && !strcmp(name, ATOMIC_COMMAND_NAME))
	{
		processAtomic(dynamic_cast<Funambol::Atomic*>(cmd));
	}
	else if (name && !strcmp(name, COPY_COMMAND_NAME))
	{
		processCopy(dynamic_cast<Funambol::Copy*>(cmd));
	}
	else if (name && !strcmp(name, DELETE_COMMAND_NAME))
	{
		processDelete(dynamic_cast<Funambol::Delete*>(cmd));
	}
	else if (name && !strcmp(name, EXEC_COMMAND_NAME))
	{
		processExec(dynamic_cast<Funambol::Exec*>(cmd));
	}
	else if (name && !strcmp(name, GET_COMMAND_NAME))
	{
		processGet(dynamic_cast<Funambol::Get*>(cmd));
	}
	else if (name && !strcmp(name, REPLACE_COMMAND_NAME))
	{
		processReplace(dynamic_cast<Funambol::Replace*>(cmd));
	}
	else if (name && !strcmp(name, RESULTS_COMMAND_NAME))
	{
		processResults(dynamic_cast<Funambol::Results*>(cmd));
	}
	else if (name && !strcmp(name, SEQUENCE_COMMAND_NAME))
	{
		processSequence(dynamic_cast<Funambol::Sequence*>(cmd));
	}
GDLDEBUG("End ResponseProcessor::processCommand");
}


void ResponseProcessor::processAlert(Funambol::Alert *cmd)
{
GDLDEBUG("Start ResponseProcessor::processAlert");
	if (!cmd) return;

	int data = cmd->getData();
	if (AC_LO_REQUEST_NEXT_CHUNK == data)
	{
		// insert status with 200 on Alert into the 0 pos
		if (cmd->getCmdID())
			m_pConnInfo->SetNextChunkAlertCmdID(cmd->getCmdID()->getCmdID());
		m_pConnInfo->connectionFinished = false;
		m_pConnInfo->msgState.DecCommandsCount();
	}
	else if (AC_SESSION_ABORT == data)
	{
		GDLDEBUG("abort session with server");
		m_pConnInfo->abortConnection = true;
	}
	else
	{
		AlertPtr ptrAlert((Funambol::Alert*)cmd->clone());
		ActionCommand *pAlertCmd = new AlertCommand(m_pPCH, ptrAlert,
													m_pConnInfo->LastServerMessageID(), m_cServerID);
		enqueueExCommand(*pAlertCmd);
	}
GDLDEBUG("End ResponseProcessor::processAlert");
}


void ResponseProcessor::processAdd(Funambol::Add *cmd)
{
	if (!cmd) return;

	AddPtr ptrAdd((Funambol::Add*)cmd->clone());
	ActionCommand *pAddCmd = new MOTreeAddCommand(m_pPCH, ptrAdd,
												  m_pConnInfo->LastServerMessageID(), m_cServerID);
	enqueueExCommand(*pAddCmd);
}


void ResponseProcessor::processAtomic(Funambol::Atomic *cmd)
{
	if (!cmd) return;

	if (cmd->getCommands())
		m_pConnInfo->msgState.IncCommandsCount(cmd->getCommands()->size());

	AtomicPtr ptrAtomic((Funambol::Atomic*)cmd->clone());
	ActionCommand *pAtomicCmd = new MOTreeAtomicCommand(m_pPCH, ptrAtomic,
														m_pConnInfo->LastServerMessageID(), m_cServerID);
	enqueueExCommand(*pAtomicCmd);
}


void ResponseProcessor::processCopy(Funambol::Copy *cmd)
{
	if (!cmd) return;

	CopyPtr ptrGet((Funambol::Copy*)cmd->clone());
	ActionCommand *pCopyCmd = new MOTreeCopyCommand(m_pPCH, ptrGet,
													m_pConnInfo->LastServerMessageID(), m_cServerID);
	enqueueExCommand(*pCopyCmd);
}


void ResponseProcessor::processDelete(Funambol::Delete *cmd)
{
	if (!cmd) return;

	DeletePtr ptrDelete((Funambol::Delete*)cmd->clone());
	ActionCommand *pDeleteCmd = new MOTreeDeleteCommand(m_pPCH, ptrDelete,
														m_pConnInfo->LastServerMessageID(), m_cServerID);
	enqueueExCommand(*pDeleteCmd);
}


void ResponseProcessor::processExec(Funambol::Exec *cmd)
{
	if (!cmd) return;

	ExecPtr ptrExec((Funambol::Exec*)cmd->clone());
	ActionCommand *pExecCmd = new MOTreeExecCommand(m_pPCH, ptrExec,
													m_pConnInfo->LastServerMessageID(), m_cServerID);
	enqueueExCommand(*pExecCmd);
}


void ResponseProcessor::processGet(Funambol::Get *cmd)
{
GDLDEBUG("Start ResponseProcessor::processGet");
	if (!cmd) return;

	GetPtr ptrGet((Funambol::Get*)cmd->clone());
	MOTreeGetCommand *pGetCmd = new MOTreeGetCommand(m_pPCH, ptrGet,
											 m_pConnInfo->LastServerMessageID(), m_cServerID);
	pGetCmd->SetMaxObjSize(m_pConnInfo->settings.MaxObjSize);
	enqueueExCommand(*pGetCmd);
GDLDEBUG("End ResponseProcessor::processGet");
}


void ResponseProcessor::processHeader()
{
	m_pConnInfo->SetLastServerMessageID(m_pSyncML->getSyncHdr()->getMsgID());
	Funambol::Meta *pMeta = m_pSyncML->getSyncHdr()->getMeta();
	if (pMeta)
	{
		m_pConnInfo->settings.MaxMsgSize = pMeta->getMaxMsgSize();
		m_pConnInfo->settings.MaxObjSize = pMeta->getMaxObjSize();
	}
}

void ResponseProcessor::processReplace(Funambol::Replace *cmd)
{
	if (!cmd) return;

	ReplacePtr ptrReplace((Funambol::Replace*)cmd->clone());
	ActionCommand *pReplaceCmd = new MOTreeReplaceCommand(m_pPCH, ptrReplace,
														  m_pConnInfo->LastServerMessageID(), m_cServerID);
	enqueueExCommand(*pReplaceCmd);
}


void ResponseProcessor::processResults(Funambol::Results *cmd)
{
	if (!cmd) return;
	m_pConnInfo->msgState.DecCommandsCount();
}


void ResponseProcessor::processSequence(Funambol::Sequence *cmd)
{
	if (!cmd) return;

	// increase number of commands to be processed
	if (cmd->getCommands())
		m_pConnInfo->msgState.IncCommandsCount(cmd->getCommands()->size());

	SequencePtr ptrSequence((Funambol::Sequence*)cmd->clone());
	ActionCommand *pSequenceCmd = new MOTreeSequenceCommand(m_pPCH, ptrSequence,
															m_pConnInfo->LastServerMessageID(), m_cServerID);
	enqueueExCommand(*pSequenceCmd);
}


void ResponseProcessor::processStatus(Funambol::Status *cmd)
{
	if (!cmd) return;

	if (!strcmp(cmd->getCmdRef(), "0") && !strcmp(cmd->getCmd(), "SyncHdr"))
	{
		// we got a status about authentication on server
		//bool authenticated = m_pSession->IsAuthenticationPassed(*cmd);
		const char *data = "";
		if (cmd->getData())
			data = cmd->getData()->getData();
		int code = atoi(data);

		GDLDEBUG("status on SyncHdr: %d", code);
		if (AUTHENTICATION_ACCEPTED == code || OK == code)
		{
//			m_pPCH->GetNotificationCenter()->SessionStarted(true);
//			m_pPCH->GetServerExchangeManager()->NotifySessionStarted();
//			m_serverExchange.NotifySessionStarted();
			m_pSession->SetClientAuthenticated(true);
			m_pConnInfo->SetAuthenticated(true); // nice
			m_pConnInfo->hasToResendLastCommands = false;

			if (cmd->getChal()) {
				m_pSession->ReadChal(*(cmd->getChal()));
			}
			
			// get resp uri from SyncBody
			const char * responseURI = m_pSyncML->getSyncHdr()->getRespURI();
			if (responseURI)
			{
				GDLDEBUG("response url: %s", responseURI);
				if (strlen(responseURI))
					m_pConnInfo->SetSessionURL(responseURI); // TODO move to processHeader

				GDLDEBUG("\tserver auth status processed");
			}
			else
			{
				GDLDEBUG("\tserver response has no RespURI set");
			}
			m_pConnInfo->msgState.DecCommandsCount();
		}
		else if (INVALID_CREDENTIALS == code || MISSING_CREDENTIALS == code)
		{
			m_pConnInfo->msgState.DecCommandsCount();
			m_pSession->SetClientAuthenticated(false);

			if (m_pSession->CredentialsSent() && !m_changeAuthType) {
				m_pConnInfo->hasToResendLastCommands = false;
				m_pConnInfo->connectionFinished = true;
				m_sessionFinished = true;
			}
			else if (cmd->getChal()) {
				m_pSession->ReadChal(*(cmd->getChal()));
				m_pConnInfo->hasToResendLastCommands = true;
				m_pConnInfo->repeatAuthentication = true;
				m_pConnInfo->connectionFinished = m_changeAuthType;
				m_sessionFinished = false;
			}
		}
		else
		{
			m_pConnInfo->SetAuthenticated(false);
			m_pConnInfo->msgState.DecCommandsCount();
			GDLDEBUG("\n\nServer refused authentication data\n\n");
		}
	}
	else
	{
		// parse responses from server on commands from client
		m_pConnInfo->msgState.DecCommandsCount();
		GDLDEBUG("for pending: %s, msg ref %s, cmd ref %s", cmd->getCmd(), cmd->getMsgRef(), cmd->getCmdRef());
		if (cmd->getData())
		{
			const char *data = cmd->getData()->getData();
			int value = atoi(data);
			if (e_ChunkedItemAcceptedAndBuffered == value) // server response on command is 213 - OK
				m_pConnInfo->hasToSendNextChunk = true;
		}
	}
}


// returned value:
// > 0  - error code is returned, on error happened during LO chunk processing
// = 0  - LO chunk successfully processed
// =-1  - command does not contain chunk, and should be processed with other routines
int ResponseProcessor::processLOChunk(Funambol::ItemizedCommand *cmd)
{
	int res = -1;
	if (m_pLOCollector && cmd && cmd->getItems() && cmd->getItems()->size() == 1)
	{
		GDLDEBUG("process next chunk, cmd - %s", cmd->getName());
		Funambol::Item *item = (Funambol::Item *)cmd->getItems()->get(0);
		if (item)
		{
			res = m_pLOCollector->AddChunk(*cmd);

			GDLDEBUG("status of chunk processing: %d, cmdsink: %d", res, m_pCommandsSink);
			if (res > 0)
			{
				if (e_ChunkedItemAcceptedAndBuffered == res)
				{
					if (m_pLOCollector->IsCompleted())
					{
						ItemizedCommandPtr ptrCmd = m_pLOCollector->GetResultCmd();
						GDLDEBUG("  process command with completed LO inside");
						// collector must be deleted before processing collected command
						SAFE_DELETE(m_pLOCollector);
						m_pConnInfo->SetWaitingForNextChunk(false);
						m_pConnInfo->SetLOMode(false);
						processCommand(ptrCmd.get());
					}
					else
					{
						SCommandPtr ptrStatus = SCommandFactory::CreateStatus((StatusCode)res, *cmd);
						GDLDEBUG("  LO chunk appended");
						if (m_pCommandsSink)//m_pConnection)
							m_pCommandsSink->AddCommand(ptrStatus, NULL);
						// generate Alert to server to get next chunk
						SAlertCommand *pAlert = new SAlertCommand(AC_LO_REQUEST_NEXT_CHUNK);
						if (m_pCommandsSink)//m_pConnection)
							m_pCommandsSink->AddCommand(SCommandPtr(pAlert), NULL);
						m_pConnInfo->SetWaitingForNextChunk(true);
					}
				}
				else
				{
					// on any error in collector - stop collecting LO
					GDLDEBUG("  appending chunk failed with result %d", res);
					SAFE_DELETE(m_pLOCollector);
					m_pConnInfo->SetLOMode(false);
					m_pConnInfo->SetWaitingForNextChunk(false);
					if (m_pCommandsSink)//m_pConnection)
						m_pCommandsSink->AddCommand(SCommandFactory::CreateStatus((StatusCode)res, *cmd), NULL);
				}
			}

			if (e_ChunkedItemAcceptedAndBuffered == res)
				return 0;
		}
	}
	return res;
}


void ResponseProcessor::initProcessing(bool sumode, int commandsCount)
{
GDLDEBUG("Start ResponseProcessor::initProcessing");
	SAFE_DELETE_ARR(m_cServerID);
	// if su mode, leave server id = NULL
	if (!sumode)
	{
		m_pConnInfo->msgState.SetCommandsCount(commandsCount);
		m_cServerID = Funambol::stringdup(m_pConnInfo->GetServerID());
	}
	m_responseCommandsCount = commandsCount;
GDLDEBUG("End ResponseProcessor::initProcessing");
}

