#include <memory>
#include <vector>
#include <assert.h>
#include <boost/filesystem.hpp>
#include "StdStream.h"
#include "PsfFsDescription.h"
#include "PsfFsWriter.h"
#include "PsfTagsDescription.h"
#include "PsfTagsWriter.h"
#include "xml/Node.h"
#include "xml/Parser.h"
#include "xml/Utils.h"

struct PSFHEADER
{
	uint8		signature[3];
	uint8		version;
	uint32		reservedSize;
	uint32		programSize;
	uint32		programCrc;
};
static_assert(sizeof(PSFHEADER) == 0x10, "PSFHEADER must be 16 bytes long.");

enum PSFVERSION
{
	PSFVERSION_PS2 = 0x02,
	PSFVERSION_PSP = 0x03
};

int main(int argc, const char** argv)
{
	if(argc < 3)
	{
		printf("Usage: PsfPacker [descriptorFile] [outputFile]\r\n");
		return -1;
	}

	PsfFs::DirectoryPtr rootDir;
	PsfTags::TagList tags;
	PSFVERSION version = PSFVERSION_PSP;
	{
		std::shared_ptr<Framework::Xml::CNode> documentRoot;

		try
		{
			Framework::CStdStream stream(argv[1], "rb");
			auto result = Framework::Xml::CParser::ParseDocument(stream);
			if(result == NULL)
			{
				throw std::exception("Cannot parse XML file.");
			}
			documentRoot = std::shared_ptr<Framework::Xml::CNode>(result);
		}
		catch(const std::exception& exception)
		{
			printf("Error. Couldn't process input file: %s.\r\n", exception.what());
			return -1;
		}

		//Read version
		{
			auto packageNode = documentRoot->Select("Package");
			if(packageNode != nullptr)
			{
				auto versionAttribute = packageNode->GetAttribute("Version");
				if(versionAttribute != nullptr)
				{
					if(!strcmp(versionAttribute, "ps2"))
					{
						version = PSFVERSION_PS2;
					}
					if(!strcmp(versionAttribute, "psp"))
					{
						version = PSFVERSION_PSP;
					}
				}
			}
		}

		//Read tags
		{
			auto tagsNode = documentRoot->Select("Package/Tags");
			if(tagsNode != nullptr)
			{
				tags = PsfTags::ParseTags(tagsNode);
			}
		}

		//Read filesystem
		{
			auto filesystemNode = documentRoot->Select("Package/Filesystem");
			if(filesystemNode != nullptr)
			{
				rootDir = PsfFs::ParseDirectoryNode(filesystemNode);
			}
		}
	}

	if(!rootDir)
	{
		printf("No filesystem found. Aborting.\r\n");
		return -1;
	}

	//Derive base path
	boost::filesystem::path basePath(argv[1]);
	basePath = boost::filesystem::complete(basePath);
	basePath = basePath.remove_leaf();

	try
	{
		Framework::CStdStream output(argv[2], "wb");

		//Write header
		PSFHEADER header;
		header.signature[0] = 'P';
		header.signature[1] = 'S';
		header.signature[2] = 'F';
		header.version		= version;
		header.reservedSize	= 0;
		header.programSize	= 0;
		header.programCrc	= 0;
		output.Write(&header, sizeof(PSFHEADER));

		//Write reserved
		uint64 reservedSize = output.Tell();
		{
			PsfFs::CWriter::Write(output, rootDir, basePath);
		}
		reservedSize = output.Tell() - reservedSize;

		//Writeback header
		header.reservedSize = static_cast<uint32>(reservedSize);

		output.Seek(0, Framework::STREAM_SEEK_SET);
		output.Write(&header, sizeof(PSFHEADER));
		output.Seek(0, Framework::STREAM_SEEK_END);

		PsfTags::CWriter::Write(output, tags);
	}
	catch(const std::exception& exception)
	{
		printf("Failed to write PSF file '%s'.\r\nError: %s\r\n", argv[2], exception.what());
	}

	return 0;
}
