////////////////////////////////////////////////////////////////////////////////
// MechWarrior II database.mw2 file system
// Copyright Bjoern Ganster
// Developed since 2007
////////////////////////////////////////////////////////////////////////////////

#include "Database_MW2.h"
#include "MWBase.h"
#include "FileCache.h"

#include <algorithm>

#define FILE_NUMBER FILE_NO_DATABASE_MW2_CPP

////////////////////////////////////////////////////////////////////////////////
// Constructor

Database_MW2::Database_MW2()
: MWArchive("Database_MW2")
{
}

////////////////////////////////////////////////////////////////////////////////
// Destructor

Database_MW2::~Database_MW2()
{
}

////////////////////////////////////////////////////////////////////////////////
// Open file

bool Database_MW2::open(const PathChar* PRJFN, bool readOnly)
{
	SET_SCOPE;
	bool openedSuccessfully;
	if (readOnly)
		openedSuccessfully = fc.openReadOnly(PRJFN);
	else
		openedSuccessfully = fc.openReadWrite(PRJFN);

	if (!openedSuccessfully) {
		printf(PathCharMod ": failed to open\n", PRJFN);
		return false;
	}

	auto fileSize = fc.getFileSize();
	if (fileSize < 20) {
		printf(PathCharMod ": unexpected small file size,%i bytes", PRJFN, fc.getFileSize());
		RETURN false;
	}

	size_t entries = fc.readDWord (0);

	// Read file offsets
	for (size_t i = 0; i < entries; i++) {
		FileOffset fileOffset = fc.readDWord (4*i+4);

		if (fileOffset < fileSize) {
			size_t toRead = std::min ((FileOffset) 50, fc.getFileSize()-fileOffset);
			MemoryBlock mb(toRead);
			fc.read (fileOffset, (char*) mb.getPtr<unsigned char>(0), toRead);
			FileType fileType = getFileTypeFromContent (&mb);

			// Form fake filename from file number and type
			BGCPString fn = BGCPString::fromInt(i);
			fn += ".";
			fn += getMWFileExt(fileType);

			addFile (fn, 0, fileOffset, fileType);
		}
	}

	calculateFileSizesFromOffsetDifferences();

	RETURN (getEntryCount() > 0);
}

////////////////////////////////////////////////////////////////////////////////
// Close file

void Database_MW2::close()
{
   fc.close();
}

////////////////////////////////////////////////////////////////////////////////
// Get name of entry

bool Database_MW2::getEntryName (size_t index, BGCPString& name)
{
   name = BGCPString::fromInt(index);
   name += '.';
   name += getMWFileExt (mEntries[index].fileType);
   return true;
}

////////////////////////////////////////////////////////////////////////////////
// Replace  file

bool Database_MW2::replaceFile (size_t fileNum, const PathChar* srcFN)
{
	// Load srcFN into MemoryBlock
	SET_SCOPE;
	MemoryBlock mb;
	if (!mb.loadFromFile(srcFN))
		return false;

	// Append  MemoryBlock to archive
	DWORD newOffset = (DWORD) fc.getFileSize();
	const unsigned char* udata = mb.getPtr<unsigned char>();
	const char* data = (const char*)((const void*) udata); // casting oddyssey ...
	fc.append(data, mb.getSize());

	// Update file data on archive
	FileType fileType = getFileTypeFromContent (&mb);
	mEntries[fileNum].fileSize   = mb.getSize();
	mEntries[fileNum].fileOffset = newOffset;
	mEntries[fileNum].fileType   = fileType;

	// Update archive file
	fc.write(4*fileNum+4, (const char*) (&newOffset), 4);
	close();
	RETURN true;
}

////////////////////////////////////////////////////////////////////////////////
// Calculate file sizes from offset differences

void Database_MW2::calculateFileSizesFromOffsetDifferences()
{
   // Calculate file size from distance to next file
   size_t entries = mEntries.size();
   for (size_t i = 0; i+1 < entries; i++) {
      int fileSize = ((int) mEntries[i+1].fileOffset) - ((int) mEntries[i].fileOffset);
      mEntries[i].fileSize = fileSize;
   }

   // File size for last file is computed from distance to EOF
   int _EOF = (int) fc.getFileSize();
   size_t i = entries-1;
   int lastFileOffset = (int) mEntries[i].fileOffset;
   if (lastFileOffset < _EOF)
      mEntries[i].fileSize = _EOF - lastFileOffset;
   else
       mEntries[i].fileSize = 0; // Error
}

////////////////////////////////////////////////////////////////////////////////
// Read bufSize bytes from file numbered index at offset into buf

bool Database_MW2::getFileChunk(size_t index, size_t offset, void* buf, size_t bufSize)
{
   if (index >= mEntries.size())
      return false;

   if (offset + bufSize > mEntries[index].fileSize)
      return false;

   return fc.read(mEntries[index].fileOffset, (char*) buf, bufSize);
}
