/*
 * This file is part of OpenTTD.
 * OpenTTD 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, version 2.
 * OpenTTD 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 OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
 */

/** @file random_access_file.cpp Actual implementation of the RandomAccessFile class. */

#include "stdafx.h"
#include "random_access_file_type.h"

#include "debug.h"
#include "error_func.h"
#include "fileio_func.h"
#include "string_func.h"

#include "safeguards.h"

/**
 * Create the RandomAccesFile.
 * @param filename Name of the file at the disk.
 * @param subdir   The sub directory to search this file in.
 */
RandomAccessFile::RandomAccessFile(std::string_view filename, Subdirectory subdir) : filename(filename)
{
	size_t file_size;
	this->file_handle = FioFOpenFile(filename, "rb", subdir, &file_size);
	if (!this->file_handle.has_value()) UserError("Cannot open file '{}'", filename);

	/* When files are in a tar-file, the begin of the file might not be at 0. */
	long pos = ftell(*this->file_handle);
	if (pos < 0) UserError("Cannot read file '{}'", filename);

	/* Make a note of start and end position for readers who check bounds. */
	this->start_pos = pos;
	this->end_pos = this->start_pos + file_size;

	/* Store the filename without path and extension */
	auto t = filename.rfind(PATHSEPCHAR);
	std::string name_without_path{filename.substr(t != std::string::npos ? t + 1 : 0)};
	this->simplified_filename = name_without_path.substr(0, name_without_path.rfind('.'));
	strtolower(this->simplified_filename);

	this->SeekTo(static_cast<size_t>(pos), SEEK_SET);
}

/**
 * Get the filename of the opened file with the path from the SubDirectory and the extension.
 * @return Name of the file.
 */
const std::string &RandomAccessFile::GetFilename() const
{
	return this->filename;
}

/**
 * Get the simplified filename of the opened file. The simplified filename is the name of the
 * file without the SubDirectory or extension in lower case.
 * @return Name of the file.
 */
const std::string &RandomAccessFile::GetSimplifiedFilename() const
{
	return this->simplified_filename;
}

/**
 * Get position in the file.
 * @return Position in the file.
 */
size_t RandomAccessFile::GetPos() const
{
	return this->pos + (this->buffer - this->buffer_end);
}

/**
 * Test if we have reached the end of the file.
 * @return True iff the current position as at or after the end of the file.
 */
bool RandomAccessFile::AtEndOfFile() const
{
	return this->GetPos() >= this->GetEndPos();
}

/**
 * Seek in the current file.
 * @param pos New position.
 * @param mode Type of seek (\c SEEK_CUR means \a pos is relative to current position, \c SEEK_SET means \a pos is absolute).
 */
void RandomAccessFile::SeekTo(size_t pos, int mode)
{
	if (mode == SEEK_CUR) pos += this->GetPos();

	this->pos = pos;
	if (fseek(*this->file_handle, this->pos, SEEK_SET) < 0) {
		Debug(misc, 0, "Seeking in {} failed", this->filename);
	}

	/* Reset the buffer, so the next ReadByte will read bytes from the file. */
	this->buffer = this->buffer_end = this->buffer_start;
}

/**
 * Read a byte from the file.
 * @return Read byte.
 */
uint8_t RandomAccessFile::ReadByte()
{
	if (this->buffer == this->buffer_end) {
		this->buffer = this->buffer_start;
		size_t size = fread(this->buffer, 1, RandomAccessFile::BUFFER_SIZE, *this->file_handle);
		this->pos += size;
		this->buffer_end = this->buffer_start + size;

		if (size == 0) return 0;
	}
	return *this->buffer++;
}

/**
 * Read a word (16 bits) from the file (in low endian format).
 * @return Read word.
 */
uint16_t RandomAccessFile::ReadWord()
{
	uint8_t b = this->ReadByte();
	return (this->ReadByte() << 8) | b;
}

/**
 * Read a double word (32 bits) from the file (in low endian format).
 * @return Read word.
 */
uint32_t RandomAccessFile::ReadDword()
{
	uint b = this->ReadWord();
	return (this->ReadWord() << 16) | b;
}

/**
 * Read a block.
 * @param ptr  Destination buffer.
 * @param size Number of bytes to read.
 */
void RandomAccessFile::ReadBlock(void *ptr, size_t size)
{
	if (this->buffer != this->buffer_end) {
		size_t to_copy = std::min<size_t>(size, this->buffer_end - this->buffer);
		std::copy_n(this->buffer, to_copy, static_cast<uint8_t *>(ptr));
		this->buffer += to_copy;
		size -= to_copy;
		if (size == 0) return;
		ptr = static_cast<char *>(ptr) + to_copy;
	}

	this->pos += fread(ptr, 1, size, *this->file_handle);
}

/**
 * Skip \a n bytes ahead in the file.
 * @param n Number of bytes to skip reading.
 */
void RandomAccessFile::SkipBytes(size_t n)
{
	assert(this->buffer_end >= this->buffer);
	size_t remaining = this->buffer_end - this->buffer;
	if (n <= remaining) {
		this->buffer += n;
	} else {
		this->SeekTo(n, SEEK_CUR);
	}
}
