/* xoreos-tools - Tools to help with xoreos development
 *
 * xoreos-tools is the legal property of its developers, whose names
 * can be found in the AUTHORS file distributed with this source
 * distribution.
 *
 * xoreos-tools 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 3
 * of the License, or (at your option) any later version.
 *
 * xoreos-tools 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 xoreos-tools. If not, see <http://www.gnu.org/licenses/>.
 */

/** @file
 *  Unit tests for our GFF3 file reader class.
 */

#include <vector>

#include "gtest/gtest.h"

#include "src/common/util.h"
#include "src/common/error.h"
#include "src/common/memreadstream.h"

#include "src/aurora/locstring.h"
#include "src/aurora/language.h"
#include "src/aurora/gff3file.h"

// --- GFF3, single struct ---

static const byte kGFF3SingleStruct[] = {
	0x47,0x46,0x46,0x20,0x56,0x33,0x2E,0x32,0x38,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
	0x44,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x11,0x00,0x00,0x00,
	0x20,0x02,0x00,0x00,0x71,0x00,0x00,0x00,0x91,0x02,0x00,0x00,0x44,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x00,0x00,
	0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
	0x02,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
	0xE8,0xFF,0xFF,0xFF,0x04,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x19,0x00,0x00,0x00,
	0x05,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0xE7,0xFF,0xFF,0xFF,0x06,0x00,0x00,0x00,
	0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x07,0x00,0x00,0x00,
	0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0xBC,0x41,
	0x09,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,
	0x0A,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,
	0x22,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x29,0x00,0x00,0x00,
	0x0D,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x43,0x00,0x00,0x00,0x10,0x00,0x00,0x00,
	0x0E,0x00,0x00,0x00,0x4D,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,
	0x5D,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x69,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x42,0x79,0x74,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x43,0x68,0x61,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x55,0x69,0x6E,0x74,0x31,0x36,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x53,0x69,0x6E,0x74,0x31,0x36,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x55,0x69,0x6E,0x74,0x33,0x32,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x53,0x69,0x6E,0x74,0x33,0x32,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x55,0x69,0x6E,0x74,0x36,0x34,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x53,0x69,0x6E,0x74,0x36,0x34,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x46,0x6C,0x6F,0x61,0x74,0x00,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x44,0x6F,0x75,0x62,0x6C,0x65,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x45,0x78,0x6F,0x53,0x74,0x72,0x69,0x6E,0x67,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x52,0x65,0x73,0x52,0x65,0x66,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x4C,0x6F,0x63,0x53,0x74,0x72,0x69,0x6E,0x67,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x56,0x6F,0x69,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x4F,0x72,0x69,0x65,0x6E,0x74,0x61,0x74,0x69,0x6F,0x6E,
	0x46,0x69,0x65,0x6C,0x64,0x56,0x65,0x63,0x74,0x6F,0x72,0x00,0x00,0x00,0x00,0x00,
	0x46,0x69,0x65,0x6C,0x64,0x53,0x74,0x72,0x52,0x65,0x66,0x00,0x00,0x00,0x00,0x00,
	0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
	0x9A,0x99,0x99,0x99,0x99,0x99,0x39,0x40,0x06,0x00,0x00,0x00,0x46,0x6F,0x6F,0x62,
	0x61,0x72,0x06,0x42,0x61,0x72,0x66,0x6F,0x6F,0x16,0x00,0x00,0x00,0x33,0x00,0x00,
	0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x51,0x75,0x75,
	0x75,0x75,0x78,0x06,0x00,0x00,0x00,0x21,0x44,0x41,0x54,0x41,0x21,0x66,0x66,0x28,
	0x42,0xCD,0xCC,0x28,0x42,0x33,0x33,0x29,0x42,0x9A,0x99,0x29,0x42,0x66,0x66,0x2C,
	0x42,0xCD,0xCC,0x2C,0x42,0x33,0x33,0x2D,0x42,0x04,0x00,0x00,0x00,0x65,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,
	0x00,0x04,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x07,0x00,0x00,
	0x00,0x08,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x0B,0x00,0x00,
	0x00,0x0C,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x0F,0x00,0x00,
	0x00,0x10,0x00,0x00,0x00
};

static const char * const kFieldNamesSingle[] = {
	"FieldByte"     , "FieldChar"  , "FieldUint16"     , "FieldSint16", "FieldUint32"   , "FieldSint32",
	"FieldUint64"   , "FieldSint64", "FieldFloat"      , "FieldDouble", "FieldExoString", "FieldResRef",
	"FieldLocString", "FieldVoid"  , "FieldOrientation", "FieldVector", "FieldStrRef"
};

static const Aurora::GFF3Struct::FieldType kFieldTypesSingle[] = {
	Aurora::GFF3Struct::kFieldTypeByte,
	Aurora::GFF3Struct::kFieldTypeChar,
	Aurora::GFF3Struct::kFieldTypeUint16,
	Aurora::GFF3Struct::kFieldTypeSint16,
	Aurora::GFF3Struct::kFieldTypeUint32,
	Aurora::GFF3Struct::kFieldTypeSint32,
	Aurora::GFF3Struct::kFieldTypeUint64,
	Aurora::GFF3Struct::kFieldTypeSint64,
	Aurora::GFF3Struct::kFieldTypeFloat,
	Aurora::GFF3Struct::kFieldTypeDouble,
	Aurora::GFF3Struct::kFieldTypeExoString,
	Aurora::GFF3Struct::kFieldTypeResRef,
	Aurora::GFF3Struct::kFieldTypeLocString,
	Aurora::GFF3Struct::kFieldTypeVoid,
	Aurora::GFF3Struct::kFieldTypeOrientation,
	Aurora::GFF3Struct::kFieldTypeVector,
	Aurora::GFF3Struct::kFieldTypeStrRef
};

GTEST_TEST(GFF3File, getType) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));

	EXPECT_EQ(gff3.getType(), MKTAG('G', 'F', 'F', ' '));
}

GTEST_TEST(GFF3File, enforceType) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct), MKTAG('G', 'F', 'F', ' '));

	EXPECT_THROW(Aurora::GFF3File gffNope(new Common::MemoryReadStream(kGFF3SingleStruct), MKTAG('N', 'O', 'P', 'E')), Common::Exception);
}

GTEST_TEST(GFF3Struct, getID) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_EQ(strct.getID(), 23);
}

GTEST_TEST(GFF3Struct, getFieldCount) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_EQ(strct.getFieldCount(), ARRAYSIZE(kFieldNamesSingle));
}

GTEST_TEST(GFF3Struct, hasField) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	for (size_t i = 0; i < ARRAYSIZE(kFieldNamesSingle); i++)
		EXPECT_TRUE(strct.hasField(kFieldNamesSingle[i])) << "At index " << i;

	EXPECT_FALSE(strct.hasField("Nope"));
}

GTEST_TEST(GFF3Struct, getFieldNames) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	const std::vector<Common::UString> &fieldNames = strct.getFieldNames();

	for (size_t i = 0; i < ARRAYSIZE(kFieldNamesSingle); i++)
		EXPECT_STREQ(fieldNames[i].c_str(), kFieldNamesSingle[i]) << "At index " << i;
}

GTEST_TEST(GFF3Struct, getFieldType) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	for (size_t i = 0; i < ARRAYSIZE(kFieldNamesSingle); i++)
		EXPECT_EQ(strct.getFieldType(kFieldNamesSingle[i]), kFieldTypesSingle[i]) << "At index " << i;
}

GTEST_TEST(GFF3Struct, getChar) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_EQ(strct.getChar("FieldChar"), 'x');
	EXPECT_EQ(strct.getChar("Nope", 99), 99);

	EXPECT_THROW(strct.getChar("FieldLocString"), Common::Exception);
}

GTEST_TEST(GFF3Struct, getUint) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_EQ(strct.getUint("FieldByte"  ), 23);
	EXPECT_EQ(strct.getUint("FieldUint16"), 24);
	EXPECT_EQ(strct.getUint("FieldUint32"), 25);
	EXPECT_EQ(strct.getUint("FieldUint64"), 42);

	EXPECT_EQ(strct.getUint("FieldStrRef"), 101);

	EXPECT_EQ(strct.getUint("FieldChar"  ), 120);
	EXPECT_EQ(strct.getUint("FieldSint16"), (uint64)((int64) -24));
	EXPECT_EQ(strct.getUint("FieldSint32"), (uint64)((int64) -25));
	EXPECT_EQ(strct.getUint("FieldSint64"), (uint64)((int64) -42));

	EXPECT_EQ(strct.getUint("Nope", 99), 99);

	EXPECT_THROW(strct.getUint("FieldLocString"), Common::Exception);
}

GTEST_TEST(GFF3Struct, getSint) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_EQ(strct.getSint("FieldByte"  ), 23);
	EXPECT_EQ(strct.getSint("FieldUint16"), 24);
	EXPECT_EQ(strct.getSint("FieldUint32"), 25);
	EXPECT_EQ(strct.getSint("FieldUint64"), 42);

	EXPECT_EQ(strct.getSint("FieldStrRef"), 101);

	EXPECT_EQ(strct.getSint("FieldChar"  ),  120);
	EXPECT_EQ(strct.getSint("FieldSint16"), - 24);
	EXPECT_EQ(strct.getSint("FieldSint32"), - 25);
	EXPECT_EQ(strct.getSint("FieldSint64"), - 42);

	EXPECT_EQ(strct.getSint("Nope", 99), 99);

	EXPECT_THROW(strct.getSint("FieldLocString"), Common::Exception);
}

GTEST_TEST(GFF3Struct, getBool) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_EQ(strct.getBool("FieldByte"  ), true);
	EXPECT_EQ(strct.getBool("FieldUint16"), true);
	EXPECT_EQ(strct.getBool("FieldUint32"), true);
	EXPECT_EQ(strct.getBool("FieldUint64"), true);

	EXPECT_EQ(strct.getBool("FieldStrRef"), true);

	EXPECT_EQ(strct.getBool("FieldChar"  ), true);
	EXPECT_EQ(strct.getBool("FieldSint16"), true);
	EXPECT_EQ(strct.getBool("FieldSint32"), true);
	EXPECT_EQ(strct.getBool("FieldSint64"), true);

	EXPECT_EQ(strct.getBool("Nope", true ),  true);
	EXPECT_EQ(strct.getBool("Nope", false), false);

	EXPECT_THROW(strct.getBool("FieldLocString"), Common::Exception);
}

GTEST_TEST(GFF3Struct, getDouble) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_DOUBLE_EQ(strct.getDouble("FieldFloat" ), 23.5f);
	EXPECT_DOUBLE_EQ(strct.getDouble("FieldDouble"), 25.6);

	EXPECT_DOUBLE_EQ(strct.getDouble("Nope", 99.9), 99.9);

	EXPECT_THROW(strct.getDouble("FieldLocString"), Common::Exception);
}

GTEST_TEST(GFF3Struct, getString) {
	LangMan.addLanguage(Aurora::kLanguageEnglish, 0, Common::kEncodingUTF8);

	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_STREQ(strct.getString("FieldExoString").c_str(), "Foobar");
	EXPECT_STREQ(strct.getString("FieldResRef"   ).c_str(), "Barfoo");
	EXPECT_STREQ(strct.getString("FieldLocString").c_str(), "Quuuux");

	EXPECT_STREQ(strct.getString("FieldUint16").c_str(), "24");
	EXPECT_STREQ(strct.getString("FieldSint16").c_str(), "-24");

	EXPECT_STREQ(strct.getString("Nope", "NOOOPE").c_str(), "NOOOPE");

	EXPECT_THROW(strct.getDouble("FieldVoid"), Common::Exception);

	Aurora::LanguageManager::destroy();
}

GTEST_TEST(GFF3Struct, getLocString) {
	LangMan.addLanguage(Aurora::kLanguageEnglish, 0, Common::kEncodingUTF8);

	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	Aurora::LocString locString1;
	EXPECT_TRUE(strct.getLocString("FieldLocString", locString1));
	EXPECT_STREQ(locString1.getString().c_str(), "Quuuux");

	Aurora::LocString locString2;
	EXPECT_FALSE(strct.getLocString("Nope", locString2));
	EXPECT_STREQ(locString2.getString().c_str(), "");

	Aurora::LocString locString3;
	EXPECT_FALSE(strct.getLocString("FieldUint16", locString3));
	EXPECT_STREQ(locString3.getString().c_str(), "");

	Aurora::LanguageManager::destroy();
}

GTEST_TEST(GFF3Struct, getVectorFloat) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	float x1 = 0.0f, y1 = 0.0f, z1 = 0.0f;
	strct.getVector("FieldVector", x1, y1, z1);

	EXPECT_FLOAT_EQ(x1, 43.1f);
	EXPECT_FLOAT_EQ(y1, 43.2f);
	EXPECT_FLOAT_EQ(z1, 43.3f);

	float x2 = 0.0f, y2 = 0.0f, z2 = 0.0f;
	strct.getVector("NOPE", x2, y2, z2);

	EXPECT_FLOAT_EQ(x2, 0.0f);
	EXPECT_FLOAT_EQ(y2, 0.0f);
	EXPECT_FLOAT_EQ(z2, 0.0f);

	float x3 = 0.0f, y3 = 0.0f, z3 = 0.0f;
	EXPECT_THROW(strct.getVector("FieldLocString", x3, y3, z3), Common::Exception);
}

GTEST_TEST(GFF3Struct, getVectorDouble) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	double x1 = 0.0, y1 = 0.0, z1 = 0.0;
	strct.getVector("FieldVector", x1, y1, z1);

	EXPECT_DOUBLE_EQ(x1, 43.1f);
	EXPECT_DOUBLE_EQ(y1, 43.2f);
	EXPECT_DOUBLE_EQ(z1, 43.3f);

	double x2 = 0.0, y2 = 0.0, z2 = 0.0;
	strct.getVector("NOPE", x2, y2, z2);

	EXPECT_DOUBLE_EQ(x2, 0.0f);
	EXPECT_DOUBLE_EQ(y2, 0.0f);
	EXPECT_DOUBLE_EQ(z2, 0.0f);

	double x3 = 0.0, y3 = 0.0, z3 = 0.0;
	EXPECT_THROW(strct.getVector("FieldLocString", x3, y3, z3), Common::Exception);
}

GTEST_TEST(GFF3Struct, getOrientationFloat) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	float a1 = 0.0f, b1 = 0.0f, c1 = 0.0f, d1 = 0.0f;
	strct.getOrientation("FieldOrientation", a1, b1, c1, d1);

	EXPECT_FLOAT_EQ(a1, 42.1f);
	EXPECT_FLOAT_EQ(b1, 42.2f);
	EXPECT_FLOAT_EQ(c1, 42.3f);
	EXPECT_FLOAT_EQ(d1, 42.4f);

	float a2 = 0.0f, b2 = 0.0f, c2 = 0.0f, d2 = 0.0f;
	strct.getOrientation("NOPE", a2, b2, c2, d2);

	EXPECT_FLOAT_EQ(a2, 0.0f);
	EXPECT_FLOAT_EQ(b2, 0.0f);
	EXPECT_FLOAT_EQ(c2, 0.0f);
	EXPECT_FLOAT_EQ(d2, 0.0f);

	float a3 = 0.0f, b3 = 0.0f, c3 = 0.0f, d3 = 0.0f;
	EXPECT_THROW(strct.getOrientation("FieldLocString", a3, b3, c3, d3), Common::Exception);
}

GTEST_TEST(GFF3Struct, getOrientationDouble) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	double a1 = 0.0, b1 = 0.0, c1 = 0.0, d1 = 0.0;
	strct.getOrientation("FieldOrientation", a1, b1, c1, d1);

	EXPECT_DOUBLE_EQ(a1, 42.1f);
	EXPECT_DOUBLE_EQ(b1, 42.2f);
	EXPECT_DOUBLE_EQ(c1, 42.3f);
	EXPECT_DOUBLE_EQ(d1, 42.4f);

	double a2 = 0.0, b2 = 0.0, c2 = 0.0, d2 = 0.0;
	strct.getOrientation("NOPE", a2, b2, c2, d2);

	EXPECT_DOUBLE_EQ(a2, 0.0);
	EXPECT_DOUBLE_EQ(b2, 0.0);
	EXPECT_DOUBLE_EQ(c2, 0.0);
	EXPECT_DOUBLE_EQ(d2, 0.0);

	double a3 = 0.0, b3 = 0.0, c3 = 0.0, d3 = 0.0;
	EXPECT_THROW(strct.getOrientation("FieldLocString", a3, b3, c3, d3), Common::Exception);
}

void compareData(Common::SeekableReadStream &data1, const char *data2, size_t n, size_t t = 0) {
	for (size_t i = 0; i < n; i++)
		EXPECT_EQ(data1.readByte(), (byte) data2[i]) << "At cast " << t << ", index " << i;
}

GTEST_TEST(GFF3Struct, getData) {
	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3SingleStruct));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	Common::SeekableReadStream *data1 = strct.getData("FieldVoid");
	ASSERT_NE(data1, static_cast<Common::SeekableReadStream *>(0));
	ASSERT_EQ(data1->size(), 6);

	compareData(*data1, "!DATA!", 6, 0);
	delete data1;

	Common::SeekableReadStream *data2 = strct.getData("FieldExoString");
	ASSERT_NE(data2, static_cast<Common::SeekableReadStream *>(0));
	ASSERT_EQ(data2->size(), 6);

	compareData(*data2, "Foobar", 6, 1);
	delete data2;

	Common::SeekableReadStream *data3 = strct.getData("FieldResRef");
	ASSERT_NE(data3, static_cast<Common::SeekableReadStream *>(0));
	ASSERT_EQ(data3->size(), 6);

	compareData(*data3, "Barfoo", 6, 2);
	delete data3;

	Common::SeekableReadStream *data4 = strct.getData("Nope");
	ASSERT_EQ(data4, static_cast<Common::SeekableReadStream *>(0));

	EXPECT_THROW(strct.getData("FieldUint16"), Common::Exception);
}

// --- GFF3, NWN premium ---

GTEST_TEST(GFF3File, premiumNWN) {
	static const byte kGFF3Premium[] = {
		0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x54,0x00,0x00,0x00,0x11,0x00,0x00,0x00,
		0x20,0x01,0x00,0x00,0x11,0x00,0x00,0x00,0x30,0x02,0x00,0x00,0x71,0x00,0x00,0x00,
		0xA1,0x02,0x00,0x00,0x44,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
		0x78,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x18,0x00,0x00,0x00,
		0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0xE8,0xFF,0xFF,0xFF,0x04,0x00,0x00,0x00,
		0x04,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x05,0x00,0x00,0x00,
		0xE7,0xFF,0xFF,0xFF,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x07,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x00,
		0x08,0x00,0x00,0x00,0x00,0x00,0xBC,0x41,0x09,0x00,0x00,0x00,0x09,0x00,0x00,0x00,
		0x10,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x18,0x00,0x00,0x00,
		0x0B,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,
		0x0C,0x00,0x00,0x00,0x29,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,
		0x43,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x4D,0x00,0x00,0x00,
		0x11,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x5D,0x00,0x00,0x00,0x12,0x00,0x00,0x00,
		0x10,0x00,0x00,0x00,0x69,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x42,0x79,0x74,
		0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x43,0x68,0x61,
		0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x55,0x69,0x6E,
		0x74,0x31,0x36,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x53,0x69,0x6E,
		0x74,0x31,0x36,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x55,0x69,0x6E,
		0x74,0x33,0x32,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x53,0x69,0x6E,
		0x74,0x33,0x32,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x55,0x69,0x6E,
		0x74,0x36,0x34,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x53,0x69,0x6E,
		0x74,0x36,0x34,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x46,0x6C,0x6F,
		0x61,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x44,0x6F,0x75,
		0x62,0x6C,0x65,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x45,0x78,0x6F,
		0x53,0x74,0x72,0x69,0x6E,0x67,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x52,0x65,0x73,
		0x52,0x65,0x66,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x4C,0x6F,0x63,
		0x53,0x74,0x72,0x69,0x6E,0x67,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x56,0x6F,0x69,
		0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x4F,0x72,0x69,
		0x65,0x6E,0x74,0x61,0x74,0x69,0x6F,0x6E,0x46,0x69,0x65,0x6C,0x64,0x56,0x65,0x63,
		0x74,0x6F,0x72,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x53,0x74,0x72,
		0x52,0x65,0x66,0x00,0x00,0x00,0x00,0x00,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0xD6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9A,0x99,0x99,0x99,0x99,0x99,0x39,0x40,
		0x06,0x00,0x00,0x00,0x46,0x6F,0x6F,0x62,0x61,0x72,0x06,0x42,0x61,0x72,0x66,0x6F,
		0x6F,0x16,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
		0x00,0x06,0x00,0x00,0x00,0x51,0x75,0x75,0x75,0x75,0x78,0x06,0x00,0x00,0x00,0x21,
		0x44,0x41,0x54,0x41,0x21,0x66,0x66,0x28,0x42,0xCD,0xCC,0x28,0x42,0x33,0x33,0x29,
		0x42,0x9A,0x99,0x29,0x42,0x66,0x66,0x2C,0x42,0xCD,0xCC,0x2C,0x42,0x33,0x33,0x2D,
		0x42,0x04,0x00,0x00,0x00,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,
		0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x05,0x00,0x00,
		0x00,0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x09,0x00,0x00,
		0x00,0x0A,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0D,0x00,0x00,
		0x00,0x0E,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x10,0x00,0x00,0x00
	};

	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3Premium), 0xFFFFFFFF, true);
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_EQ(strct.getUint("FieldUint32"), 25);
	EXPECT_EQ(strct.getUint("FieldUint64"), 42);

	EXPECT_STREQ(strct.getString("FieldExoString").c_str(), "Foobar");
}

// --- GFF3, structs ---

GTEST_TEST(GFF3Struct, getStruct) {
	static const byte kGFF3Structs[] = {
		0x47,0x46,0x46,0x20,0x56,0x33,0x2E,0x32,0x38,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
		0x5C,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
		0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB8,0x00,0x00,0x00,0x10,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x02,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
		0x19,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
		0x01,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0x00,0x00,0x00,
		0x0E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x55,0x69,0x6E,
		0x74,0x33,0x32,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x53,0x74,0x72,
		0x75,0x63,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
		0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00
	};

	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3Structs));

	const Aurora::GFF3Struct &strct0 = gff3.getTopLevel();
	const Aurora::GFF3Struct &strct1 = strct0.getStruct("FieldStruct");
	const Aurora::GFF3Struct &strct2 = strct1.getStruct("FieldStruct");

	EXPECT_THROW(strct2.getStruct("FieldStruct"), Common::Exception);
	EXPECT_THROW(strct2.getStruct("FieldUint32"), Common::Exception);
	EXPECT_THROW(strct0.getStruct("Nope"       ), Common::Exception);

	EXPECT_EQ(strct0.getFieldCount(), 2);
	EXPECT_EQ(strct1.getFieldCount(), 2);
	EXPECT_EQ(strct2.getFieldCount(), 1);

	EXPECT_EQ(strct0.getID(), 23);
	EXPECT_EQ(strct1.getID(), 24);
	EXPECT_EQ(strct2.getID(), 25);

	EXPECT_EQ(strct0.getUint("FieldUint32"), 32);
	EXPECT_EQ(strct1.getUint("FieldUint32"), 33);
	EXPECT_EQ(strct2.getUint("FieldUint32"), 34);
}

// --- GFF3, lists ---

GTEST_TEST(GFF3Struct, getList) {
	static const byte kGFF3Lists[] = {
		0x47,0x46,0x46,0x20,0x56,0x33,0x2E,0x32,0x38,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,
		0xB0,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x58,0x01,0x00,0x00,0x02,0x00,0x00,0x00,
		0x78,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x01,0x00,0x00,0x20,0x00,0x00,0x00,
		0x98,0x01,0x00,0x00,0x34,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x02,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
		0x19,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,
		0x18,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1B,0x00,0x00,0x00,0x08,0x00,0x00,0x00,
		0x01,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
		0x1D,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,
		0x0B,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,
		0x01,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
		0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,
		0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x21,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x10,0x00,0x00,0x00,
		0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,
		0x01,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x23,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x28,0x00,0x00,0x00,
		0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x26,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x00,0x00,0x00,
		0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
		0x00,0x00,0x00,0x00,0x29,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x55,0x69,0x6E,
		0x74,0x33,0x32,0x00,0x00,0x00,0x00,0x00,0x46,0x69,0x65,0x6C,0x64,0x4C,0x69,0x73,
		0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
		0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x05,0x00,0x00,0x00,
		0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
		0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
		0x05,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,
		0x02,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x09,0x00,0x00,0x00
	};

	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3Lists));
	const Aurora::GFF3Struct &strct0 = gff3.getTopLevel();

	EXPECT_EQ(strct0.getFieldCount(), 2);
	EXPECT_EQ(strct0.getID(), 23);
	EXPECT_EQ(strct0.getUint("FieldUint32"), 32);

	const Aurora::GFF3List &list0 = strct0.getList("FieldList");

	EXPECT_THROW(strct0.getStruct("FieldUint32"), Common::Exception);
	EXPECT_THROW(strct0.getStruct("Nope"       ), Common::Exception);

	ASSERT_EQ(list0.size(), 3);
	ASSERT_NE(list0[0], static_cast<const Aurora::GFF3Struct *>(0));
	ASSERT_NE(list0[1], static_cast<const Aurora::GFF3Struct *>(0));
	ASSERT_NE(list0[2], static_cast<const Aurora::GFF3Struct *>(0));

	const Aurora::GFF3Struct &strct1 = *list0[0];
	EXPECT_EQ(strct1.getFieldCount(), 2);
	EXPECT_EQ(strct1.getID(), 24);
	EXPECT_EQ(strct1.getUint("FieldUint32"), 33);

	const Aurora::GFF3Struct &strct2 = *list0[1];
	EXPECT_EQ(strct2.getFieldCount(), 2);
	EXPECT_EQ(strct2.getID(), 25);
	EXPECT_EQ(strct2.getUint("FieldUint32"), 34);

	const Aurora::GFF3Struct &strct3 = *list0[2];
	EXPECT_EQ(strct3.getFieldCount(), 2);
	EXPECT_EQ(strct3.getID(), 26);
	EXPECT_EQ(strct3.getUint("FieldUint32"), 35);

	const Aurora::GFF3List &list1 = strct1.getList("FieldList");
	const Aurora::GFF3List &list2 = strct2.getList("FieldList");
	const Aurora::GFF3List &list3 = strct3.getList("FieldList");

	ASSERT_EQ(list1.size(), 2);
	ASSERT_NE(list1[0], static_cast<const Aurora::GFF3Struct *>(0));
	ASSERT_NE(list1[1], static_cast<const Aurora::GFF3Struct *>(0));

	ASSERT_EQ(list2.size(), 2);
	ASSERT_NE(list2[0], static_cast<const Aurora::GFF3Struct *>(0));
	ASSERT_NE(list2[1], static_cast<const Aurora::GFF3Struct *>(0));

	ASSERT_EQ(list3.size(), 2);
	ASSERT_NE(list3[0], static_cast<const Aurora::GFF3Struct *>(0));
	ASSERT_NE(list3[1], static_cast<const Aurora::GFF3Struct *>(0));

	const Aurora::GFF3Struct &strct4 = *list1[0];
	EXPECT_EQ(strct4.getFieldCount(), 1);
	EXPECT_EQ(strct4.getID(), 27);
	EXPECT_EQ(strct4.getUint("FieldUint32"), 36);

	const Aurora::GFF3Struct &strct5 = *list1[1];
	EXPECT_EQ(strct5.getFieldCount(), 1);
	EXPECT_EQ(strct5.getID(), 28);
	EXPECT_EQ(strct5.getUint("FieldUint32"), 37);

	const Aurora::GFF3Struct &strct6 = *list2[0];
	EXPECT_EQ(strct6.getFieldCount(), 1);
	EXPECT_EQ(strct6.getID(), 29);
	EXPECT_EQ(strct6.getUint("FieldUint32"), 38);

	const Aurora::GFF3Struct &strct7 = *list2[1];
	EXPECT_EQ(strct7.getFieldCount(), 1);
	EXPECT_EQ(strct7.getID(), 30);
	EXPECT_EQ(strct7.getUint("FieldUint32"), 39);

	const Aurora::GFF3Struct &strct8 = *list3[0];
	EXPECT_EQ(strct8.getFieldCount(), 1);
	EXPECT_EQ(strct8.getID(), 31);
	EXPECT_EQ(strct8.getUint("FieldUint32"), 40);

	const Aurora::GFF3Struct &strct9 = *list3[1];
	EXPECT_EQ(strct9.getFieldCount(), 1);
	EXPECT_EQ(strct9.getID(), 32);
	EXPECT_EQ(strct9.getUint("FieldUint32"), 41);
}

// --- GFF3, V3.3 ---

GTEST_TEST(GFF3File, GFF3V33) {
	static const byte kGFF3V33[] = {
		0x47,0x46,0x46,0x20,0x56,0x33,0x2E,0x33,0x38,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
		0x44,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
		0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		0x01,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,
		0x46,0x69,0x65,0x6C,0x64,0x55,0x69,0x6E,0x74,0x33,0x32,0x00,0x00,0x00,0x00,0x00
	};

	Aurora::GFF3File gff3(new Common::MemoryReadStream(kGFF3V33));
	const Aurora::GFF3Struct &strct = gff3.getTopLevel();

	EXPECT_EQ(strct.getID(), 23);
	EXPECT_EQ(strct.getUint("FieldUint32"), 32);
}
