////////////////////////////////////////////////////////////////////////////////
// Support for MechWarrior 3 ZBD archives
// Copyright Bjoern Ganster
// Developed since 2007
////////////////////////////////////////////////////////////////////////////////

#include "MechWarrior3ZBD.h"
#include "Texture.h"

#include <iostream>

#define FILE_NUMBER FILE_NO_MECHWARRIOR_3_ZBD_CPP

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

MechWarrior3ZBD::MechWarrior3ZBD()
: MWArchive("MechWarrior3ZBD")
{
}

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

MechWarrior3ZBD::~MechWarrior3ZBD()
{
}

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

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

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

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

	if (!openedSuccessfully) {
		RETURN false;
	}

	// Derive access constants. This used to depend on file sizes, but the file size could change a patches.
	// These defaults work in many cases, with exceptions handled below.
	size_t contentsTableOffset = 24;
	size_t recordSize = 40;
	size_t fileOffsetInRecord = 32;
	size_t fileSizeInRecord = 36;
	size_t fileNameInRecord = 0;
	auto fileCount = fc.readDWord(0x0c);

	SET_OP;
	BGCPString prjFnStr(PRJFN, CP_SYSTEM);
	if (prjFnStr.endsWith("mechlib.zbd")
	||  prjFnStr.endsWith("motion.zbd")
	||  prjFnStr.endsWith("soundsH.zbd")
	||  prjFnStr.endsWith("soundsL.zbd")
	||  prjFnStr.endsWith("soundsh.zbd")  // Recoil uses lower case h instead of upper case h
	||  prjFnStr.endsWith("soundsl.zbd")) // same
	{
		// TOC at EOF, footer size is 8
		recordSize = 148;
		fileOffsetInRecord = 0;
		fileSizeInRecord = 4;
		fileNameInRecord = 8;
		fileCount = fc.readDWord(fc.getFileSize() - 4);
		contentsTableOffset = fc.getFileSize() - recordSize * fileCount - 8;

		if (fileCount == 0) {
			// Pirate's moon has a zero DWord at the file's end ...
			fileCount = fc.readDWord(fc.getFileSize() - 8);
			contentsTableOffset = fc.getFileSize() - recordSize * fileCount - 12;
		}
	} else if (prjFnStr.endsWith("anim.zbd")
	||         prjFnStr.endsWith("gamez.zbd")) {
		// Shouldn't get here: instead of this class, a gamez archive handler should have been instantiated ...
		COUT << "Wrong handler used..." << std::endl;
		RETURN false;
	}

	// Plausibility check ...
	auto archiveSize = fc.getFileSize();
	if (contentsTableOffset + fileCount * recordSize > archiveSize) {
		close();
		RETURN false;
	}

	// Read the entries
	SET_OP;
	clearFileEntries();
	for (size_t i = 0; i < fileCount; i++) {
		size_t offset = contentsTableOffset + recordSize * i;
		DWORD FileOffset, FileSize;
		fc.read(offset + fileOffsetInRecord, (char*)(&FileOffset), 4);
		fc.read(offset + fileSizeInRecord, (char*)(&FileSize), 4);

		// For some ZBD subtypes, we need to deduct the file size
		if (FileOffset + (int64_t) FileSize > (int64_t) archiveSize) {
			if (i+1 < fileCount) {
				DWORD nextFileOffset;
				fc.read(offset + recordSize + fileOffsetInRecord, (char*)(&nextFileOffset), 4);
				FileSize = nextFileOffset - FileOffset;
			} else {
				FileSize = archiveSize - offset;
			}
		}

		char fnBuf[256]; // will be less than the values defined for recordSize, above
		fc.read(offset + fileNameInRecord, fnBuf, recordSize);

		if (FileSize > 0 && FileOffset + FileSize <= archiveSize) {
			BGCPString fn(fnBuf);
			addFile(fn, FileSize, FileOffset);
		}
	}
	RETURN true;
}

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

bool MechWarrior3ZBD::getFileChunk(size_t index, size_t offset, void* buf, size_t bufSize)
{
	SET_SCOPE;
	if (index >= mEntries.size()) {
		RETURN false;
	}

	if (offset + bufSize > mEntries[index].fileSize) {
		RETURN false;
	}

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

////////////////////////////////////////////////////////////////////////////////
// Decode MW3 555 image

bool Decode_MW3_555(unsigned char* mem, size_t size, Texture& texture, bool flipY)
{
	if (size < 16) {
		return NULL;
	}

	SET_SCOPE;
	bool hasAlpha = (mem[0] != 5);

	WORD w = *((WORD*)(&mem[4]));
	WORD h = *((WORD*)(&mem[6]));

	if (size < (size_t)2 * w * h + 16) {
		RETURN false;
	}

	if (hasAlpha) {
		texture.resize(w+1, h+1, GL_RGBA);
	} else {
		texture.resize(w+1, h+1, GL_RGB);
	}

	for (TextureDim x = 0; x < w; x++) {
		for (TextureDim y = 0; y < h; y++) {
			size_t index = 2 * (y * w + x) + 16;
			unsigned char b1 = mem[index++];
			unsigned char b2 = mem[index++];

			// r5g6b5
			unsigned char b = b1 & 31;
			unsigned char g = (b1 >> 5) + ((b2 & 7) << 3);
			unsigned char r = b2 & 248;
			unsigned char a = 255;
			if ((b2 & 1) == 0)
				a = 0;

			if (flipY) {
				texture.setRGBA(x, h-y-1, r, g << 2, b << 3, a);
			} else {
				texture.setRGBA(x, y, r, g << 2, b << 3, a);
			}
		}
	}

	RETURN true;
}

