/*
 * UpdateManager.cpp
 *
 *  Created on: 30.11.2009
 *      Author: stefan.detter
 */

#include "FirmwareUpdateManager.h"

#include <QApplication>
#include <QDir> 
#include <QMessageBox>
#include <QList>
#include <QSet>
#include <QVariant>
#include <QDesktopServices>

#include <QrfeReaderManager>

#include "../Resource.h"

#include <def.h>


FirmwareUpdateManager::FirmwareUpdateManager(QWidget* window, QObject* parent)
	: QObject(parent)
	, QrfeTraceModule("UpdateManager")
	, m_window(window)
{
	connect(Resource::d, 	SIGNAL( addFileRequest(const QFileInfo&, bool&, bool&, QString&) ),
			this, 			  SLOT( addFileRequest(const QFileInfo&, bool&, bool&, QString&) ), Qt::DirectConnection);
}

FirmwareUpdateManager::~FirmwareUpdateManager()
{
}


void FirmwareUpdateManager::init()
{
	QFileInfoList files = Resource::d->firmwareFiles();
	QString tempError;
	foreach(QFileInfo info, files)
	{
		insertFile(info, tempError);
	}

	dumpRepository();

	files = Resource::d->testFirmwareFiles();
	foreach(QFileInfo info, files)
	{
		insertFile(info, tempError);
	}

	files = Resource::d->bootloaderFiles();
	foreach(QFileInfo info, files)
	{
		insertFile(info, tempError);
	}

	dumpBootloaderRepository();
}


void FirmwareUpdateManager::addFileRequest ( const QFileInfo& file, bool& yesToAll, bool& result, QString& errMsg )
{
	if(result == true)
		return;
 
	ulong readerType, hardwareRevision, softwareRevision;
	bool isBootloader = false;

	if(!checkFile(file, readerType, hardwareRevision, softwareRevision, isBootloader, errMsg))
	{
		result = false;
		return;
	}

	if(!parseFile(file, readerType, hardwareRevision, softwareRevision, errMsg))
	{
		result = false;
		return;
	}


	QMap<ulong, QMap<ulong, QMap<ulong, ImageInfo> > > & imageList = (isBootloader)?m_bootloaderImageList:((isTestFirmware(softwareRevision))?m_testImageList:m_imageList);

	if(imageList[readerType][hardwareRevision].contains(softwareRevision))
	{
		if(!yesToAll)
		{
			QMessageBox::StandardButton b;
			b = QMessageBox::question(m_window,
									  "Add Firmware File",
									  QString(
											  "The %1 repository already contains the %2 for "
											  "the reader type %3 with the hardware revision %4 and the "
											  "software revision %5. \n\n"
											  "Old file from: %6\n"
											  "New file from: %7\n\n"
											  "Do you want to replace the firmware file?")
									  .arg((isBootloader)?"bootloader":"firmware")
									  .arg((isBootloader)?"bootloader":"firmware")
									  .arg(readerType, 8, 16, QChar('0'))
									  .arg(hardwareRevision, 8, 16, QChar('0'))
									  .arg(softwareRevision, 8, 16, QChar('0'))
									  .arg(imageList[readerType][hardwareRevision][softwareRevision].file.lastModified().toString())
									  .arg(file.lastModified().toString()),
									  QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll,
									  QMessageBox::Yes
									  );

			if(b == QMessageBox::No)
			{
				errMsg = "File should not be replaced.";
				result = false;
				return;
			}
			else if(b == QMessageBox::YesToAll)
			{
				yesToAll = true;
			}
		}

		QFile::remove(imageList[readerType][hardwareRevision][softwareRevision].file.absoluteFilePath());
		imageList[readerType][hardwareRevision].remove(softwareRevision);
	}

	QFileInfo newFile;
	QString subDir = QString("%1").arg(readerType, 8, 16, QChar('0'));
	if(!Resource::d->copyFileToResource(file,
			(isBootloader)?
					Resource::BootloaderResource:
					((isTestFirmware(softwareRevision))?
							Resource::TestFimrwareResource:
							Resource::FimrwareResource),
			subDir, newFile))
	{
		errMsg = "The firmware file could not be copied.";
		result = false;
		return;
	}

	errMsg = "The firmware file was successfully added to the repository.";
	result = true;

	QString tmp;

	if(!insertFile(newFile, tmp)){
		errMsg = "Error on import file " + newFile.fileName() + ": " +  tmp;
		error("Error on import file " + newFile.fileName() + ": " +  tmp);
		result = false;
		return;
	}

	return;
}



void FirmwareUpdateManager::checkReaderForUpdates(QrfeReaderInterface* reader)
{
	ulong readerType		= reader->readerTypeExact();
	ulong softwareRevision 	= reader->softwareRevision();
	ulong hardwareRevision 	= reader->hardwareRevision();

	if(reader->currentSystem() == QrfeGlobal::BOOTLOADER)
		return;

	QList<ulong> softwareRevisions = m_imageList[readerType][hardwareRevision].keys();

	if(softwareRevisions.size() == 0)
		return;

	qSort(softwareRevisions);
	ulong maxSoftwareRevision = softwareRevisions.last();
	if( (maxSoftwareRevision & 0x0000FF00) == 0x0000FF00){
		if(softwareRevisions.size() > 2)
			maxSoftwareRevision = softwareRevisions.at(softwareRevisions.size() - 2);
		else
			maxSoftwareRevision = 0;
	}

	if(maxSoftwareRevision > softwareRevision)
	{
		QMessageBox::StandardButton b;
		b = QMessageBox::question(
				m_window,
				"Update available",
				"A newer firmware version is available for the attached reader. Do you want to update the reader now?",
				QMessageBox::Yes | QMessageBox::No,
				QMessageBox::Yes);

		if(b != QMessageBox::Yes)
			return;

		QList<QVariant> params;
		QString readerId = reader->readerId();

		params.clear();
		emit appendGlobalJob(JOB_StopScan, params);

		params.clear();
		params.append(readerId);
		emit appendGlobalJob(JOB_SwitchReaderToBootloaderAndShowDialog, params);
	}
}


QMap<ulong, QMap<ulong, ImageInfo> > FirmwareUpdateManager::getAvailableSoftwareRevisions (ulong readerType, ulong hardwareRevision)
{
	QMap<ulong, QMap<ulong, ImageInfo> > result;

	if( (readerType&0x000000FF) == 0xFF)
	{
		foreach(uint rt, m_imageList.keys())
		{
			if( (rt&0xFFFFFF00) == (readerType&0xFFFFFF00) )
				result.insert(rt, m_imageList.value(rt).value(hardwareRevision));
		}
	}
	else
	{
		result.insert(readerType, m_imageList.value(readerType).value(hardwareRevision));
	}

	return result;
}

QMap<ulong, QMap<ulong, ImageInfo> > FirmwareUpdateManager::getAvailableSoftwareRevisions (QSet<ulong> readerTypes, ulong hardwareRevision)
{
	QMap<ulong, QMap<ulong, ImageInfo> > result;

	foreach(ulong readerType, readerTypes.toList())
	{
		if( (readerType&0x000000FF) == 0xFF)
		{
			foreach(uint rt, m_imageList.keys())
			{
				if( (rt&0xFFFFFF00) == (readerType&0xFFFFFF00) )
					result.insert(rt, m_imageList.value(rt).value(hardwareRevision));
			}
		}
		else
		{
			result.insert(readerType, m_imageList.value(readerType).value(hardwareRevision));
		}
	}

	return result;
}

QMap<ulong, QMap<ulong, ImageInfo> > FirmwareUpdateManager::getAvailableTestSoftwareRevisions (ulong readerType, ulong hardwareRevision)
{
	QMap<ulong, QMap<ulong, ImageInfo> > result;

	if( (readerType&0x000000FF) == 0xFF)
	{
		foreach(uint rt, m_testImageList.keys())
		{
			if( (rt&0xFFFFFF00) == (readerType&0xFFFFFF00) )
				result.insert(rt, m_testImageList.value(rt).value(hardwareRevision));
		}
	}
	else
	{
		result.insert(readerType, m_testImageList.value(readerType).value(hardwareRevision));
	}

	return result;
}

QMap<ulong, QMap<ulong, ImageInfo> > FirmwareUpdateManager::getAvailableTestSoftwareRevisions (QSet<ulong> readerTypes, ulong hardwareRevision)
{
	QMap<ulong, QMap<ulong, ImageInfo> > result;

	foreach(ulong readerType, readerTypes.toList())
	{
		if( (readerType&0x000000FF) == 0xFF)
		{
			foreach(uint rt, m_testImageList.keys())
			{
				if( (rt&0xFFFFFF00) == (readerType&0xFFFFFF00) )
					result.insert(rt, m_testImageList.value(rt).value(hardwareRevision));
			}
		}
		else
		{
			result.insert(readerType, m_testImageList.value(readerType).value(hardwareRevision));
		}
	}

	return result;
}


QMap<ulong, QMap<ulong, ImageInfo> > FirmwareUpdateManager::getAvailableBootloaderRevisions (ulong readerType, ulong hardwareRevision, bool allowCharSwitch)
{
	QMap<ulong, QMap<ulong, ImageInfo> > result;

	foreach(uint rt, m_bootloaderImageList.keys())
	{
        bool ok = false;
        if(allowCharSwitch)
            ok = (rt&0xFFFF0000) == (readerType&0xFFFF0000);
        else
            ok = (rt&0xFFFFFF00) == (readerType&0xFFFFFF00);

        if( ok )
			result.insert(rt, m_bootloaderImageList.value(rt).value(hardwareRevision));
	}

	return result;
}


QFileInfo FirmwareUpdateManager::getImageFile(ulong readerType, ulong hardwareRevision, ulong softwareRevision, bool isBootloader)
{
	if(isBootloader)
		return m_bootloaderImageList.value(readerType).value(hardwareRevision).value(softwareRevision).file;

	if(isTestFirmware(softwareRevision))
		return m_testImageList.value(readerType).value(hardwareRevision).value(softwareRevision).file;
	else
		return m_imageList.value(readerType).value(hardwareRevision).value(softwareRevision).file;
}

bool FirmwareUpdateManager::isTestFirmware(ulong &softwareRevision)
{
	if( (softwareRevision & 0x00008000) != 0)
		return true;
	return false;
}

bool FirmwareUpdateManager::checkFile(const QFileInfo&  file, ulong &readerType, ulong &hardwareRevision, ulong &softwareRevision, bool &isBootloader, QString &error)
{
	if(!file.exists())
	{
		error = "The file does not exist.";
		return false;
	}

	if(!file.isFile())
	{
		error = "The data type is not a file.";
		return false;
	}

	if(!file.isReadable())
	{
		error = "The file is not readable.";
		return false;
	}

    if(file.completeSuffix() != "idt")
	{
		error = "Unkown file type.";
		return false;
	}

	error = "The file is no valid firmware file.";

	QString fileName = file.fileName();
	fileName = fileName.remove(fileName.size() - 4, 4);
	QStringList list = fileName.split("-");
	if(list.size() != 4)
	{
		return false;
	}

	if(list.at(0) != "PUR" && list.at(0) != "AUR")
	{
		if(list.at(0) == "BL")
			isBootloader = true;
		else
			return false;
	}

	bool ok = false;
	readerType 		= list.at(3).toULong(&ok, 16);
	if(!ok)
		return false;
	softwareRevision 	= list.at(2).toULong(&ok, 16);
	if(!ok)
		return false;
	hardwareRevision 	= list.at(1).toULong(&ok, 16);
	if(!ok)
		return false;

	error = "No error.";

	return true;
}

bool FirmwareUpdateManager::parseFile(const QFileInfo&  file, ulong readerType, ulong hardwareRevision, ulong softwareRevision, QString &error)
{
	QFile image(file.absoluteFilePath());

	error = "The file is no valid firmware file.";

	image.open(QIODevice::ReadOnly);
	if(!image.isOpen())
		return false;

	image.seek(0);
	ulong read_hwRev = byteArrayToUlong(image.read(4));

	image.seek(4);
	ulong read_swRev = byteArrayToUlong(image.read(4));

	image.seek(8);
	ulong read_rType = byteArrayToUlong(image.read(4));

	if(read_hwRev != hardwareRevision || read_swRev != softwareRevision || read_rType != readerType){
		image.close();
		return false;
	}

	image.close();

	error = "No error";

	return true;
}


bool FirmwareUpdateManager::insertFile(const QFileInfo& file, QString &error)
{
	ulong readerType, hardwareRevision, softwareRevision;
	bool isBootloader = false;

	if(!checkFile(file, readerType, hardwareRevision, softwareRevision, isBootloader, error))
		return false;
	if(!parseFile(file, readerType, hardwareRevision, softwareRevision, error))
		return false;

	bool _isTestFirmware = isTestFirmware(softwareRevision);

	QFile image(file.absoluteFilePath());

	image.open(QIODevice::ReadOnly);
	if(!image.isOpen())
		return false;

	image.seek(12);
	ulong read_size = byteArrayToUlong(image.read(4));

	ImageInfo ii;

    if(file.size() > (qint64)read_size + 65)
	{
		trc(0, "Image size: " + QString::number(file.size()));
		trc(0, "Read size: " + QString::number(read_size));
		trc(0, "Read size + 65: " + QString::number(read_size + 65));

		uint offset = read_size + 65 + 256;
		ulong value;

		while(offset < file.size())
		{
			image.seek(offset);
			value = byteArrayToUlong(image.read(4));
			switch(value)
			{
			case 0x00000001:
				offset += 4;
				image.seek(offset);
				trc(1, "Version is: " + QString::number(byteArrayToUlong(image.read(4))));
				break;
			case 0x00000002:
				{
					offset += 4;
					image.seek(offset);
					QDateTime t = QDateTime::fromTime_t(byteArrayToUlong(image.read(4)));
					ii.creation = t;
					trc(1, "Creation Date is: " + t.toString());
					break;
				}
			default:
				break;
			}
			offset += 4;
		}
	}
	else
	{
		ii.creation = file.lastModified();
	}

	ii.file = file;

	if(isBootloader)
		m_bootloaderImageList[readerType][hardwareRevision][softwareRevision] = ii;
	else if(_isTestFirmware)
		m_testImageList[readerType][hardwareRevision][softwareRevision] = ii;
	else
		m_imageList[readerType][hardwareRevision][softwareRevision] = ii;

	image.close();
	return true;
}


ulong FirmwareUpdateManager::byteArrayToUlong(QByteArray ba)
{
	ulong value = 0;
	value += (((ulong)(uchar)ba.at(3)) << 24);
	value += (((ulong)(uchar)ba.at(2)) << 16);
	value += (((ulong)(uchar)ba.at(1)) << 8);
	value += (ulong)(uchar)ba.at(0);
	return value;
}

void FirmwareUpdateManager::dumpRepository()
{
	trc(2, "Firmware repository:");
	foreach(ulong readerType, m_imageList.keys())
	{
		trc(2, "\tReader Type - " + QString("%1").arg(readerType, 8, 16, QChar('0')));
		foreach(ulong hardwareRevision, m_imageList.value(readerType).keys())
		{
			trc(2, "\t\tHardware Revision - " + QString("%1").arg(hardwareRevision, 8, 16, QChar('0')));
			foreach(ulong softwareRevision, m_imageList.value(readerType).value(hardwareRevision).keys())
			{
				trc(2, "\t\t\tSoftware Revision - " + QString("%1").arg(softwareRevision, 8, 16, QChar('0')));
			}
		}
	}
}

void FirmwareUpdateManager::dumpTestRepository()
{
	trc(2, "Test Firmware repository:");
	foreach(ulong readerType, m_testImageList.keys())
	{
		trc(2, "\tReader Type - " + QString("%1").arg(readerType, 8, 16, QChar('0')));
		foreach(ulong hardwareRevision, m_testImageList.value(readerType).keys())
		{
			trc(2, "\t\tHardware Revision - " + QString("%1").arg(hardwareRevision, 8, 16, QChar('0')));
			foreach(ulong softwareRevision, m_testImageList.value(readerType).value(hardwareRevision).keys())
			{
				trc(2, "\t\t\tSoftware Revision - " + QString("%1").arg(softwareRevision, 8, 16, QChar('0')));
			}
		}
	}
}

void FirmwareUpdateManager::dumpBootloaderRepository()
{
	trc(2, "Bootloader repository:");
	foreach(ulong readerType, m_bootloaderImageList.keys())
	{
		trc(2, "\tReader Type - " + QString("%1").arg(readerType, 8, 16, QChar('0')));
		foreach(ulong hardwareRevision, m_bootloaderImageList.value(readerType).keys())
		{
			trc(2, "\t\tHardware Revision - " + QString("%1").arg(hardwareRevision, 8, 16, QChar('0')));
			foreach(ulong softwareRevision, m_testImageList.value(readerType).value(hardwareRevision).keys())
			{
				trc(2, "\t\t\tSoftware Revision - " + QString("%1").arg(softwareRevision, 8, 16, QChar('0')));
			}
		}
	}
}
