/*
 * This file is part of liblcf. Copyright (c) 2020 liblcf authors.
 * https://github.com/EasyRPG/liblcf - https://easyrpg.org
 *
 * liblcf is Free/Libre Open Source Software, released under the MIT License.
 * For the full copyright and license information, please view the COPYING
 * file that was distributed with this source code.
 */

#include <string>
#include <vector>
#include "reader_struct.h"
#include "rpg_eventcommand.h"

template <>
struct RawStruct<RPG::EventCommand> {
	static void ReadLcf(RPG::EventCommand& ref, LcfReader& stream, uint32_t length);
	static void WriteLcf(const RPG::EventCommand& ref, LcfWriter& stream);
	static int LcfSize(const RPG::EventCommand& ref, LcfWriter& stream);
	static void WriteXml(const RPG::EventCommand& ref, XmlWriter& stream);
	static void BeginXml(RPG::EventCommand& ref, XmlReader& stream);
};

template <>
struct RawStruct<std::vector<RPG::EventCommand> > {
	static void ReadLcf(std::vector<RPG::EventCommand>& ref, LcfReader& stream, uint32_t length);
	static void WriteLcf(const std::vector<RPG::EventCommand>& ref, LcfWriter& stream);
	static int LcfSize(const std::vector<RPG::EventCommand>& ref, LcfWriter& stream);
	static void WriteXml(const std::vector<RPG::EventCommand>& ref, XmlWriter& stream);
	static void BeginXml(std::vector<RPG::EventCommand>& ref, XmlReader& stream);
};

/**
 * Reads Event Command.
 */
void RawStruct<RPG::EventCommand>::ReadLcf(RPG::EventCommand& event_command, LcfReader& stream, uint32_t /* length */) {
	stream.Read(event_command.code);
	if (event_command.code != 0) {
		stream.Read(event_command.indent);
		stream.ReadString(event_command.string, stream.ReadInt());

		auto& param_buf = stream.IntBuffer();

		param_buf.clear();
		for (int i = stream.ReadInt(); i > 0; i--) {
			param_buf.push_back(stream.ReadInt());
		}
		if (!param_buf.empty()) {
			event_command.parameters.reserve(param_buf.size());
			event_command.parameters.assign(param_buf.begin(), param_buf.end());
		}
	}
}

void RawStruct<RPG::EventCommand>::WriteLcf(const RPG::EventCommand& event_command, LcfWriter& stream) {
	stream.Write(event_command.code);
	stream.Write(event_command.indent);
	stream.WriteInt(stream.Decode(event_command.string).size());
	stream.Write(event_command.string);
	int32_t count = (int32_t)event_command.parameters.size();
	stream.Write(count);
	for (int i = 0; i < count; i++)
		stream.Write(event_command.parameters[i]);
}

int RawStruct<RPG::EventCommand>::LcfSize(const RPG::EventCommand& event_command, LcfWriter& stream) {
	int result = 0;
	result += LcfReader::IntSize(event_command.code);
	result += LcfReader::IntSize(event_command.indent);
	result += LcfReader::IntSize(stream.Decode(event_command.string).size());
	result += stream.Decode(event_command.string).size();
	int count = event_command.parameters.size();
	result += LcfReader::IntSize(count);
	for (int i = 0; i < count; i++)
		result += LcfReader::IntSize(event_command.parameters[i]);
	return result;
}

void RawStruct<RPG::EventCommand>::WriteXml(const RPG::EventCommand& event_command, XmlWriter& stream) {
	stream.BeginElement("EventCommand");
	stream.WriteNode<int32_t>("code", event_command.code);
	stream.WriteNode<int32_t>("indent", event_command.indent);
	stream.WriteNode<std::string>("string", event_command.string);
	stream.WriteNode<std::vector<int32_t>>("parameters", event_command.parameters);
	stream.EndElement("EventCommand");
}

class EventCommandXmlHandler : public XmlHandler {
private:
	RPG::EventCommand& ref;
	enum {
		None,
		Code,
		Indent,
		String,
		Parameters
	} field;
public:
	EventCommandXmlHandler(RPG::EventCommand& ref) : ref(ref), field(None) {}
	void StartElement(XmlReader& stream, const char* name, const char** /* atts */) {
		if (strcmp(name, "code") == 0)
			field = Code;
		else if (strcmp(name, "indent") == 0)
			field = Indent;
		else if (strcmp(name, "string") == 0)
			field = String;
		else if (strcmp(name, "parameters") == 0)
			field = Parameters;
		else {
			stream.Error("Unrecognized field '%s'", name);
			field = None;
		}
	}
	void EndElement(XmlReader& /* stream */, const char* /* name */) {
		field = None;
	}
	void CharacterData(XmlReader& /* stream */, const std::string& data) {
		switch (field) {
			case None:
				break;
			case Code:
				XmlReader::Read<int32_t>(ref.code, data);
				break;
			case Indent:
				XmlReader::Read<int32_t>(ref.indent, data);
				break;
			case String:
				XmlReader::Read<std::string>(ref.string, data);
				break;
			case Parameters:
				XmlReader::Read<std::vector<int32_t>>(ref.parameters, data);
				break;
		}
	}
};

void RawStruct<RPG::EventCommand>::BeginXml(RPG::EventCommand& ref, XmlReader& stream) {
	stream.SetHandler(new WrapperXmlHandler("EventCommand", new EventCommandXmlHandler(ref)));
}

/**
 * Reads event commands.
 */
void RawStruct<std::vector<RPG::EventCommand> >::ReadLcf(
	std::vector<RPG::EventCommand>& event_commands, LcfReader& stream, uint32_t length) {
	// Event Commands is a special array
	// Has no size information. Is terminated by 4 times 0x00.
	unsigned long startpos = stream.Tell();
	unsigned long endpos = startpos + length;

	// Since we don't know the number of event parameters without reading, we store
	// them all in a temporary buffer and then copy it to EventCommand::parameters.
	// This prevents extra allocations from repeated calls to push_back().
	for (;;) {
		uint8_t ch = (uint8_t)stream.Peek();
		if (ch == 0) {
			stream.Seek(4, LcfReader::FromCurrent);
			break;
		}

		if (stream.Tell() >= endpos) {
			stream.Seek(endpos, LcfReader::FromStart);
			fprintf(stderr, "Event command corrupted at %" PRIu32 "\n", stream.Tell());
			for (;;) {
				// Try finding the real end of the event command (4 0-bytes)
				int i = 0;
				for (; i < 4; ++i) {
					stream.Read(ch);

					if (ch != 0) {
						break;
					}
				}

				if (i == 4 || stream.Eof()) {
					break;
				}
			}

			break;
		}

		RPG::EventCommand command;
		RawStruct<RPG::EventCommand>::ReadLcf(command, stream, 0);
		event_commands.push_back(command);
	}
}

void RawStruct<std::vector<RPG::EventCommand> >::WriteLcf(const std::vector<RPG::EventCommand>& event_commands, LcfWriter& stream) {
	int count = event_commands.size();
	for (int i = 0; i < count; i++)
		RawStruct<RPG::EventCommand>::WriteLcf(event_commands[i], stream);
	for (int i = 0; i < 4; i++)
		stream.WriteInt(0);
}

int RawStruct<std::vector<RPG::EventCommand> >::LcfSize(const std::vector<RPG::EventCommand>& event_commands, LcfWriter& stream) {
	int result = 0;
	int count = event_commands.size();
	for (int i = 0; i < count; i++)
		result += RawStruct<RPG::EventCommand>::LcfSize(event_commands[i], stream);
	result += 4;
	return result;
}

void RawStruct<std::vector<RPG::EventCommand> >::WriteXml(const std::vector<RPG::EventCommand>& event_commands, XmlWriter& stream) {
	std::vector<RPG::EventCommand>::const_iterator it;
	for (it = event_commands.begin(); it != event_commands.end(); it++)
		RawStruct<RPG::EventCommand>::WriteXml(*it, stream);
}

class EventCommandVectorXmlHandler : public XmlHandler {
public:
	EventCommandVectorXmlHandler(std::vector<RPG::EventCommand>& ref) : ref(ref) {}

	void StartElement(XmlReader& stream, const char* name, const char** /* atts */) {
		if (strcmp(name, "EventCommand") != 0)
			stream.Error("Expecting %s but got %s", "EventCommand", name);
		ref.resize(ref.size() + 1);
		RPG::EventCommand& obj = ref.back();
		stream.SetHandler(new EventCommandXmlHandler(obj));
	}
private:
	std::vector<RPG::EventCommand>& ref;
};

void RawStruct<std::vector<RPG::EventCommand> >::BeginXml(std::vector<RPG::EventCommand>& obj, XmlReader& stream) {
	stream.SetHandler(new EventCommandVectorXmlHandler(obj));
}
