/*
 * 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 <base/base64.h>
#include <syncml/core/Exec.h>
#include <Utils.h>
#include "daemon/INotificationCenter.h"
#include "serverexchange/wrappers/SAlertCommand.h"
#include "serverexchange/wrappers/SCommandFactory.h"
#include "serverexchange/firmware/FirmwareManager.h"
#include "serverexchange/IServerExchangeManager.h"
#include "treemanager/IMOTreeManager.h"
#include "Logger/LoggerMacroses.h"

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_DataStorage;
using namespace NS_DM_Client::NS_SyncMLCommand;

static const char * c_LogName = "FWManager";


FirmwareManager::FirmwareManager(ProfileComponentsHolder* prholder) :
	m_cancelled(false),
	m_storageInitialized(false),
	m_fumouri(NULL),
	m_fwDataURI(NULL),
	m_fwFileName(NULL),
	m_responseType(NULL),
	m_serverID(NULL),
	m_url(NULL),
	m_fwCommandStatus(FW_UNDEFINED_ERROR),
	m_fwStatus(IFirmwareUpdater::e_OK),
	m_operation(FWO_UNDEFINED),
	m_pDDProcessor(NULL),
	m_pMessenger(NULL),
	m_pPCH(prholder)
{
	terminate = false;
	start();
}


FirmwareManager::~FirmwareManager()
{
	softTerminate();
	m_event.signal();
	wait();
	m_pPCH->GetDeviceAdapter()->ResetFirmwareStorage();
	
	SAFE_DELETE_ARR(m_fumouri);
	SAFE_DELETE_ARR(m_fwDataURI);
	SAFE_DELETE_ARR(m_fwFileName);
	SAFE_DELETE_ARR(m_responseType);
	SAFE_DELETE_ARR(m_serverID);
	SAFE_DELETE_ARR(m_url);
	SAFE_DELETE(m_pDDProcessor);
	SAFE_DELETE(m_pMessenger);
	
	m_pPCH = NULL;
}


Downloader::DownloadStatus FirmwareManager::Download()
{
	return DownloadAndUpdate(false);
}


Downloader::DownloadStatus FirmwareManager::DownloadAndUpdate(bool update)
{
	SAFE_DELETE(m_pDDProcessor);
	m_fwCommandStatus = FW_UNDEFINED_ERROR;
	Downloader::DownloadStatus dstatus = Downloader::DS_FAILED;
	Downloader *downloader = DownloaderFactory::CreateDownloader(m_url);
	
	if (!downloader)
	{
		setFUMOStatus(FS_DOWNLOAD_FAILED);
		GDLERROR("failed to create downloader");
	}
	else
	{
		downloader->SetDownloader(this);

		SAFE_DELETE(m_pMessenger);
		m_pMessenger = CreateMessenger();
		
		setFUMOStatus(FS_DOWNLOAD_PROGRESSING);
		dstatus = downloader->Download();
		GDLDEBUG("fw download finished, downloader transport status: %d", downloader->GetLastError());
		GDLDEBUG("fw downloader status: %d", dstatus);

		if (DS_FINISHED != dstatus)
		{
			m_pPCH->GetDeviceAdapter()->ResetFirmwareStorage();
			if (DS_FAILED == dstatus && m_cancelled)
				m_fwCommandStatus = FW_USER_CANCELLED;
			else if (DS_MALFORMED_URL == dstatus)
				m_fwCommandStatus = FW_MALFORMED_URL;
			else
				m_fwCommandStatus = FW_ALT_DOWNLOAD_SERVER_ERROR;
			setFUMOStatus(FS_DOWNLOAD_FAILED);
		}
		else
		{
			if (m_pDDProcessor) // if DDProcessor was created - downloaded file was DD xml
				return dstatus;

			if (NS_DM_Client::IFirmwareUpdater::e_OK == m_fwStatus)
			{
				dstatus = Downloader::DS_FINISHED;
				// close storage
				m_fwStatus = m_pPCH->GetDeviceAdapter()->AppendFirmwareChunk(NULL, 0, true);
				m_storageInitialized = false;
				GDLDEBUG("DeviceAdapter storage closed with: %d", m_fwStatus);
				if (update)
				{
					setFUMOStatus(FS_DOWNLOAD_COMPLETE);
					setFUMOStatus(doUpdate());
				}
				else
				{
					setFUMOStatus(FS_READY_TO_UPDATE);
					m_fwCommandStatus = FW_SUCCESSFUL;
				}
			}
			else
			{
				// error while download
				GDLDEBUG("reset fw storage");
				m_fwStatus = m_pPCH->GetDeviceAdapter()->ResetFirmwareStorage();
				setFUMOStatus(FS_DOWNLOAD_FAILED);
				m_fwCommandStatus = FW_ALT_DOWNLOAD_SERVER_ERROR;
			}
		}
		SAFE_DELETE(downloader);
	}

	return dstatus;
}


void FirmwareManager::PerformDownload()
{
	m_operation = FWO_DOWNLOAD;
	m_event.signal();
}


void FirmwareManager::PerformDownloadAndUpdate()
{
	m_operation = FWO_DOWNLOAD_AND_UPDATE;
	m_event.signal();
}


void FirmwareManager::PerformUpdate()
{
	m_operation = FWO_UPDATE;
	m_event.signal();
}


void FirmwareManager::SetExecCmd(ExecPtr cmd)
{
	m_ExecCommand = cmd;
}


void FirmwareManager::SetFUMOURI(const char *uri)
{
	SAFE_DELETE_ARR(m_fumouri);
	m_fumouri = Funambol::stringdup(uri);
}


void FirmwareManager::SetFWDataURI(const char *uri)
{
	SAFE_DELETE_ARR(m_fwDataURI);
	m_fwDataURI = Funambol::stringdup(uri);
}


void FirmwareManager::SetFWURL(const char *url)
{
	SAFE_DELETE_ARR(m_url);
	m_url = Funambol::stringdup(url);
}


void FirmwareManager::SetResponseType(const char * resptype)
{
	SAFE_DELETE_ARR(m_responseType);
	m_responseType = Funambol::stringdup(resptype);
}


void FirmwareManager::SetServerID(const char * serverID)
{
	SAFE_DELETE_ARR(m_serverID);
	m_serverID = Funambol::stringdup(serverID);
}


void FirmwareManager::run()
{
	GDLDEBUG("start FirmwareManager");
	while(!terminate)
	{
		GDLDEBUG("requested operation: %d", m_operation);
		if (FWO_UNDEFINED != m_operation)
		{
			enum INotification::FirmwareUpdate::EnumFirmwareOperation fwOperation = INotification::FirmwareUpdate::e_Error;
			switch (m_operation)
			{
			case FWO_DOWNLOAD:
				fwOperation = INotification::FirmwareUpdate::e_Download;
				break;

			case FWO_DOWNLOAD_AND_UPDATE:
				fwOperation = INotification::FirmwareUpdate::e_DownloadAndUpdate;
				break;

			case FWO_UPDATE:
				fwOperation = INotification::FirmwareUpdate::e_Update;
				break;
			}
			
			m_pPCH->GetNotificationCenter()->FirmwareUpdateStart(fwOperation);
			if (FWO_DOWNLOAD == m_operation || FWO_DOWNLOAD_AND_UPDATE == m_operation)
			{
				bool update = (FWO_DOWNLOAD_AND_UPDATE == m_operation);
				GDLDEBUG("download firmware from url %s, %s perform update", m_url, update ? "" : "do not");
				if (DS_FINISHED == DownloadAndUpdate(update) && m_pDDProcessor)
				{
					m_pDDProcessor->Parse();
					if (m_pDDProcessor->IsOctet() && !m_pDDProcessor->objectURI.empty())
					{
						SetFWURL(m_pDDProcessor->objectURI.c_str());
						m_properties.clear();
						DownloadAndUpdate(update);
					}
					else
					{
						GDLERROR("downloaded dd xml points to unsupported object [isOctet='%s', objectURI='%s']", 
								 m_pDDProcessor->IsOctet(), m_pDDProcessor->objectURI.c_str());
						m_fwCommandStatus = FW_UNDEFINED_ERROR;
						setFUMOStatus(FS_DOWNLOAD_FAILED);
					}
				}
			}
			else if (FWO_UPDATE == m_operation)
			{
				if (m_fwDataURI)
				{
					GDLDEBUG("update fw from uri %s", m_fumouri);
					setFUMOStatus(writeFWData());
				}
				else
				{
					GDLERROR("failed to update firmware: fw data URI is not set");
					setFUMOStatus(FS_UPDATE_FAILED_NO_DATA);
				}
			}
			m_pPCH->GetNotificationCenter()->FirmwareUpdateFinished(fwOperation);
			sendResultAlert();
		}

		m_event.wait();
	}
	GDLDEBUG("exit FirmwareManager");
}


// Downloader implementation - callback to store data from specific downloader instance
// Returned value indicates number of stored bytes
// Returned 0 will stop file downloading (specific curl behaviour)
int FirmwareManager::storeBuffer(void *buffer, uint size)
{
	GDLDEBUG("storeBuffer: %d bytes", size);
	
	Properties::iterator it = m_properties.find("Content-Type");
	if (it != m_properties.end() && strstr(it->second.c_str(), MIME_OMA_DD))
	{
		if (!m_pDDProcessor)
			m_pDDProcessor = new DDProcessor();
		return m_pDDProcessor->AppendBuffer(buffer, size);
	}
	else
	{
		if (!m_storageInitialized)
		{
			m_fwStatus = m_pPCH->GetDeviceAdapter()->InitFirmwareStorage();
			GDLDEBUG("InitFirmwareStorage ended with: %d", m_fwStatus);
			if (NS_DM_Client::IFirmwareUpdater::e_OK != m_fwStatus)
				return 0;
			m_storageInitialized = true;
		}
		
		m_fwStatus = m_pPCH->GetDeviceAdapter()->AppendFirmwareChunk((const char*)buffer, size, false);
		if (NS_DM_Client::IFirmwareUpdater::e_OK == m_fwStatus)
			if (!(m_cancelled = !notifyProgress()))
				return size;
	}

	return 0;
}


/// trigger the firmware update on the device
FUMOState FirmwareManager::doUpdate()
{
	FUMOState result = FS_UPDATE_FAILED_NO_DATA;
	GDLDEBUG("perform fw update");
	setFUMOStatus(FS_UPDATE_PROGRESSING);
	m_fwStatus = m_pPCH->GetDeviceAdapter()->UpdateFirmware();

	if (NS_DM_Client::IFirmwareUpdater::e_OK == m_fwStatus)
	{
		m_fwCommandStatus = FW_SUCCESSFUL;
		result = FS_UPDATE_SUCCESSFUL_HAVE_DATA;
	}
	else if (NS_DM_Client::IFirmwareUpdater::e_FirmwareUpdateFailedBecausePackageIsCorrupted == m_fwStatus)
	{
		m_fwCommandStatus = FW_CORRUPTED_PACKAGE;
		result = FS_UPDATE_FAILED_NO_DATA;
	}
	else
	{
		m_fwCommandStatus = FW_UPDATE_FAILED;
		result = FS_UPDATE_FAILED_HAVE_DATA;
	}

	m_fwStatus = m_pPCH->GetDeviceAdapter()->ResetFirmwareStorage();
	GDLDEBUG("reset fw storage ended with: %d", m_fwStatus);
	return result;
}


// return false if download should be stopped
// return true if download should be continued
bool FirmwareManager::notifyProgress()
{
	UIOptionalParameters parameters;
	if (m_pMessenger)
		return (e_Ok == m_pMessenger->ProgressNotification("", parameters, 0));
	else
		return true;
}


void FirmwareManager::sendResultAlert()
{
	// notify server about status of the FW download
	GDLDEBUG("send Alert on finished FW download");
	m_pPCH->GetServerExchangeManager()->AddCommand(
		SCommandFactory::CreateAlertOnFWDownload(m_fwCommandStatus, m_ExecCommand, m_responseType), m_serverID);
}


void FirmwareManager::setFUMOStatus(int value)
{
	if (!m_fumouri)
	{
		GDLERROR("Set status to %d: FUMO uri is not set!!!", value);
		return;
	}

	Funambol::StringBuffer fmState(m_fumouri);
	GDLDEBUG("State uri: %s", fmState.c_str());
	if (m_fumouri)
	{
		if (m_fumouri[strlen(m_fumouri)-1] != '/')
		{
			fmState.append("/");
		}
		fmState.append("State");
	}

	Funambol::Item item;
	Funambol::Target target(fmState.c_str());
	Funambol::Meta meta;
	meta.setType("text/plain");
	meta.setFormat("int");
	item.setTarget(&target);
	item.setMeta(&meta);

	char buffer[16] = {0};
	__sprintf(buffer, "%d", value);
	Funambol::ComplexData data(buffer);
	item.setData(&data);

	StatusCode rescode;
	if (e_Ok != (rescode = m_pPCH->GetMOTreeManager()->Replace(fmState.c_str(), item, NULL)))
	{
		GDLDEBUG("REPLACE failed with %d", rescode);
		rescode = m_pPCH->GetMOTreeManager()->Add(fmState.c_str(), item, NULL, false);
	}

	GDLDEBUG("Setting Status to %d finished with %d for FUMO node %s", value, rescode, m_fumouri);
}


/// load data from the uri to DeviceAdapter
FUMOState FirmwareManager::writeFWData()
{
	FUMOState result = FS_UPDATE_FAILED_NO_DATA;
	String value;
	if (e_Ok == m_pPCH->GetMOTreeManager()->GetValue(m_fwDataURI, value, NULL)) // perform in root mode
	{
		String format; // check if type is b64 - if so - decode
		if (e_Ok == m_pPCH->GetMOTreeManager()->GetPropertyValue(m_fwDataURI, "Format", format, NULL))
		{
			if (!format.compare("b64"))
			{
				// decode pkg data to plain binary
				char * pDecodedFW = new char[value.size()+1];
				memset(pDecodedFW, 0, value.size()+1);
				int decoded_len = Funambol::b64_decode(pDecodedFW, value.c_str());
				if (decoded_len)
				{
					result = FS_UPDATE_FAILED_HAVE_DATA;
					IDeviceAdapter *pDA = m_pPCH->GetDeviceAdapter();
					if (IFirmwareUpdater::e_OK == (m_fwStatus = pDA->InitFirmwareStorage()))
					{
						if(IFirmwareUpdater::e_OK == (m_fwStatus = pDA->AppendFirmwareChunk(pDecodedFW, decoded_len, true)))
						{
							result = doUpdate();
						}
						else
						{
							GDLDEBUG("Error while appending data into fw storage of the device adapter: %d", m_fwStatus);
						}
					}
					else
					{
						GDLDEBUG("Error while initializing fw storage of the device adapter: %d", m_fwStatus);
					}
				}
				else
				{
					GDLERROR("failed to decode from b64");
				}
				SAFE_DELETE_ARR(pDecodedFW);
			}
			else
			{
				GDLERROR("fw data are of the unsupported Format >%s<", format.c_str());
			}
		}
		else
		{
			GDLERROR("Failed to retrieve Format property of the fw data node");
		}
	}
	else
	{
		GDLERROR("Failed to retrieve fw from the Update node");
	}
	return result;
}
