/*
 * 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/MessageFactory.h"
#include "serverexchange/LOResults.h"
#include "serverexchange/LOSendingStrategy.h"
#include "serverexchange/wrappers/SAlertCommand.h"
#include "serverexchange/wrappers/SCommandFactory.h"

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

static const char * c_LogName = "LOSendingStrategy";


LOSendingStrategy::LOSendingStrategy(ConnectionInfo &info, NS_SyncMLCommand::CommandStorage &cmds, ISession &session) :
	SendingStrategy(info, session), m_currentIndex(-1), m_pLOResults(NULL), m_commands(cmds)
{
	m_connInfo.hasLOSending = true;
}


LOSendingStrategy::~LOSendingStrategy()
{
	m_connInfo.hasLOSending = false;
}


bool LOSendingStrategy::Finished()
{
	GDLDEBUG("is finished (m_currentIndex: %d, m_commands.Size: %d)", m_currentIndex, m_commands.Size());
	return (m_currentIndex >= m_commands.Size()-1) && (m_pLOResults ? m_pLOResults->HasAllChunksCreated() : true);
}


SendingStrategy::Status LOSendingStrategy::Send()
{
	MessageFactory factory(m_connInfo);
	bool sendFirstLOChunk = false;
	int  bytes_left = m_connInfo.settings.MaxMsgSize - factory.MessageSizeWithoutBody();

	GDLDEBUG("has %d commands to send to server;\tMaxObjSizeData: %d\tbytes_left: %d",
		m_commands.Size(), m_connInfo.settings.MaxObjSize, bytes_left);

	factory.ResetCommands();
	
	SStatusCommand *status = m_session.CreateSyncHdrStatus(AUTHENTICATION_ACCEPTED, NULL);
	GDLDEBUG("Post status command to continue communication");
	factory.AddCommand(SCommandPtr(status));
	
	if (m_connInfo.NextChunkAlertCmdID() && strlen(m_connInfo.NextChunkAlertCmdID()))
	{
		SStatusCommandPtr ptrStatus = SCommandFactory::CreateStatus(e_Ok);
		Funambol::CmdID cmdid(m_connInfo.NextChunkAlertCmdID());
		ptrStatus->Prepare();
		ptrStatus->Internal()->setCmd(ALERT_COMMAND_NAME);
		ptrStatus->Internal()->setCmdID(&cmdid);
		ptrStatus->Internal()->setCmdRef(m_connInfo.NextChunkAlertCmdID());
		ptrStatus->Internal()->setMsgRef(m_connInfo.LastServerMessageID());
		factory.AddCommand(ptrStatus);
	}
	Funambol::StringBuffer *msg = factory.GetMessage(true);
	int msgsize = msg ? msg->length() : 0;
	SAFE_DELETE(msg);

	int cmdsize = 0;
	if (m_pLOResults)
	{
		// send next chunk from LO
		GDLDEBUG("LOResults->HasAllChunksCreated() %d", m_pLOResults->HasAllChunksCreated());

		int nextChunkEmptySize = m_pLOResults->GetNextChunkEmptySize();
		int sizeForData = m_connInfo.settings.MaxMsgSize - nextChunkEmptySize - msgsize;

		ResultsPtr ptrResult = m_pLOResults->GetNextChunk("", sizeForData);
		GDLDEBUG("next chunk to send [%x]", ptrResult.get());

		bool lastmsg = (m_currentIndex == m_commands.Size()-1) && m_pLOResults->HasAllChunksCreated();
		cmdsize = factory.AddCommand(ptrResult);
		bytes_left -= cmdsize;

		Funambol::StringBuffer *pSyncMLMessage = factory.GetMessage(lastmsg);
		if (pSyncMLMessage)
		{
			const char *xml = pSyncMLMessage->c_str();
			m_session.SendMessage(xml);
			SAFE_DELETE(pSyncMLMessage);
		}
		else
		{
			GDLDEBUG("failed to retrieve message from MessageFactory");
		}

		if (m_pLOResults->HasAllChunksCreated())
		{
			SAFE_DELETE(m_pLOResults);
			m_connInfo.hasLOSending = false;
		}
		return SendingSucceded;
	}

	m_currentIndex++;
	if (m_currentIndex >= 0 && m_currentIndex < m_commands.Size())
	{
		m_commands[m_currentIndex]->Prepare();

		if (canFitCommandInto(m_commands[m_currentIndex], msgsize))
		{
			GDLDEBUG("command '%s' will fit into the message", m_commands[m_currentIndex]->Internal()->getName());
			cmdsize = factory.AddCommand(m_commands[m_currentIndex]);
			bytes_left -= cmdsize;

			bool lastmsg = (m_currentIndex == m_commands.Size()-1);
			GDLDEBUG("last message of package: %s\t(m_currentIndex == m_commands.Size()-1):  [%d == %d]",
					 lastmsg ? "yes" : "no", m_currentIndex, m_commands.Size()-1);

			Funambol::StringBuffer *pSyncMLMessage = factory.GetMessage(lastmsg);
			if (pSyncMLMessage)
			{
				m_session.SendMessage(pSyncMLMessage->c_str());
				SAFE_DELETE(pSyncMLMessage);
			}
		}
		else
		{
			GDLDEBUG("command '%s' should be sent within several messages", m_commands[m_currentIndex]->Internal()->getName());
			SResultsCommand *pResultsCommand = dynamic_cast<SResultsCommand*>(m_commands[m_currentIndex].get());
			if (pResultsCommand)
			{
				ResultsPtr ptrResults = boost::dynamic_pointer_cast<Funambol::Results>(pResultsCommand->Internal());
				if (ptrResults.get())
				{
					GDLDEBUG("Received pointer to Results %x", ptrResults.get());
					m_pLOResults = new LOResults(ptrResults, bytes_left, m_connInfo.settings.MaxObjSize);
					sendFirstLOChunk = true;
					m_connInfo.hasLOSending = true;
					return NeedToSendLO;
				}
				else
				{
					GDLERROR("Failed to get internal Results object");
				}
			}
			else
			{
				GDLERROR("Unexpected type of command to be sent in LO mode"); // not a results with a LO - what it is ?
			}
		}
	}

	return SendingSucceded;
}


bool LOSendingStrategy::canFitCommandInto(NS_SyncMLCommand::SCommandPtr ptrCmd, int emptyMsgSize)
{
	if (!ptrCmd.get() || !ptrCmd->Internal().get() || !ptrCmd->Internal()->getItems())
	{
		return true;
	}

	GDLDEBUG("canFitCommandInto %d [MMS %d]", emptyMsgSize, m_connInfo.settings.MaxObjSize);

	int bytes_left = m_connInfo.settings.MaxMsgSize - emptyMsgSize;
	Funambol::ArrayList *items = ptrCmd->Internal()->getItems();
	for (int i=0; i<items->size(); ++i)
	{
		Funambol::Item *item = (Funambol::Item *)items->get(i);
		if (item && item->getData())
		{
			const char *pData = item->getData()->getData();
			int data_size = pData ? strlen(pData) : 0;

			GDLDEBUG("item #%d has data [%x] with size - %d", i, pData, data_size);
			GDLDEBUG("\tstrlen(pData)+200 > bytes_left = (%d > %d)", data_size+400, bytes_left);
			if (pData && ((data_size > (int)m_connInfo.settings.MaxObjSize) || (data_size+400 > bytes_left)))
			{
				GDLDEBUG("Check failed on item: %s [%d bytes]", ptrCmd->Internal()->getName(), data_size);
				return false; // found item with data larger than MaxObjSize param
			}
		}
	}
	return true;
}
