/*
 * 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 <syncml/core/AbstractCommand.h>
#include <syncml/core/Atomic.h>
#include <syncml/core/Item.h>
#include <syncml/core/Source.h>

#include "commontypes.h"
#include "executionqueue/IExecutionQueue.h"
#include "Logger/LoggerMacroses.h"
#include "treemanager/IMOTreeManager.h"
#include "treemanager/AlertCommand.h"
#include "treemanager/MOTreeAddCommand.h"
#include "treemanager/MOTreeAtomicCommand.h"
#include "treemanager/MOTreeCopyCommand.h"
#include "treemanager/MOTreeDeleteCommand.h"
#include "treemanager/MOTreeReplaceCommand.h"
#include "treemanager/MOTreeExecCommand.h"

#include "serverexchange/IServerExchangeManager.h"
#include "serverexchange/wrappers/SCommandFactory.h"
#include <Logger/LoggerMacroses.h>

const char* const c_LogName = "MOTreeAtomicCommand";

using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_SyncMLCommand;

//const char* const c_LogName = "MOTreeAtomicCommand";

MOTreeAtomicCommand::MOTreeAtomicCommand(ProfileComponentsHolder* prholder,
                                         AtomicPtr &cmd,
                                         const String& msgID,
                                         const char* serverId) :
    ActionCommand(prholder, cmd, msgID, serverId), m_AtomicCommand(cmd), m_commandItems(0)
{
    if(cmd.get() == NULL)
    {
        GDLWARN("cmd is NULL");
    }
}

MOTreeAtomicCommand::~MOTreeAtomicCommand()
{
    if (m_commandItems)
    {
        delete m_commandItems;
    }
}

bool MOTreeAtomicCommand::Execute()
{
    if (!m_pProfile)
    {
        GDLERROR("component holder instanse not valid");
        return false;
    }

    if (!m_AtomicCommand.get())
    {
        GDLERROR("command instanse not valid");
        return false;
    }

    Funambol::ArrayList *cmds = m_AtomicCommand.get()->getCommands();
    if (!cmds || cmds->isEmpty())
    {
        m_resCode = e_Ok;
    }
    else
    {
        m_pProfile->GetMOTreeManager()->StartTransaction();

        if (!notifyProvisionUpdate(e_Atomic, e_sessionStart, e_Ok))
        {
            GDLWARN("failed to notify provision update at start");
        }

        m_resCode = processCommands(*cmds);

        if (!notifyProvisionUpdate(e_Atomic, e_sessionStart, m_resCode))
        {
            GDLWARN("failed to notify provision update at start");
        }
    }

    InvokeResult();
    postCommandsResults(*cmds);

    return true;
}


// implementation of IResultCollector interface
// MOTreeAtomicCommand collects execution results of its inner commands
void MOTreeAtomicCommand::PutResult(Funambol::AbstractCommand * pCommand, StatusCode result)
{
    if (!pCommand) return;

    GDLDEBUG("Put Result: %x %d", pCommand, result);

    m_commandsResults.push_back(SCommandFactory::CreateStatus(result, *pCommand));
}


ActionCommand * MOTreeAtomicCommand::createProcessingCommand(Funambol::AbstractCommand &cmd)
{
    const char *name = cmd.getName();
    if (name && !strcmp(name, ADD_COMMAND_NAME))
    {
        AddPtr ptrAdd((Funambol::Add*)cmd.clone());
        if(ptrAdd.get() == NULL)
        {
            GDLWARN("ptrAdd is NULL");
        }
        return new(std::nothrow) MOTreeAddCommand(m_pProfile, ptrAdd, m_messageID, m_serverID);
    }
    else if (name && !strcmp(name, ALERT_COMMAND_NAME))
    {
        AlertPtr ptrAlert((Funambol::Alert*)cmd.clone());
        if(ptrAlert.get() == NULL)
        {
            GDLWARN("ptrAlert is NULL");
        }
        return new(std::nothrow) AlertCommand(m_pProfile, ptrAlert, m_messageID, m_serverID);
    }
    else if (name && !strcmp(name, COPY_COMMAND_NAME))
    {
        CopyPtr ptrCopy((Funambol::Copy*)cmd.clone());
        if(ptrCopy.get() == NULL)
        {
            GDLWARN("ptrCopy is NULL");
        }
        return new(std::nothrow) MOTreeCopyCommand(m_pProfile, ptrCopy, m_messageID, m_serverID);
    }
    else if (name && !strcmp(name, DELETE_COMMAND_NAME))
    {
        DeletePtr ptrDelete((Funambol::Delete*)cmd.clone());
        if(ptrDelete.get() == NULL)
        {
            GDLWARN("ptrDelete is NULL");
        }
        return new(std::nothrow) MOTreeDeleteCommand(m_pProfile, ptrDelete, m_messageID, m_serverID);
    }
    else if (name && !strcmp(name, REPLACE_COMMAND_NAME))
    {
        ReplacePtr ptrReplace((Funambol::Replace*)cmd.clone());
        if(ptrReplace.get() == NULL)
        {
            GDLWARN("ptrReplace is NULL");
        }
        return new(std::nothrow) MOTreeReplaceCommand(m_pProfile, ptrReplace, m_messageID, m_serverID);
    }
    return NULL;
}


// MOTreeAtomicCommand executes inner commands within itself, without sending them
// to the ExecutionQueue (it is expected that MOTreeAtomicCommand is already placed into the ExecutionQueue)
StatusCode MOTreeAtomicCommand::processCommands(Funambol::ArrayList &clist)
{
    StatusCode res = e_Ok;
    Funambol::AbstractCommand* cmd = NULL;
    uint count = clist.size();
    GDLDEBUG("Atomic command has %d nested commands", count);
    for (uint i=0; i<count; ++i)
    {
        if ((cmd = static_cast<Funambol::AbstractCommand*>(clist[i])))
        {
            ActionCommand *pActionCmd = createProcessingCommand(*cmd);
            GDLDEBUG("Created ActionCommand %x for %s", pActionCmd, cmd->getName());

            if (pActionCmd)
            {
                pActionCmd->SetResultsCollector(this);
                pActionCmd->Execute();
                StatusCode cmdres = pActionCmd->GetResult();
                delete pActionCmd;

                GDLDEBUG("Command %s executed with result %d", cmd->getName(), cmdres);
                if (e_Ok != cmdres)
                {
                    res = e_CommandFailed;
                    break;
                }
            }
            else
            {
                GDLERROR("Command not created");
                res = e_CommandFailed;
                m_pProfile->GetMOTreeManager()->Rollback();
                break;
            }
        }
        else
        {
            GDLERROR("casting to Funambol::AbstractCommand failed");
            res = e_CommandFailed;//e_AtomicFailed;
            m_pProfile->GetMOTreeManager()->Rollback();
        }
    }

    if (e_Ok == res)
        m_pProfile->GetMOTreeManager()->Commit();

    return res;
}


// MOTreeAtomicCommand collects results of it inner commands and post them by its own
// this is made to provide possibility to correct successful results of first commands
// in case of failure of following commands
void MOTreeAtomicCommand::postCommandsResults(Funambol::ArrayList &atomicCommands)
{
    SCommandsArray results;

    if (e_Ok == m_resCode)
    {
        for (uint i=0; i<m_commandsResults.size(); ++i)
            results.push_back(m_commandsResults[i]);
    }
    else
    {
        for (int i=0; i<atomicCommands.size(); ++i)
        {
            StatusCode errCode = e_CommandFailed;
            Funambol::AbstractCommand *pCmd = (Funambol::AbstractCommand *)atomicCommands[i];
            if (pCmd && pCmd->getCmdID() && pCmd->getCmdID()->getCmdID())
            {
                if (findResultForCommand(pCmd->getCmdID()->getCmdID()))
                {
                    errCode = e_AtomicRollbackOK;
                }
            }

            SCommandPtr status_msg = SCommandFactory::CreateStatus(errCode, *pCmd);
            if(status_msg.get() == NULL)
            {
                GDLWARN("failed to CreateStatus");
            }

            results.push_back(status_msg);
        }
    }

    if (results.size())
        m_pProfile->GetServerExchangeManager()->AddCommands(results, m_serverID);
}


NS_SyncMLCommand::SCommandPtr MOTreeAtomicCommand::findResultForCommand(const char *cmdID)
{
    for (uint i=0; i<m_commandsResults.size(); ++i)
    {
        if (!strcmp(cmdID, m_commandsResults[i].get()->GetCmdRef()))
            return m_commandsResults[i];
    }
    return NS_SyncMLCommand::SCommandPtr();
}

Funambol::ArrayList* MOTreeAtomicCommand::getListOfItems()
{
    if (m_commandItems == 0)
    {
        Funambol::Atomic* atomic = m_AtomicCommand.get();
        if (atomic)
        {
            Funambol::ArrayList* cmds = atomic->getCommands();
            if (cmds)
            {
                Funambol::AbstractCommand* cmd = NULL;
                Funambol::ArrayList* conmmand_items;
                ActionCommand *tree_cmd = 0;
                size_t commands_count = cmds->size();

                bool res = true;

                for (size_t i = 0; i < commands_count, res; ++i)
                {
                    if ((cmd = static_cast<Funambol::AbstractCommand*>((*cmds)[i])))
                    {
                        if (tree_cmd = createProcessingCommand(*cmd))
                        {
                            if (conmmand_items = tree_cmd->getListOfItems())
                            {
                                if (m_commandItems == 0)
                                {
                                    m_commandItems = new Funambol::ArrayList;
                                }
                                if (m_commandItems)
                                {
                                    if (m_commandItems->add(conmmand_items) == -1)
                                    {
                                        GDLERROR("Failed to add items to items list");
                                        res = false;
                                    }
                                }
                            }
                            else
                            {
                                GDLERROR("Failed to get list of items from command");
                                res = false;
                            }

                            delete tree_cmd;
                        }
                        else
                        {
                            GDLERROR("Failed to create processing command");
                            res = false;
                        }
                    }
                    else
                    {
                        GDLERROR("Failed to cast to Funambol::AbstractCommand");
                        res = false;
                    }
                }

                if (!res)
                {
                    delete m_commandItems;
                    m_commandItems = 0;
                }
            }
            else
            {
                GDLERROR("Failed to get list of commands from sequence command");
            }

        }
        else
        {
            GDLERROR("Failed to get atomic command");
        }
    }

    return m_commandItems;
}

