/*
 * Copyright 2009 Funambol, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $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.state.hasLOSending = true;
}


LOSendingStrategy::~LOSendingStrategy()
{
    m_connInfo.state.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);
        if(ptrStatus.get() == NULL)
        {
            GDLWARN("ptrStatus is NULL");
        }

        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);
        if(ptrResult.get() == NULL)
        {
            GDLWARN("ptrResult is NULL");
        }
        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)
        {
            m_session.SendMessage(*pSyncMLMessage);
            SAFE_DELETE(pSyncMLMessage);
        }
        else
        {
            GDLDEBUG("failed to retrieve message from MessageFactory");
        }

        if (m_pLOResults->HasAllChunksCreated())
        {
            SAFE_DELETE(m_pLOResults);
            m_connInfo.state.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);
                SAFE_DELETE(pSyncMLMessage);
            }
        }
        else
        {
            GDLDEBUG("command '%s' should be sent within several messages", m_commands[m_currentIndex]->Internal()->getName());
            if (!strcmp(m_commands[m_currentIndex]->Internal()->getName(), RESULTS_COMMAND_NAME))
            {
                SResultsCommand *pResultsCommand = static_cast<SResultsCommand*>(m_commands[m_currentIndex].get());
                ResultsPtr ptrResults = boost::static_pointer_cast<Funambol::Results>(pResultsCommand->Internal());
                if (ptrResults.get() != NULL)
                {
                    GDLDEBUG("Received pointer to Results %x", ptrResults.get());
                    m_pLOResults = new(std::nothrow) LOResults(ptrResults, bytes_left, m_connInfo.settings.MaxObjSize);
                    if(m_pLOResults == NULL) GDLWARN("new LOResults");

                    sendFirstLOChunk = true;
                    m_connInfo.state.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() == NULL)
    {
        GDLWARN("ptrCmd is NULL");
    }

    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;
}
