/*
 * 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 <cstdio>
#include <syncml/core/Exec.h>
#include <syncml/core/Item.h>
#include <syncml/core/Meta.h>
#include <syncml/core/Source.h>
#include <syncml/core/Target.h>

#include "commontypes.h"
#include "downloader.h"
#include "Errors.h"
#include "identifiers.h"
#include "Utils.h"
#include "serverexchange/commands/TriggerDRMDCollectingCommand.h"
#include "executionqueue/IExecutionQueue.h"
#include "Logger/LoggerMacroses.h"
#include "treemanager/MOTreeExecCommand.h"
#include "treemanager/IMOTreeManager.h"
#include "serverexchange/firmware/FirmwareManager.h"
#include "serverexchange/IServerExchangeManager.h"

const char* const c_LogName = "MOTreeExecCommand";

#define LOG_INFO_ NS_Logging::FunctionNameLogHelper(__FUNCTION__, NS_Logging::e_info).Log

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

typedef StatusCode (MOTreeExecCommand::*ExecMethod)(const char *, const char *);
typedef Funambol::StringBuffer FStringBuffer;

const char *action_name[3]  = { "Download", "DownloadAndUpdate", "Update",  };
const int   action_len[3]   = { strlen(action_name[0]), strlen(action_name[1]), strlen(action_name[2]) };
ExecMethod  actions[3]      = { &MOTreeExecCommand::executeDownload,
                                &MOTreeExecCommand::executeDU,
                                &MOTreeExecCommand::executeUpdate };

MOTreeExecCommand::MOTreeExecCommand(ProfileComponentsHolder* prholder,
                                     ExecPtr& cmd,
                                     const String& msgID,
                                     const char* serverId)
    : MOTreeItemisedCommand(prholder, cmd, msgID, serverId), m_ExecCommand(cmd)
{
    if(cmd.get() == NULL)
    {
        GDLWARN("cmd is NULL");
    }
}


bool MOTreeExecCommand::Execute()
{
    GDLINFO("FUNCTION STARTED");

    bool res = m_pProfile && m_ExecCommand.get();
    if (res)
    {
        res = false;
        m_resCode = performOperation();
        res = InvokeResult();
    }
    else
    {
        GDLERROR("component holder or command instanse not valid");
    }

    GDLINFO("FUNCTION END. Res: %d", res);
    return res;
}


StatusCode MOTreeExecCommand::performOperation()
{
    StatusCode res = e_CommandFailed;
    Funambol::ArrayList* items = m_ExecCommand->getItems();
    if (items)
    {
        IMOTreeManager* treeManager = m_pProfile ? m_pProfile->GetMOTreeManager() : 0;
        if (!treeManager)
        {
            GDLERROR("can't get tree manager instance");
            return e_Failed;
        }

        if (notifyProvisionUpdate(e_Exec, e_sessionStart, e_Ok))
        {
            res = processExecOnMultipleItems(items, treeManager);
        }
        else
        {
            GDLERROR("NotifyProvisionUpdate failed");
            res = e_Failed;
        }

        if (!notifyProvisionUpdate(e_Exec, e_sessionEnd, res))
        {
            GDLERROR("NotifyProvisionUpdate failed");
        }
    }
    else
    {
        GDLERROR("get items from ItemizedCommand failed");
    }
    return res;
}


StatusCode MOTreeExecCommand::executeItem(Funambol::Item &item)
{
    StatusCode res = e_CommandFailed;
    char *fumoURI = NULL;

    do
    {
        if (item.getTarget())
        {
            const char * targetURI = item.getTarget()->getLocURI();

            if (!targetURI)
                break;

            if (!(e_Ok == (res = m_pProfile->GetMOTreeManager()->ExecPermitted(targetURI, m_serverID))))
                break;

            if (!strcmp(targetURI, URI_DIAGNOSTICS_START))
            {
                res = executeDiagnostics();
                break;
            }

            // get parent of exec uri - if it is FUMO node - perform FUMO exec
            const char *pos = targetURI + strlen(targetURI)-1;
            {
                while (*pos == '/' && pos >= targetURI) --pos;
                while (*pos != '/' && pos >= targetURI) --pos;
                if (pos > targetURI)
                {
                    fumoURI = new char[pos - targetURI + 1];
                    if(fumoURI == NULL)
                    {
                        GDLWARN("new char");
                    }
                    memset(fumoURI, '\0', pos - targetURI + 1);
                    __strncpy(fumoURI, targetURI, pos - targetURI);
                    GDLDEBUG("FUMO node uri: %s", fumoURI);
                }
            }

            if (!fumoURI)
            {
                GDLDEBUG("Undefined operation Exec on node %s", targetURI);
                return e_OptionalFeatureNotSupported;
            }

            String type;
            if (e_Ok == (res = m_pProfile->GetMOTreeManager()->GetPropertyValue(fumoURI, "Type", type, NULL)))
            {
                if (strcmp(NODE_TYPE_FUMO, type.c_str()))
                {
                    GDLERROR("Calling Exec on node with no FUMO identifier");
                    res = e_CommandFailed;
                    break;
                }
            }
            else
            {
                GDLERROR("get item property from MO Tree Manager failed. Res=%d", res);
                break;
            }

            const int targetURIlen = strlen(targetURI);
            for (int i=0; i<3; ++i)
            {
                if (targetURIlen > (action_len[i]+1))
                {
                    const char * targetsh = targetURI+targetURIlen-action_len[i];
                    GDLDEBUG("\tcheck target: %s", targetsh);
                    // check which exec should be called - download, update or d&u
                    if (!strcmp(action_name[i], targetsh))
                    {
                        res = (this->*actions[i])(fumoURI, targetURI);
                        break;
                    }
                }
            }
        }
    } while(0);
    SAFE_DELETE_ARR(fumoURI);
    GDLDEBUG("Execute on item finished");

    return res;
}


// @param fwmouri   path to <X> - Firmware MO
// @param fwmoexuri path to <X>/Download
StatusCode MOTreeExecCommand::executeDownload(const char * fumouri, const char * fwmoexuri)
{
    StatusCode res = e_CommandFailed;
    FStringBuffer pkgUrl = fwmoexuri;
    pkgUrl.append("/PkgURL"); // <X>/Download/PkgURL

    GDLDEBUG("download fw from url from uri >%s<", pkgUrl.c_str());
    String value;
    if (e_Ok == m_pProfile->GetMOTreeManager()->GetValue(pkgUrl.c_str(), value, NULL)) // perform in root mode
    {
        GDLDEBUG("\turl %s", value.c_str());

        FirmwareManager *pManager = m_pProfile->GetServerExchangeManager()->GetFirmwareManager();
        pManager->SetExecCmd(m_ExecCommand);
        pManager->SetFWURL(value.c_str());
        pManager->SetServerID(m_serverID);
        pManager->PerformDownload(fumouri);

        res = e_AcceptForProcessing;
    }
    return res;
}


// @param fwmouri   path to <X> - Firmware MO
// @param fwmoexuri path to <X>/DownloadAndUpdate
StatusCode MOTreeExecCommand::executeDU(const char * fumouri, const char * fwmoexuri)
{
    StatusCode res = e_CommandFailed;
    FStringBuffer pkgUrl = fwmoexuri;
    pkgUrl.append("/PkgURL"); // <X>/DownloadAndUpdate/PkgURL

    GDLDEBUG("download and update fw from url >%s<", pkgUrl.c_str());
    String value;
    if (e_Ok == m_pProfile->GetMOTreeManager()->GetValue(pkgUrl.c_str(), value, NULL)) // perform in root mode
    {
        FirmwareManager *pManager = m_pProfile->GetServerExchangeManager()->GetFirmwareManager();
        pManager->SetExecCmd(m_ExecCommand);
        pManager->SetFWURL(value.c_str());
        pManager->SetServerID(m_serverID);
        pManager->PerformDownloadAndUpdate(fumouri);

        res = e_AcceptForProcessing;
    }
    return res;
}


// @param fwmouri   path to <X> - Firmware MO
// @param fwmoexuri path to <X>/Update
StatusCode MOTreeExecCommand::executeUpdate(const char * fumouri, const char * fwmoexuri)
{
    FStringBuffer pkgData = fwmoexuri;
    pkgData.append("/PkgData"); // <X>/Update/PkgData

    GDLDEBUG("update fw from uri >%s<", pkgData.c_str());
    String value;

    FirmwareManager *pManager = m_pProfile->GetServerExchangeManager()->GetFirmwareManager();

    if (e_Ok == m_pProfile->GetMOTreeManager()->GetValue(pkgData.c_str(), value, NULL) && value.size())
    {
        pManager->SetFWDataURI(pkgData.c_str());
    }
    pManager->SetExecCmd(m_ExecCommand);
    pManager->SetServerID(m_serverID);
    pManager->PerformUpdate(fumouri);

    return e_AcceptForProcessing;
}


StatusCode MOTreeExecCommand::executeDiagnostics()
{
    GDLDEBUG("Exec diagnostics");
    int duration;

    String valueDuration;
    if (e_Ok == m_pProfile->GetMOTreeManager()->GetValue(URI_DIAGNOSTICS_DURATION, valueDuration, NULL))
    {
        duration = atoi(valueDuration.c_str());
        if (0 == duration || 3 == duration || 4 == duration)
        {
            GDLERROR("operation canceled: invalid duration value - %d", duration);
            return e_OperationCancelled;
        }
    }
    else
    {
        GDLERROR("Diagnostic's Duration property is not set");
        return e_CommandFailed;
    }

    String valueServerid;
    if (e_Ok == m_pProfile->GetMOTreeManager()->GetValue(URI_DIAGNOSTICS_SERVERID, valueServerid, NULL))
    {
        // check if there is an acc record for server_id //ask SEM
        if (!m_pProfile->GetServerExchangeManager()->HasAccountFor(valueServerid))
        {
            GDLERROR("Received 'exec diagnostics' but no DRMD server acc found in the tree");
            return e_CommandFailed;
        }
    }
    else
    {
        GDLERROR("Diagnostic's Server_ID property is not set");
        return e_CommandFailed;
    }

    String valueOriginationid;
    if (e_Ok != m_pProfile->GetMOTreeManager()->GetValue(URI_DIAGNOSTICS_ORIGINATIONID, valueOriginationid, NULL))
    {
        GDLERROR("Diagnostic's Origination_ID property is not set");
        return e_CommandFailed;
    }

    ICommand *cmd = new(std::nothrow) TriggerDRMDCollectingCommand(m_pProfile, duration);
    if(cmd == (ICommand*)NULL)
    {
        GDLWARN("new TriggerDRMDCollectingCommand")
    }

    if (!m_pProfile->GetExecutionQueue()->Add(*cmd))
    {
        GDLERROR("Failed to add command %p to Q", cmd);
        if (cmd)
        {
            delete cmd;
        }

        return e_CommandFailed;
    }

    return e_AcceptForProcessing;
}

StatusCode MOTreeExecCommand::processExecOnMultipleItems(Funambol::ArrayList* items, IMOTreeManager* treeManager)
{
    StatusCode res = e_Ok;

    for (int i=0; i<items->size(); ++i)
    {
        Funambol::Item *item = (Funambol::Item*)(*items)[i];
        if (item)
        {
            res = executeItem(*item);
        }
    }

    return res;
}

Funambol::ArrayList* MOTreeExecCommand::getListOfItems()
{
    return m_ExecCommand->getItems();
}
