// Copyright (C) 2002-2009 Nikolaus Gebhardt
// Copyright (C) 2009 Christian Stehno
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
// Based on the NPK reader from Irrlicht

#include "CNPKReader.h"

#ifdef __IRR_COMPILE_WITH_NPK_ARCHIVE_LOADER_

#include "os.h"
#include "coreutil.h"

#ifdef _DEBUG
#define IRR_DEBUG_NPK_READER
#endif

namespace irr
{
namespace io
{

namespace
{
	bool isHeaderValid(const SNPKHeader& header)
	{
		const c8* const tag = header.Tag;
		return tag[0] == '0' &&
			   tag[1] == 'K' &&
			   tag[2] == 'P' &&
			   tag[3] == 'N';
	}
} // end namespace


//! Constructor
CArchiveLoaderNPK::CArchiveLoaderNPK( io::IFileSystem* fs)
: FileSystem(fs)
{
#ifdef _DEBUG
	setDebugName("CArchiveLoaderNPK");
#endif
}


//! returns true if the file maybe is able to be loaded by this class
bool CArchiveLoaderNPK::isALoadableFileFormat(const io::path& filename) const
{
	return core::hasFileExtension(filename, "npk");
}

//! Check to see if the loader can create archives of this type.
bool CArchiveLoaderNPK::isALoadableFileFormat(E_FILE_ARCHIVE_TYPE fileType) const
{
	return fileType == EFAT_NPK;
}

//! Creates an archive from the filename
/** \param file File handle to check.
\return Pointer to newly created archive, or 0 upon error. */
IFileArchive* CArchiveLoaderNPK::createArchive(const io::path& filename, bool ignoreCase, bool ignorePaths) const
{
	IFileArchive *archive = 0;
	io::IReadFile* file = FileSystem->createAndOpenFile(filename);

	if (file)
	{
		archive = createArchive(file, ignoreCase, ignorePaths);
		file->drop ();
	}

	return archive;
}

//! creates/loads an archive from the file.
//! \return Pointer to the created archive. Returns 0 if loading failed.
IFileArchive* CArchiveLoaderNPK::createArchive(io::IReadFile* file, bool ignoreCase, bool ignorePaths) const
{
	IFileArchive *archive = 0;
	if ( file )
	{
		file->seek ( 0 );
		archive = new CNPKReader(file, ignoreCase, ignorePaths);
	}
	return archive;
}


//! Check if the file might be loaded by this class
/** Check might look into the file.
\param file File handle to check.
\return True if file seems to be loadable. */
bool CArchiveLoaderNPK::isALoadableFileFormat(io::IReadFile* file) const
{
	SNPKHeader header;

	file->read(&header, sizeof(header));

	return isHeaderValid(header);
}


/*!
	NPK Reader
*/
CNPKReader::CNPKReader(IReadFile* file, bool ignoreCase, bool ignorePaths)
: CFileList(file ? file->getFileName() : "", ignoreCase, ignorePaths), File(file)
{
#ifdef _DEBUG
	setDebugName("CNPKReader");
#endif

	if (File)
	{
		File->grab();
		if (scanLocalHeader())
			sort();
		else
			os::Printer::log("Failed to load NPK archive.");
	}
}


CNPKReader::~CNPKReader()
{
	if (File)
		File->drop();
}


const IFileList* CNPKReader::getFileList() const
{
	return this;
}


bool CNPKReader::scanLocalHeader()
{
	SNPKHeader header;
		
	// Read and validate the header
	File->read(&header, sizeof(header));
	if (!isHeaderValid(header))
		return false;

	// Seek to the table of contents	
#ifdef __BIG_ENDIAN__
	header.Offset = os::Byteswap::byteswap(header.Offset);
	header.Length = os::Byteswap::byteswap(header.Length);
#endif
	header.Offset += 8;
	core::stringc dirName;
	bool inTOC=true;
	// Loop through each entry in the table of contents
	while (inTOC && (File->getPos() < File->getSize()))
	{
		// read an entry
		char tag[4]={0};
		SNPKFileEntry entry;
		File->read(tag, 4);
		const int numTag = MAKE_IRR_ID(tag[3],tag[2],tag[1],tag[0]);
		int size;

		bool isDir=true;

		switch (numTag)
		{
			case MAKE_IRR_ID('D','I','R','_'):
			{
				File->read(&size, 4);
				readString(entry.Name);
				entry.Length=0;
				entry.Offset=0;
#ifdef IRR_DEBUG_NPK_READER
		os::Printer::log("Dir", entry.Name);
#endif
			}
				break;
			case MAKE_IRR_ID('F','I','L','E'):
			{
				File->read(&size, 4);
				File->read(&entry.Offset, 4);
				File->read(&entry.Length, 4);
				readString(entry.Name);
				isDir=false;
#ifdef IRR_DEBUG_NPK_READER
		os::Printer::log("File", entry.Name);
#endif
#ifdef __BIG_ENDIAN__
				entry.Offset = os::Byteswap::byteswap(entry.Offset);
				entry.Length = os::Byteswap::byteswap(entry.Length);
#endif
			}
				break;
			case MAKE_IRR_ID('D','E','N','D'):
			{
				File->read(&size, 4);
				entry.Name="";
				entry.Length=0;
				entry.Offset=0;
				const s32 pos = dirName.findLast('/', dirName.size()-2);
				if (pos==-1)
					dirName="";
				else
					dirName=dirName.subString(0, pos);
#ifdef IRR_DEBUG_NPK_READER
		os::Printer::log("Dirend", dirName);
#endif
			}
				break;
			default:
				inTOC=false;
		}
		// skip root dir
		if (isDir)
		{
			if (!entry.Name.size() || (entry.Name==".") || (entry.Name=="<noname>"))
				continue;
			dirName += entry.Name;
			dirName += "/";
		}
#ifdef IRR_DEBUG_NPK_READER
		os::Printer::log("Name", entry.Name);
#endif
		addItem(isDir?dirName:dirName+entry.Name, entry.Length, isDir, Offsets.size());
		Offsets.push_back(entry.Offset+header.Offset);
	}
	return true;
}


//! opens a file by file name
IReadFile* CNPKReader::createAndOpenFile(const io::path& filename)
{
	s32 index = findFile(filename, false);

	if (index != -1)
		return createAndOpenFile(index);

	return 0;
}


//! opens a file by index
IReadFile* CNPKReader::createAndOpenFile(u32 index)
{
	if (index < Files.size())
	{
		return createLimitReadFile(Files[index].FullName, File, Offsets[Files[index].ID], Files[index].Size);
	}
	else
		return 0;
}

void CNPKReader::readString(core::stringc& name)
{
	short stringSize;
	char buf[256];
	File->read(&stringSize, 2);
#ifdef __BIG_ENDIAN__
	stringSize = os::Byteswap::byteswap(stringSize);
#endif
	name.reserve(stringSize);
	while(stringSize)
	{
		const short next = core::min_(stringSize, (short)255);
		File->read(buf,next);
		buf[next]=0;
		name.append(buf);
		stringSize -= next;
	}
}


} // end namespace io
} // end namespace irr

#endif // __IRR_COMPILE_WITH_NPK_ARCHIVE_LOADER_

