#include "stdafx.h"
#include "StudyBoxLoader.h"
#include "RomData.h"
#include "MapperFactory.h"
#include "../Utilities/FolderUtilities.h"
#include "../Utilities/HexUtilities.h"

uint32_t StudyBoxLoader::ReadInt(uint8_t*& data)
{
	uint32_t val = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
	data += 4;
	return val;
}

string StudyBoxLoader::ReadFourCC(uint8_t*& data)
{
	stringstream ss;
	for(int i = 0; i < 4; i++) {
		ss << (char)data[i];
	}
	data += 4;
	return ss.str();
}

vector<uint8_t> StudyBoxLoader::ReadArray(uint8_t*& data, uint32_t length)
{
	vector<uint8_t> out;
	out.resize(length);
	memcpy(out.data(), data, length);
	data += length;
	return out;
}

bool StudyBoxLoader::LoadStudyBoxTape(vector<uint8_t> studyBoxFile, StudyBoxData& studyBoxData)
{
	uint8_t* data = studyBoxFile.data();
	uint8_t* end = data + studyBoxFile.size();

	if(end - data < 16) {
		//File too small to parse
		Log("[Study Box] File is too small to parse");
		return false;
	}

	if(ReadFourCC(data) != "STBX") {
		//Invalid file!
		Log("[Study Box] Invalid studybox file");
		return false;
	}

	uint32_t size = ReadInt(data);
	if(size != 4) {
		//Should be 4 bytes, with only a version field
		Log("[Study Box] Unexpected length value");
		return false;
	}

	uint32_t version = ReadInt(data);
	if(version != 0x100) {
		//Unsupported version
		Log("[Study Box] Unsupported version: " + std::to_string(version));
		return false;
	}

	uint32_t prevAudioOffset = 0;
	uint32_t prevLeadInOffset = 0;
	while(data < end - 4) {
		string cc = ReadFourCC(data);

		if(cc == "PAGE") {
			uint32_t pageSize = ReadInt(data);
			uint32_t leadInOffset = ReadInt(data);
			uint32_t audioOffset = ReadInt(data);

			if(audioOffset < leadInOffset) {
				//Invalid file, lead in must start before the track
				Log("[Study Box] Track lead in must start before the first bit of data");
				return false;
			}

			if(audioOffset < prevAudioOffset || leadInOffset < prevLeadInOffset) {
				//Invalid file, page chunks must be in the order found on the tape
				Log("[Study Box] PAGE chunks must be in the order found on the audio tape");
				return false;
			}

			prevAudioOffset = audioOffset;
			prevLeadInOffset = leadInOffset;

			if((end - data) >= pageSize - 8) {
				vector<uint8_t> pageData = ReadArray(data, pageSize - 8);
				studyBoxData.Pages.push_back({ leadInOffset, audioOffset, pageData });
			} else {
				//Invalid size value
				Log("[Study Box] Invalid size value for PAGE chunk");
				return false;
			}
		} else if(cc == "AUDI") {
			uint32_t size = ReadInt(data);
			uint32_t fileType = ReadInt(data);
			if(fileType == 0) {
				if((end - data) >= size - 4) {
					studyBoxData.AudioFile = ReadArray(data, size - 4);
					//AUDI chunk should be the last in the file
					break;
				} else {
					Log("[Study Box] Invalid size value for AUDI chunk");
					return false;
				}
			} else {
				//Unsupported audio type
				Log("[Study Box] Unsupported audio type: " + std::to_string(fileType));
				return false;
			}
		} else {
			//Unsupported tag
			Log("[Study Box] Unsupported tag");
			return false;
		}
	}

	return studyBoxData.Pages.size() > 0;
}

vector<uint8_t> StudyBoxLoader::LoadBios()
{
	vector<uint8_t> biosData;
	ifstream biosFile(FolderUtilities::CombinePath(FolderUtilities::GetHomeFolder(), "StudyBox.bin"), ios::in | ios::binary);
	if(biosFile) {
		return vector<uint8_t>(std::istreambuf_iterator<char>(biosFile), {});
	}
	return {};
}

void StudyBoxLoader::LoadRom(RomData& romData, vector<uint8_t>& romFile, string filepath)
{
	romData.Info.Hash.PrgCrc32 = romData.Info.Hash.Crc32;

	//Cheat for SHA1 hash (repeat crc32 5 times) - this is to avoid the performance penalty of running SHA1 on a large file
	string crc32String = HexUtilities::ToHex(romData.Info.Hash.Crc32);
	romData.Info.Hash.Sha1 = crc32String + crc32String + crc32String + crc32String + crc32String;

	romData.Info.Format = RomFormat::StudyBox;
	romData.Info.MapperID = MapperFactory::StudyBoxMapperID;
	romData.Info.Mirroring = MirroringType::Vertical;
	romData.PrgRom = LoadBios();
	romData.Info.System = GameSystem::Famicom;

	if(romData.PrgRom.size() != 0x40000) {
		romData.Error = true;
		romData.BiosMissing = true;
	} else {
		romData.StudyBox.FileName = filepath;
		if(!LoadStudyBoxTape(romFile, romData.StudyBox)) {
			romData.Error = true;
		}
	}
}
