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


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;
	}

	if (m_logger)
	{
		defFWStorageLocation = (m_fwDefLocation.empty()) ? "" : m_fwDefLocation.c_str();
		defFWPackageName = (m_fwPackageDefName.empty()) ? "" : m_fwPackageDefName.c_str();
		LOG_DEBUG_(*m_logger, "Default firmware location = '%s'", defFWStorageLocation);
		LOG_DEBUG_(*m_logger, "Default firmware package name = '%s'", defFWPackageName);
	}

	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[m_fwPackageName.length()];
	if (lastChar != dirSeparator)
	{
		m_fwPackageName += dirSeparator;
	}
	m_fwPackageName += (packageName) ? packageName : m_fwPackageDefName.c_str();

	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;
	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;
	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(String& ind)
{
	ind.clear();
	if (!m_fwPackageName.empty())
	{
		ind =	m_fwPackageName;
		ind +=	fwUpdateIndication;
		return !ind.empty();
	}

	return false;
}

