/*
 * 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 <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 "DeviceAdapter/Diagnostics/CollectDiagnosticsCommand.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"

#define URI_DIAGNOSTICS_START         "./WiMAX_Diagnostics/Start"
#define URI_DIAGNOSTICS_DURATION      "./WiMAX_Diagnostics/Start/Duration"
#define URI_DIAGNOSTICS_SERVERID      "./WiMAX_Diagnostics/Start/Server_ID"
#define URI_DIAGNOSTICS_ORIGINATIONID "./WiMAX_Diagnostics/Start/Origination_ID"

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_DM_Diagnostics;
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)
    : ActionCommand(prholder, cmd, msgID, serverId), m_ExecCommand(cmd)
{
}


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)
    {
		for (int i=0; i<items->size(); ++i)
		{
			Funambol::Item *item = (Funambol::Item*)(*items)[i];
			if (item)
				res = executeItem(*item);
		}
    }
    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];
					__strncpy(fumoURI, targetURI, pos - targetURI);
					fumoURI[pos - targetURI] = 0;
					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(FUMO_TYPE, 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->SetFUMOURI(fumouri);
		pManager->SetFWURL(value.c_str());
		pManager->SetResponseType(RESPONSE_FOR_DOWNLOAD);
		pManager->SetServerID(m_serverID);
		pManager->PerformDownload();

		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->SetFUMOURI(fumouri);
		pManager->SetFWURL(value.c_str());
		pManager->SetResponseType(RESPONSE_FOR_DU);
		pManager->SetServerID(m_serverID);
		pManager->PerformDownloadAndUpdate();

		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->SetExecCmd(m_ExecCommand);
		pManager->SetFUMOURI(fumouri);
		pManager->SetFWDataURI(pkgData.c_str());
		pManager->SetResponseType(RESPONSE_FOR_UPDATE);
		pManager->SetServerID(m_serverID);
		pManager->PerformUpdate();
		// executeUpdate in FWM
	}
	else
	{
		pManager->PerformUpdate();
	}

	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 TriggerDRMDCollectingCommand(m_pProfile, duration);
//	ICommand *cmd = new CollectDiagnosticsCommand(m_pProfile, duration, valueServerid, valueOriginationid);
	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;
}
