/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/util.h"
#include "titanic/support/simple_file.h"

namespace Titanic {

CString readStringFromStream(Common::SeekableReadStream *s) {
	CString result;
	char c;
	while ((c = s->readByte()) != '\0')
		result += c;

	return result;
}

/*------------------------------------------------------------------------*/

bool File::open(const Common::String &filename) {
	if (!Common::File::open(filename))
		error("Could not open file - %s", filename.c_str());
	return true;
}

/*------------------------------------------------------------------------*/

SimpleFile::SimpleFile(): _inStream(nullptr), _outStream(nullptr), _lineCount(1) {
}

SimpleFile::~SimpleFile() {
	close();
}

void SimpleFile::open(Common::SeekableReadStream *stream) {
	close();
	_inStream = stream;
}

void SimpleFile::open(Common::OutSaveFile *stream) {
	close();
	_outStream = stream;
}

void SimpleFile::close() {
	if (_outStream) {
		_outStream->finalize();
		delete _outStream;
		_outStream = nullptr;
	}

	if (_inStream) {
		delete _inStream;
		_inStream = nullptr;
	}
}

void SimpleFile::safeRead(void *dst, size_t count) {
	if (unsafeRead(dst, count) != count)
		error("Could not read %d bytes", (int)count);
}

size_t SimpleFile::unsafeRead(void *dst, size_t count) {
	assert(_inStream);
	return _inStream->read(dst, count);
}

size_t SimpleFile::write(const void *src, size_t count) const {
	assert(_outStream);
	return _outStream->write(src, count);
}

void SimpleFile::seek(int offset, int origin) {
	assert(_inStream);
	_inStream->seek(offset, origin);
}

byte SimpleFile::readByte() {
	byte b;
	safeRead(&b, 1);
	return b;
}

uint SimpleFile::readUint16LE() {
	uint val;
	safeRead(&val, 2);
	return READ_LE_UINT16(&val);
}

uint SimpleFile::readUint32LE() {
	uint val;
	safeRead(&val, 4);
	return READ_LE_UINT32(&val);
}

CString SimpleFile::readString() {
	char c;
	CString result;
	bool backslashFlag = false;

	// First skip any spaces
	do {
		safeRead(&c, 1);
	} while (Common::isSpace(c));

	// Ensure we've found a starting quote for the string
	if (c != '"')
		error("Could not find starting quote");

	bool endFlag = false;
	while (!endFlag) {
		// Read the next character
		safeRead(&c, 1);

		if (backslashFlag) {
			backslashFlag = false;
			switch (c) {
			case 'n':
				result += '\n';
				break;
			case 'r':
				result += '\r';
				break;
			case '\t':
				result += '\t';
				break;
			default:
				result += c;
				break;
			}
		} else {
			switch (c) {
			case '"':
				endFlag = true;
				break;
			case '\\':
				backslashFlag = true;
				break;
			default:
				result += c;
				break;
			}
		}
	}

	// Return the string
	return result;
}

int SimpleFile::readNumber() {
	char c;
	int result = 0;
	bool minusFlag = false;

	// First skip any spaces
	do {
		safeRead(&c, 1);
	} while (Common::isSpace(c));

	// Check for prefix sign
	if (c == '+' || c == '-') {
		minusFlag = c == '-';
		safeRead(&c, 1);
	}

	// Read in the number
	if (!Common::isDigit(c))
		error("Invalid number");

	while (Common::isDigit(c)) {
		result = result * 10 + (c - '0');
		safeRead(&c, 1);
	}

	// Finally, if it's a minus value, then negate it
	if (minusFlag)
		result = -result;

	return result;
}

double SimpleFile::readFloat() {
	char c;
	Common::String result;

	// First skip any spaces
	do {
		safeRead(&c, 1);
	} while (Common::isSpace(c));

	// Check for prefix sign
	if (c == '+' || c == '-') {
		result += c;
		safeRead(&c, 1);
	}

	// Read in the number
	if (!Common::isDigit(c))
		error("Invalid number");

	while (Common::isDigit(c) || c == '.') {
		result += c;
		safeRead(&c, 1);
	}

	// Convert to a float and return it
	float floatValue;
	sscanf(result.c_str(), "%f", &floatValue);

	return floatValue;
}

Point SimpleFile::readPoint() {
	Point pt;
	pt.x = readNumber();
	pt.y = readNumber();

	return pt;
}

Rect SimpleFile::readRect() {
	Rect r;
	r.left = readNumber();
	r.top = readNumber();
	r.right = readNumber();
	r.bottom = readNumber();

	return r;
}

Rect SimpleFile::readBounds() {
	Rect r;
	r.left = readNumber();
	r.top = readNumber();
	r.setWidth(readNumber());
	r.setHeight(readNumber());

	return r;
}

void SimpleFile::readBuffer(char *buffer, size_t count) {
	CString tempString = readString();
	if (buffer) {
		strncpy(buffer, tempString.c_str(), count);
		buffer[count - 1] = '\0';
	}
}

void SimpleFile::writeUint16LE(uint val) {
	byte lo = val & 0xff;
	byte hi = (val >> 8) & 0xff;
	write(&lo, 1);
	write(&hi, 1);
}

void SimpleFile::writeUint32LE(uint val) {
	uint16 lo = val & 0xffff;
	uint16 hi = (val >> 16) & 0xff;
	writeUint16LE(lo);
	writeUint16LE(hi);
}

void SimpleFile::writeLine(const CString &str) const {
	write(str.c_str(), str.size());
	write("\r\n", 2);
}

void SimpleFile::writeString(const CString &str) const {
	if (str.empty())
		return;

	const char *msgP = str.c_str();
	char c;

	while ((c = *msgP++) != '\0') {
		switch (c) {
		case '\r':
			write("\\r", 2);
			break;
		case '\n':
			write("\\n", 2);
			break;
		case '\t':
			write("\\t", 2);
			break;
		case '\"':
			write("\\\"", 2);
			break;
		case '\\':
			write("\\\\", 2);
			break;
		case '{':
			write("\\{", 2);
			break;
		case '}':
			write("\\}", 2);
			break;
		default:
			write(&c, 1);
			break;
		}
	}
}

void SimpleFile::writeQuotedString(const CString &str) const {
	write("\"", 1);
	writeString(str);
	write("\" ", 2);
}

void SimpleFile::writeQuotedLine(const CString &str, int indent) const {
	writeIndent(indent);
	writeQuotedString(str);
	write("\n", 1);
}

void SimpleFile::writeNumber(int val) const {
	CString str = CString::format("%d ", val);
	write(str.c_str(), str.size());
}

void SimpleFile::writeNumberLine(int val, int indent) const {
	writeIndent(indent);
	writeNumber(val);
	write("\n", 1);
}

void SimpleFile::writeFloat(double val) const {
	Common::String valStr = Common::String::format("%f ", val);
	write(valStr.c_str(), valStr.size());
}

void SimpleFile::writeFloatLine(double val, int indent) const {
	writeIndent(indent);
	writeFloat(val);
	write("\n", 1);
}

void SimpleFile::writePoint(const Point &pt, int indent) const {
	writeIndent(indent);
	writeNumber(pt.x);
	writeNumber(pt.y);
	write("\n", 1);
}

void SimpleFile::writeRect(const Rect &r, int indent) const {
	writePoint(Point(r.left, r.top), indent);
	writePoint(Point(r.right, r.bottom), indent);
}

void SimpleFile::writeBounds(const Rect &r, int indent) const {
	writePoint(Point(r.left, r.top), indent);
	writePoint(Point(r.width(), r.height()), indent);
}

void SimpleFile::writeFormat(const char *format, ...) const {
	// Convert the format specifier and params to a string
	va_list va;
	va_start(va, format);
	CString line = CString::vformat(format, va);
	va_end(va);

	// Write out the string
	write(format, strlen(format));
}

void SimpleFile::writeIndent(uint indent) const {
	for (uint idx = 0; idx < indent; ++idx)
		write("\t", 1);
}

bool SimpleFile::isClassStart() {
	char c;

	do {
		safeRead(&c, 1);
	} while (Common::isSpace(c));

	assert(c == '{' || c == '}');
	return c == '{';
}

void SimpleFile::writeClassStart(const CString &classStr, int indent) {
	write("\n", 1);
	writeIndent(indent);
	write("{\n", 2);
	writeIndent(indent + 1);
	writeQuotedString(classStr);
	write("\n", 1);
}

void SimpleFile::writeClassEnd(int indent) {
	writeIndent(indent);
	write("}\n", 2);
}

bool SimpleFile::scanf(const char *format, ...) {
	va_list va;
	va_start(va, format);
	char c;

	CString formatStr(format);
	while (!formatStr.empty()) {
		if (formatStr.hasPrefix(" ")) {
			formatStr.deleteChar(0);

			safeRead(&c, 1);
			if (!Common::isSpace(c)) {
				va_end(va);
				return false;
			}

			// Skip over whitespaces
			skipSpaces();
		} else if (formatStr.hasPrefix("%d")) {
			// Read in a number
			formatStr = CString(formatStr.c_str() + 2);
			int *param = (int *)va_arg(va, int *);
			*param = readNumber();

			if (!eos())
				_inStream->seek(-1, SEEK_CUR);
		} else if (formatStr.hasPrefix("%s")) {
			// Read in text until the next space
			formatStr = CString(formatStr.c_str() + 2);
			CString *str = (CString *)va_arg(va, CString *);
			str->clear();
			while (!eos() && !Common::isSpace(c = readByte()))
				*str += c;

			if (!eos())
				_inStream->seek(-1, SEEK_CUR);
		}
	}

	skipSpaces();
	va_end(va);
	return true;
}

void SimpleFile::skipSpaces() {
	char c = ' ';
	while (!eos() && Common::isSpace(c))
		safeRead(&c, 1);

	if (!eos())
		_inStream->seek(-1, SEEK_CUR);
}

/*------------------------------------------------------------------------*/

bool StdCWadFile::open(const Common::String &filename) {
	Common::File f;
	CString name = filename;

	// Check for whether it is indeed a file/resource pair
	int idx = name.indexOf('#');

	if (idx < 0) {
		// Nope, so open up file for standard reading
		assert(!name.empty());
		if (!f.open(name))
			return false;

		SimpleFile::open(f.readStream(f.size()));
		f.close();
		return true;
	}

	// Split up the name and resource, and get the resource index
	CString fname = name.left(idx) + ".st";
	int extPos = name.lastIndexOf('.');
	CString resStr = name.mid(idx + 1, extPos - idx - 1);
	int resIndex = resStr.readInt();

	// Open up the index for access
	if (!f.open(fname))
		return false;
	int indexSize = f.readUint32LE() / 4;
	assert(resIndex < indexSize);

	// Get the specific resource's offset, and size by also
	// getting the offset of the following resource
	f.seek(resIndex * 4);
	uint resOffset = f.readUint32LE();
	uint nextOffset = (resIndex == (indexSize - 1)) ? f.size() :
		f.readUint32LE();

	// Read in the resource
	f.seek(resOffset);
	Common::SeekableReadStream *stream = f.readStream(nextOffset - resOffset);
	SimpleFile::open(stream);

	f.close();
	return true;
}

} // End of namespace Titanic