/*
 * 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 "DeviceAdapter/FirmwareUpdate/DiskStorageFirmwareUpdater.h"
#include <Logger/Logger.h>
#include <FileSystemUtils.h>

#include <string>

using namespace NS_DM_Client;
typedef IFirmwareUpdater::EnumFirmwareStatus	EnumFirmwareStatus;

static const char fwUpdateIndication[] = ".updated";
static const char successfulFWUpdate[] = "SUCCESS";

DiskStorageFirmwareUpdater::DiskStorageFirmwareUpdater()
: m_logger(0),
m_fwPkgStorage(0),
m_lastFWChunk(false),
m_notCleanStorage(false),
m_fwUpdateFailure(false)
{
}


DiskStorageFirmwareUpdater::~DiskStorageFirmwareUpdater()
{
	ResetStorage();
}


bool DiskStorageFirmwareUpdater::Init(NS_Logging::Logger* logger)
{
	return Init(NULL, NULL, false, logger);
}

bool DiskStorageFirmwareUpdater::Init(const char* defFWPackageName, const char* defFWStorageLocation,
	bool notCleanStorage, NS_Logging::Logger* logger)
{
	m_logger = logger;

	m_notCleanStorage = notCleanStorage;

	m_fwDefLocation.clear();
	m_fwPackageDefName.clear();
	m_fwPackageName.clear();

	if (defFWPackageName)
	{
		m_fwPackageDefName = defFWPackageName;
	}

	if (defFWStorageLocation)
	{
		m_fwDefLocation = defFWStorageLocation;
	}

	return true;
}


EnumFirmwareStatus DiskStorageFirmwareUpdater::InitStorage(const char* packageName, const char* location)
{
	if (isFWStorageInitialised())
	{
		if (m_logger) LOG_DEBUG_(*m_logger, "previous firmware storage initialization is not finilized. PkgStorage=%p", m_fwPkgStorage);
		return e_FirmwareStorageFailure;
	}

	EnumFirmwareStatus res = e_OK;
	if (!location)
	{
		location =  m_fwDefLocation.c_str();
	}

	m_lastFWChunk = false;
	m_pkgSize = 0;

	if (!CreatePath(location))
	{
		if (m_logger) LOG_ERROR_(*m_logger, "failed to create path: '%s'", location);
		res = e_FirmwareStorageFailure;
		return res;
	}

	m_fwPackageName = location;

	const char dirSeparator = '/';
	const char lastChar = *m_fwPackageName.rbegin();
	if (lastChar != dirSeparator)
	{
		m_fwPackageName += dirSeparator;
	}
	m_fwPackageName += (packageName) ? packageName : m_fwPackageDefName;

	if (m_logger) LOG_DEBUG_(*m_logger, "init firmware storage: '%s'", m_fwPackageName.c_str());

	m_fwPkgStorage = fopen(m_fwPackageName.c_str(), "wb");
	if (!m_fwPkgStorage)
	{
		if (m_logger) LOG_ERROR_(*m_logger, "Failed to open FW storage: '%s'", m_fwPackageName.c_str());
		m_fwPackageName.clear();
		res = e_FirmwareStorageFailure;
	}

	return res;
}


EnumFirmwareStatus DiskStorageFirmwareUpdater::ResetStorage()
{
	const char* pkgName = (m_fwPackageName.empty()) ? "" : m_fwPackageName.c_str();
	if (m_logger) LOG_DEBUG_(*m_logger, "FWPackageStorage=%p, FWPackageName='%s', NotCleanStorage=%d",
		m_fwPkgStorage, pkgName, m_notCleanStorage);

	if (m_fwPkgStorage)
	{
		fclose(m_fwPkgStorage);
		m_fwPkgStorage = NULL;

		if (m_notCleanStorage)
		{
			if (m_logger) LOG_WARNING_(*m_logger, "Flag to not clean the firmware storage is set. Skip cleaning.");
		}
		else
		{
			if (remove(pkgName) != 0)
			{
				if (m_logger) LOG_ERROR_(*m_logger, "Failed to remove firmware package: '%s'.", pkgName);
			}

			if (!resetFWUpdatedIndication())
			{
				if (m_logger) LOG_ERROR_(*m_logger, "Failed to reset FW update indication");
			}
		}
	}
	m_fwPackageName.clear();

	EnumFirmwareStatus res = e_OK;
	if (m_logger) LOG_DEBUG_(*m_logger, "result=%d", res);

	return res;
}


EnumFirmwareStatus DiskStorageFirmwareUpdater::AppendChunk(const char* buffer, size_t size, bool last)
{
	EnumFirmwareStatus res = e_OK;

	do
	{
		if (!isFWStorageInitialised())
		{
			if (m_logger) LOG_ERROR_(*m_logger, "Firmware storage is not ready");
			res = e_FirmwareStorageFailure;
			break;
		}

		if (m_lastFWChunk)
		{
			if (m_logger) LOG_ERROR_(*m_logger, "Firmware package already complete. Last chunk received already.");
			res = e_FirmwareStorageOverflow;
			break;
		}

		if (buffer && fwrite(buffer, 1, size, m_fwPkgStorage) != size)
		{
			if (m_logger) LOG_ERROR_(*m_logger, "Failed to write firmware chunk. PkgStorage=%p", m_fwPkgStorage);
			res = e_FirmwareStorageOverflow;
			break;
		}
		else
		{
			fflush(m_fwPkgStorage);
		}
		m_pkgSize += size;

		m_lastFWChunk = last;

	}while(0);

	if (m_logger) LOG_DEBUG_(*m_logger, "chunk size=%d, last=%d, Result=%d", size, last, res);
	return res;
}

EnumFirmwareStatus DiskStorageFirmwareUpdater::Update()
{
	if (!isFWStorageInitialised())
	{
		if (m_logger) LOG_ERROR_(*m_logger, "Firmware storage is not ready");
		return e_FirmwareStorageFailure;
	}

	EnumFirmwareStatus res = e_OK;
	if (m_lastFWChunk)
	{
		setFWUpdatedIndication();
	}
	else
	{
		if (m_logger) LOG_ERROR_(*m_logger, "Firmware package is not finalized. Need to write last chunk before update.");
		res = e_FirmwareUpdateFailed;
	}

	if (res == e_OK && m_fwUpdateFailure)
	{
		if (m_logger) LOG_DEBUG_(*m_logger, "Set testing result as FAILED");
		res = e_FirmwareUpdateFailed;
	}

	if (m_logger) LOG_DEBUG_(*m_logger, "Result=%d", res);
	return res;
}


bool DiskStorageFirmwareUpdater::isFWStorageInitialised() const
{
	return m_fwPkgStorage != 0;
}


bool DiskStorageFirmwareUpdater::setFWUpdatedIndication()
{
	bool brc = false;
	std::string fwPkgUpdated;

	do
	{
		if (!generateFWUpdatedIndicator(fwPkgUpdated))
		{
			if (m_logger) LOG_ERROR_(*m_logger, "Failed to generate FW update indicator");
			break;
		}

		FILE* fwUdated = fopen(fwPkgUpdated.c_str(), "w+");
		if (fwUdated)
		{
			const size_t bytesToWrite = sizeof(successfulFWUpdate) - 1;
			if (fwrite(successfulFWUpdate, 1, bytesToWrite, fwUdated) != bytesToWrite)
			{
				if (m_logger) LOG_ERROR_(*m_logger, "Failed to write FW update status to: '%s'", fwPkgUpdated.c_str());
			}
			fclose(fwUdated);
		}
		else
		{
			if (m_logger) LOG_ERROR_(*m_logger, "Failed to set FW update indication: '%s'", fwPkgUpdated.c_str());
			break;
		}

		brc = true;
	}while(false);

	if (m_logger) LOG_DEBUG_(*m_logger, "brc=%d", brc);
	return brc;
}


bool DiskStorageFirmwareUpdater::resetFWUpdatedIndication()
{
	bool brc = false;
	std::string fwPkgUpdated;

	do
	{
		if (!generateFWUpdatedIndicator(fwPkgUpdated))
		{
			if (m_logger) LOG_ERROR_(*m_logger, "Failed to generate FW update indicator");
			break;
		}

		if (remove(fwPkgUpdated.c_str()) != 0)
		{
			if (m_logger) LOG_ERROR_(*m_logger, "Failed to remove FW update indication");
			break;
		}

		brc = true;
	}while(false);

	return brc;
}


bool DiskStorageFirmwareUpdater::generateFWUpdatedIndicator(std::string& ind)
{
	ind.clear();
	if (!m_fwPackageName.empty())
	{
		ind =	m_fwPackageName;
		ind +=	fwUpdateIndication;
		return !ind.empty();
	}

	return false;
}

