/*
	Copyright 2022 flyinghead

	This file is part of Flycast.

    Flycast is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    Flycast is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Flycast.  If not, see <https://www.gnu.org/licenses/>.
 */
#include "isofs.h"
#include "iso9660.h"

#include <cstring>

static u32 decode_iso733(iso733_t v)
{
	return ((v >> 56) & 0x000000FF)
			| ((v >> 40) & 0x0000FF00)
			| ((v >> 24) & 0x00FF0000)
			| ((v >> 8) & 0xFF000000);
}

IsoFs::IsoFs(Disc *disc) : disc(disc)
{
	baseFad = disc->GetBaseFAD();
}

IsoFs::Directory *IsoFs::getRoot()
{
	u8 temp[2048];
	disc->ReadSectors(baseFad + 16, 1, temp, 2048);
	// Primary Volume Descriptor
	const iso9660_pvd_t *pvd = (const iso9660_pvd_t *)temp;

	Directory *root = new Directory(this);
	if (pvd->type == 1 && !memcmp(pvd->id, ISO_STANDARD_ID, strlen(ISO_STANDARD_ID)) && pvd->version == 1)
	{
		u32 lba = decode_iso733(pvd->root_directory_record.extent);
		u32 len = decode_iso733(pvd->root_directory_record.size);

		len = ((len + 2047) / 2048) * 2048;
		root->data.resize(len);

		DEBUG_LOG(GDROM, "iso9660 root directory FAD: %d, len: %d", 150 + lba, len);
		disc->ReadSectors(150 + lba, len / 2048, root->data.data(), 2048);
	}
	else {
		WARN_LOG(GDROM, "iso9660 PVD NOT found");
		root->data.resize(1);
		root->data[0] = 0;
	}
	return root;
}

IsoFs::Entry *IsoFs::Directory::getEntry(const std::string& name)
{
	std::string isoname = name + ';';
	for (u32 i = 0; i < data.size(); )
	{
		const iso9660_dir_t *dir = (const iso9660_dir_t *)&data[i];
		if (dir->length == 0)
			break;

		if ((u8)dir->filename.str[0] > isoname.size()
				&& memcmp(dir->filename.str + 1, isoname.c_str(), isoname.size()) == 0)
		{
			DEBUG_LOG(GDROM, "Found %s at offset %X", name.c_str(), i);
			u32 startFad = decode_iso733(dir->extent) + 150;
			u32 len = decode_iso733(dir->size);
			if ((dir->file_flags & ISO_DIRECTORY) == 0)
			{
				File *file = new File(fs);
				file->startFad = startFad;
				file->len = len;

				return file;
			}
			else
			{
				Directory *directory = new Directory(fs);
				directory->data.resize(len);
				fs->disc->ReadSectors(startFad, len / 2048, directory->data.data(), 2048);

				return directory;
			}
		}
		i += dir->length;
	}
	return nullptr;
}

u32 IsoFs::File::read(u8 *buf, u32 size, u32 offset) const
{
	size = std::min(size, len - offset);
	u32 sectors = size / 2048;
	fs->disc->ReadSectors(startFad + offset / 2048, sectors, buf, 2048);
	size -= sectors * 2048;
	if (size > 0)
	{
		u8 temp[2048];
		fs->disc->ReadSectors(startFad + offset / 2048 + sectors, 1, temp, 2048);
		memcpy(buf + sectors * 2048, temp, size);
	}
	return sectors * 2048 + size;
}
