////////////////////////////////////////////////////////////////////////////////
// MechWarrior3AnimFile.cpp
// Parser for MW3 anim.zbd files
// Copyright Bjoern Ganster
// Developed since 2022
////////////////////////////////////////////////////////////////////////////////

#include "BGBase.h"
#include "Archive.h"
#include "MechWarrior3AnimFile.h"
#include "XMLTree.h"

#include <iostream>

#define FILE_NUMBER FILE_NO_MECHWARRIOR3_ANIM_FILE_CPP

////////////////////////////////////////////////////////////////////////////////
// Implementation of an archive class for anim.zbd files

// Constructor
MechWarrior3AnimFile::MechWarrior3AnimFile()
: MWArchive("MechWarrior3AnimFile", MechWarrior3AnimFile_ID)
{
}

// Destructor
MechWarrior3AnimFile::~MechWarrior3AnimFile()
{
}

struct PostTocData {
	uint32_t zeroes[2];
	uint16_t zero8;
	uint16_t count; // What does it count? Not the number of records here ...
	uint32_t unknown[4];
	float gravity;
	uint32_t zeroes2[47];
	uint32_t unknown2;
	uint32_t zeroes3[56];
};

struct AnimData {
	char name1[32], name2[32];
	uint32_t animPtr;
	uint8_t animRoot[32];
	uint32_t animRootPtr;
	uint32_t zero;
	uint32_t unknownPtr;
	uint8_t zeroes2[36];
	uint32_t flags;
	uint8_t unk152;
	uint8_t animActivation;
	uint8_t unknown154[2];
	float execRangeMin, execRangeMax, resetTime, unk168, maxHealth, currentHealth;
	uint32_t zeroes180[4];
	uint32_t sequenceDefinitionPtr;
	char unknown200[56];
	uint32_t resetSequencePtr, resetSequenceSize;
	uint8_t sequenceDefinitionCount, objectCount, nodeCount, lightCount, pufferCount, dynamicSoundCount, staticSoundCount, knownCount;
	uint8_t activePrereqCount, activePrereqToSatisfy, animRefCount, unk275;
	uint32_t objectsPtr, nodesPtr, lightsPtr, puffersPtr, dynamicSoundsPtr, staticSoundsPtr, unknownPtr2, activePrereqsPtr, animRefsPtr, unk312;
};

struct EventHeader {
	uint8_t eventType;
	uint8_t startOffset;
	uint16_t pad02;
	uint32_t size;
	float startTime;
};

struct ActivationPrerequisiteCondition {
	uint32_t optional;
	uint32_t conditionType;
	uint8_t data[40];
};

// Open file
bool MechWarrior3AnimFile::open(const PathChar* PRJFN, bool readOnly)
{
	SET_SCOPE;

	if (!mMb.loadFromFile(PRJFN)) {
		RETURN false;
	}

	int AnimHeaderSize = 12;
	int tocEntrySize = 84; // the same for MW3 and MW3:PM
	auto fileListEntryCount = mMb.get<uint32_t>(8);
	int MW3PufferSize = 44;

	// Check magic number and version
	if (mMb.get<uint32_t>(0) == 0x08170616) {
		headerVersion = mMb.get<uint32_t>(4);
		if (headerVersion == 50) {
			// MW3:PM
			AnimHeaderSize = 16;
			tocEntrySize = 84;
			fileListEntryCount = mMb.get<uint32_t>(12);
			MW3PufferSize = 44; // ???
		} else if (headerVersion != 0x27) {
			// MW3
			RETURN false;
		}
	} else {
		RETURN false;
	}

	// There is a list of files at the start of the file, but there are no file offsets stored that location, so I don't know
	// what to do with that list ... safe to ignore?
	// The list could be used to store pointers if direct access to the nodes is desired ...
	if (fileListEntryCount < 1 || fileListEntryCount * tocEntrySize + AnimHeaderSize > mMb.getSize()) {
		RETURN false;
	}

	// Extract data from the post TOC data structure ...
	SET_OP;
	size_t dataOffset = fileListEntryCount * tocEntrySize + AnimHeaderSize;
	PostTocData* postTocData = mMb.getPtr<PostTocData>(dataOffset, false);

	if (postTocData == nullptr) {
		RETURN false;
	}

	dataOffset += sizeof(PostTocData);
	animDataOffset = dataOffset;
	animDataCount = postTocData->count;

	if (animDataOffset + animDataCount * sizeof(AnimData) > mMb.getSize()) {
		// Something's wrong ... either correct the anim data count or just abort ...
		SET_OP;
		COUT << "EOF reached, aborting" << std::endl;
		animDataCount = (mMb.getSize() - animDataOffset) / sizeof(AnimData);
	}

	// We have to walk over the data structures ...
	// Based on the assumption that at offset 200 within the structure, we can always find the string "RESET_SEQUENCE", look for the next record
	size_t count = 0, lastOffset = 0, ouches = 0, jumpTotal = 0;
	while (/*count < animDataCount &&*/ dataOffset + sizeof(AnimData) < mMb.getSize()) {
		SET_OP;
		AnimData* animDataPtr = mMb.getPtr<AnimData>(dataOffset, false);
		if (animDataPtr == nullptr) {
			RETURN false;
		}

		if (animDataPtr->zero != 0 
		|| animDataPtr->animPtr == 0
		|| animDataPtr->resetSequenceSize > 5 * 1024)
		{
			SET_OP;
			COUT << "Noise at offset ";
			BGCPString::fromInt(dataOffset, 16).printLn();
			lastOffset = dataOffset;
			++ouches;
			dataOffset += 201;
		} else {
			SET_OP;
			/*COUT << "Signal at offset ";
			BGCPString::fromInt(dataOffset, 16).print();
			COUT << ", ";
			BGCPString::fromInt(dataOffset-lastOffset, 16).print();
			COUT << " bytes forward. ";*/

			/*if (animDataPtr->flags > 0) {
				COUT << "Flags: ";
				BGCPString::fromInt(animDataPtr->flags, 2).print();
				COUT << "=";
				for (int i = 0; i < 30; ++i) {
					if (((animDataPtr->flags & (1 << i)) > 0)) {
						COUT << i << ",";
					}
				}
			}*/

			/*if (animDataPtr->objectCount > 0) {
				COUT << animDataPtr->objectCount << " objects, ";
			}
			if (animDataPtr->nodeCount > 0) {
				COUT << animDataPtr->nodeCount << " nodes, ";
			}
			if (animDataPtr->lightCount > 0) {
				COUT << animDataPtr->lightCount << " lights, ";
			}
			if (animDataPtr->pufferCount > 0) {
				COUT << animDataPtr->pufferCount << " puffers, ";
			}
			if (animDataPtr->staticSoundCount > 0) {
				COUT << animDataPtr->staticSoundCount << " sounds, ";
			}
			if (animDataPtr->activePrereqCount > 0) {
				COUT << animDataPtr->activePrereqCount << " prereqs, ";
			}
			if (animDataPtr->animRefCount > 0) {
				COUT << animDataPtr->animRefCount << " animrefs, ";
			}
			if (animDataPtr->sequenceDefinitionCount > 0) {
				COUT << animDataPtr->sequenceDefinitionCount << " sequences, ";
			}

			COUT << std::endl;*/

			SET_OP;
			lastOffset = dataOffset;
			dataOffset += sizeof(AnimData) + 96*animDataPtr->objectCount + 40*animDataPtr->nodeCount + 44*animDataPtr->lightCount +
				MW3PufferSize * animDataPtr->pufferCount + 36 * animDataPtr->staticSoundCount + 48 * animDataPtr->activePrereqCount +
				72 * animDataPtr->animRefCount + animDataPtr->resetSequenceSize; // 64 * animDataPtr->sequenceDefinitionCount;

			size_t i = 0;
			while (i < animDataPtr->sequenceDefinitionCount && dataOffset < mMb.getSize()) {
				SET_OP;
				auto eventHeader = mMb.getPtr<EventHeader>(dataOffset, false);
				if (eventHeader != nullptr && dataOffset + eventHeader->size <= mMb.getSize()) {
					dataOffset += eventHeader->size;
				} else {
					dataOffset = mMb.getSize();
				}
				++i;
			}

			SET_OP;
			++count;
		}

		SET_OP;
		size_t pos = 0;

		if (dataOffset - lastOffset > 1024 * 1024) {
			COUT << "Alert: jump of " << dataOffset - lastOffset << " bytes aborted from offset " << lastOffset << std::endl;
			//dataOffset = lastOffset + 400;
		}

		if (dataOffset < mMb.getSize()) {
			SET_OP;
			dataOffset += 201;
			char* resetSequence = mMb.getPtr<char>(dataOffset, false);
			if (resetSequence == nullptr) {
				RETURN false;
			}
			if (bgtextscan(resetSequence, "RESET_SEQUENCE", 0, mMb.getSize() - dataOffset, pos)) {
				if (pos > 0) {
					jumpTotal += pos;
					//COUT << "Jumping forward by " << pos << " bytes\n";
				}
				dataOffset += pos-200;
			} else {
				count = animDataCount;
				dataOffset = mMb.getSize();
			}
		}
	}

	SET_OP;
	COUT << count <<"/" << animDataCount << " records found, " << ouches << " noises, " << jumpTotal << " bytes jumped. " << 
		100.0 * jumpTotal / mMb.getSize() << "% unexplained. " << std::endl;
	RETURN true;
}

// Close file
void MechWarrior3AnimFile::close()
{
}

// Read bufSize bytes from file numbered index at offset into buf
bool MechWarrior3AnimFile::getFileChunk(size_t index, size_t offset, void* buf, size_t bufSize)
{
	return false;
}

// Enter subdirectory
bool MechWarrior3AnimFile::enterSubDir(size_t /*index*/)
{
	return false;
}

bool MechWarrior3AnimFile::enterSubDir(const char* /*dirName*/)
{
	return false;
}

// Leave subdirectory
bool MechWarrior3AnimFile::leaveDir()
{
	return false;
}

// Check if currently in sub directory
bool MechWarrior3AnimFile::inSubDir() const
{
	return false;
}

// Replace file
bool MechWarrior3AnimFile::replaceFile(size_t /*fileNum*/, const PathChar* /*srcFN*/)
{
	return false;
}

// Check if archive type supports file replacement
bool MechWarrior3AnimFile::replaceFilePossible() const
{
	return false;
}

// Check if entry index is a directory
bool MechWarrior3AnimFile::isDirectory(size_t /*index*/) const
{
	return false;
}

// Add files to archive
void MechWarrior3AnimFile::addFileToArchive(const PathChar* /*FN*/)
{
}

bool MechWarrior3AnimFile::canAddFilesToArchive() const
{
	return false;
}

