#include "Formats.h"
#include "String_.h"
#include "World.h"
#include "Deflate.h"
#include "Block.h"
#include "Entity.h"
#include "Platform.h"
#include "ExtMath.h"
#include "Logger.h"
#include "Game.h"
#include "Server.h"
#include "Event.h"
#include "Funcs.h"
#include "Errors.h"
#include "Stream.h"
#include "Chat.h"
#include "TexturePack.h"
#include "Utils.h"
#include "Audio.h"

#ifdef CC_BUILD_FILESYSTEM
static struct LocationUpdate* spawn_point;
static struct MapImporter* imp_head;
static struct MapImporter* imp_tail;


/*########################################################################################################################*
*--------------------------------------------------------General----------------------------------------------------------*
*#########################################################################################################################*/
static cc_result Map_ReadBlocks(struct Stream* stream) {
	World.Volume = World.Width * World.Length * World.Height;
	World.Blocks = (BlockRaw*)Mem_TryAlloc(World.Volume, 1);

	if (!World.Blocks) return ERR_OUT_OF_MEMORY;
	return Stream_Read(stream, World.Blocks, World.Volume);
}

static cc_result Map_SkipGZipHeader(struct Stream* stream) {
	struct GZipHeader gzHeader;
	cc_result res;
	GZipHeader_Init(&gzHeader);

	while (!gzHeader.done) {
		if ((res = GZipHeader_Read(stream, &gzHeader))) return res;
	}
	return 0;
}

void MapImporter_Register(struct MapImporter* imp) {
	LinkedList_Append(imp, imp_head, imp_tail);
}

struct MapImporter* MapImporter_Find(const cc_string* path) {
	struct MapImporter* imp;
	cc_string ext;

	for (imp = imp_head; imp; imp = imp->next)
	{
		ext = String_FromReadonly(imp->fileExt);
		if (String_CaselessEnds(path, &ext)) return imp;
	}
	return NULL;
}

cc_result Map_LoadFrom(const cc_string* path) {
	cc_string relPath, fileName, fileExt;
	struct LocationUpdate update = { 0 };
	struct MapImporter* imp;
	struct Stream stream;
	cc_filepath raw_path;
	cc_result res;

	Game_Reset();
	spawn_point = &update;

	Platform_EncodePath(&raw_path, path);
	res = Stream_OpenPath(&stream, &raw_path);
	if (res) { Logger_IOWarn2(res, "opening", &raw_path); return res; }

	imp = MapImporter_Find(path);
	if (!imp) {
		res = ERR_NOT_SUPPORTED;
	} else if ((res = imp->import(&stream))) {
		World_Reset();
	}

	/* No point logging error for closing readonly file */
	(void)stream.Close(&stream);
	if (res) Logger_IOWarn2(res, "decoding", &raw_path);

	World_SetNewMap(World.Blocks, World.Width, World.Height, World.Length);
	if (!spawn_point) LocalPlayer_CalcDefaultSpawn(Entities.CurPlayer, &update);
	LocalPlayers_MoveToSpawn(&update);

	relPath = *path;
	Utils_UNSAFE_GetFilename(&relPath);
	String_UNSAFE_Separate(&relPath, '.', &fileName, &fileExt);
	String_Copy(&World.Name, &fileName);
	return res;
}


/*########################################################################################################################*
*--------------------------------------------------MCSharp level Format---------------------------------------------------*
*#########################################################################################################################*/
#define LVL_CUSTOMTILE 163
#define LVL_CHUNKSIZE 16
/* MCSharp* format is a GZIP compressed binary map format. All metadata is discarded.
	U16 "Identifier" (must be 1874)
	U16 "Width",  "Length", "Height"
	U16 "SpawnX", "SpawnZ", "SpawnY"
	U8  "Yaw", "Pitch"
	U16 "Build permissions" (ignored)
	U8* "Blocks"
	
	-- this data is only in MCGalaxy maps
	U8  "Identifier" (0xBD for 'block definitions', i.e. custom blocks)
	U8* "Data"       (16x16x16 sparsely allocated chunks)
}*/

static const cc_uint8 Lvl_table[256] = {
	 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
	16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
	32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
	48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
	64, 65,  0,  0,  0,  0, 39, 36, 36, 10, 46, 21, 22, 22, 22, 22,
	 4,  0, 22, 21,  0, 22, 23, 24, 22, 26, 27, 28, 30, 31, 32, 33,
	34, 35, 36, 22, 20, 49, 45,  1,  4,  0,  9, 11,  4, 19,  5, 17,
	10, 49, 20,  1, 18, 12,  5, 25, 46, 44, 17, 49, 20,  1, 18, 12,
	 5, 25, 36, 34,  0,  9, 11, 46, 44,  0,  9, 11,  8, 10, 22, 27,
	22,  8, 10, 28, 17, 49, 20,  1, 18, 12,  5, 25, 46, 44, 11,  9,
	 0,  9, 11, 163, 0,  0,  9, 11,  0,  0,  0,  0,  0,  0,  0, 28,
	22, 21, 11,  0,  0,  0, 46, 46, 10, 10, 46, 20, 41, 42, 11,  9,
	 0,  8, 10, 10,  8,  0, 22, 22,  0,  0,  0,  0,  0,  0,  0,  0,
	 0,  0,  0, 21, 10,  0,  0,  0,  0,  0, 22, 22, 42,  3,  2, 29,
	47,  0,  0,  0,  0,  0, 27, 46, 48, 24, 22, 36, 34,  8, 10, 21,
	29, 22, 10, 22, 22, 41, 19, 35, 21, 29, 49, 34, 16, 41,  0, 22
};

static cc_result Lvl_ReadCustomBlocks(struct Stream* stream) {	
	cc_uint8 chunk[LVL_CHUNKSIZE * LVL_CHUNKSIZE * LVL_CHUNKSIZE];
	cc_uint8 hasCustom;
	int baseIndex, index, xx, yy, zz;
	cc_result res;
	int x, y, z, i;

	/* skip bounds checks when we know chunk is entirely inside map */
	int adjWidth  = World.Width  & ~0x0F;
	int adjHeight = World.Height & ~0x0F;
	int adjLength = World.Length & ~0x0F;

	for (y = 0; y < World.Height; y += LVL_CHUNKSIZE) {
		for (z = 0; z < World.Length; z += LVL_CHUNKSIZE) {
			for (x = 0; x < World.Width; x += LVL_CHUNKSIZE) {

				if ((res = stream->ReadU8(stream, &hasCustom))) return res;
				if (hasCustom != 1) continue;
				if ((res = Stream_Read(stream, chunk, sizeof(chunk)))) return res;
				baseIndex = World_Pack(x, y, z);

				if ((x + LVL_CHUNKSIZE) <= adjWidth && (y + LVL_CHUNKSIZE) <= adjHeight && (z + LVL_CHUNKSIZE) <= adjLength) {
					for (i = 0; i < sizeof(chunk); i++) {
						xx = i & 0xF; yy = (i >> 8) & 0xF; zz = (i >> 4) & 0xF;

						index = baseIndex + World_Pack(xx, yy, zz);
						World.Blocks[index] = World.Blocks[index] == LVL_CUSTOMTILE ? chunk[i] : World.Blocks[index];
					}
				} else {
					for (i = 0; i < sizeof(chunk); i++) {
						xx = i & 0xF; yy = (i >> 8) & 0xF; zz = (i >> 4) & 0xF;
						if ((x + xx) >= World.Width || (y + yy) >= World.Height || (z + zz) >= World.Length) continue;

						index = baseIndex + World_Pack(xx, yy, zz);
						World.Blocks[index] = World.Blocks[index] == LVL_CUSTOMTILE ? chunk[i] : World.Blocks[index];
					}
				}
			}
		}
	}
	return 0;
}

/* Imports a world from a .lvl MCSharp server map file */
/* Used by MCSharp/MCLawl/MCForge/MCDzienny/MCGalaxy */
static cc_result Lvl_Load(struct Stream* stream) {
	cc_uint8 header[18];
	cc_uint8* blocks;
	cc_uint8 section;
	cc_result res;
	int i;

	struct Stream compStream;
	struct InflateState state;
	Inflate_MakeStream2(&compStream, &state, stream);
	
	if ((res = Map_SkipGZipHeader(stream)))                       return res;
	if ((res = Stream_Read(&compStream, header, sizeof(header)))) return res;
	if (Stream_GetU16_LE(&header[0]) != 1874) return LVL_ERR_VERSION;

	World.Width  = Stream_GetU16_LE(&header[2]);
	World.Length = Stream_GetU16_LE(&header[4]);
	World.Height = Stream_GetU16_LE(&header[6]);

	spawn_point->flags = LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH;
	spawn_point->pos.x = Stream_GetU16_LE(&header[8]);
	spawn_point->pos.z = Stream_GetU16_LE(&header[10]);
	spawn_point->pos.y = Stream_GetU16_LE(&header[12]);
	spawn_point->yaw   = Math_Packed2Deg(header[14]);
	spawn_point->pitch = Math_Packed2Deg(header[15]);
	/* (2) pervisit, perbuild permissions */

	if ((res = Map_ReadBlocks(&compStream))) return res;
	blocks = World.Blocks;
	/* Bulk convert 4 blocks at once */
	for (i = 0; i < (World.Volume & ~3); i += 4) {
		*blocks = Lvl_table[*blocks]; blocks++;
		*blocks = Lvl_table[*blocks]; blocks++;
		*blocks = Lvl_table[*blocks]; blocks++;
		*blocks = Lvl_table[*blocks]; blocks++;
	}
	for (; i < World.Volume; i++) {
		*blocks = Lvl_table[*blocks]; blocks++;
	}

	/* 0xBD section type is not present in older .lvl files */
	res = compStream.ReadU8(&compStream, &section);
	if (res == ERR_END_OF_STREAM) return 0;

	if (res) return res;
	/* Unrecognised section type, stop reading */
	if (section != 0xBD) return 0;

	res = Lvl_ReadCustomBlocks(&compStream);
	/* At least one map out there has a corrupted 0xBD section */
	if (res == ERR_END_OF_STREAM) {
		Chat_AddRaw("&cEnd of stream reading .lvl custom blocks section");
		Chat_AddRaw("&c  Some blocks may therefore appear incorrectly");
		res = 0;
	}
	return res;
}


/*########################################################################################################################*
*----------------------------------------------------fCraft map format----------------------------------------------------*
*#########################################################################################################################*/
/* fCraft* format is a binary map format. All metadata is discarded.
	U32 "Identifier" (must be FC2AF40)
	U8  "Revision"   (only '13' supported)
	U16 "Width",  "Height", "Length"
	U32 "SpawnX", "SpawnY", "SpawnZ"
	U8  "Yaw", "Pitch"
	U32 "DateModified", "DateCreated" (ignored)
	U8* "UUID"
	U8  "Layers"     (only maps with 1 layer supported)
	U8* "LayersInfo" (ignored, assumes only layer is map blocks)
	U32 "MetaCount"
	METADATA { STR "Group", "Key", "Value" }
	U8* "Blocks"
}*/
static cc_result Fcm_ReadString(struct Stream* stream) {
	cc_uint8 data[2];
	int len;
	cc_result res;

	if ((res = Stream_Read(stream, data, sizeof(data)))) return res;
	len = Stream_GetU16_LE(data);

	return stream->Skip(stream, len);
}

/* Imports a world from a .fcm fCraft server map file (v3 only) */
/* Used by fCraft/800Craft/LegendCraft/ProCraft */
static cc_result Fcm_Load(struct Stream* stream) {
	cc_uint8 header[79];	
	cc_result res;
	int i, count;

	struct Stream compStream;
	struct InflateState state;
	Inflate_MakeStream2(&compStream, &state, stream);

	if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
	if (Stream_GetU32_LE(&header[0]) != 0x0FC2AF40UL)        return FCM_ERR_IDENTIFIER;
	if (header[4] != 13) return FCM_ERR_REVISION;
	
	World.Width  = Stream_GetU16_LE(&header[5]);
	World.Height = Stream_GetU16_LE(&header[7]);
	World.Length = Stream_GetU16_LE(&header[9]);
	
	spawn_point->flags = LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH;
	spawn_point->pos.x = ((int)Stream_GetU32_LE(&header[11])) / 32.0f;
	spawn_point->pos.y = ((int)Stream_GetU32_LE(&header[15])) / 32.0f;
	spawn_point->pos.z = ((int)Stream_GetU32_LE(&header[19])) / 32.0f;
	spawn_point->yaw   = Math_Packed2Deg(header[23]);
	spawn_point->pitch = Math_Packed2Deg(header[24]);

	/* header[25] (4) date modified */
	/* header[29] (4) date created */
	Mem_Copy(&World.Uuid, &header[33], WORLD_UUID_LEN);
	/* header[49] (26) layer index */
	count = (int)Stream_GetU32_LE(&header[75]);

	/* header isn't compressed, rest of data is though */
	for (i = 0; i < count; i++) {
		if ((res = Fcm_ReadString(&compStream))) return res; /* Group */
		if ((res = Fcm_ReadString(&compStream))) return res; /* Key   */
		if ((res = Fcm_ReadString(&compStream))) return res; /* Value */
	}

	return Map_ReadBlocks(&compStream);
}


/*########################################################################################################################*
*---------------------------------------------------------NBTFile---------------------------------------------------------*
*#########################################################################################################################*/
enum NbtTagType { 
	NBT_END, NBT_I8,  NBT_I16, NBT_I32,  NBT_I64, NBT_F32, 
	NBT_F64, NBT_I8S, NBT_STR, NBT_LIST, NBT_DICT
};

#define NBT_SMALL_SIZE  STRING_SIZE
#define NBT_STRING_SIZE STRING_SIZE

#define NbtTag_IsSmall(tag) ((tag)->dataSize <= NBT_SMALL_SIZE)
#define IsTag(tag, tagName) (String_CaselessEqualsConst(&tag->name, tagName))
struct NbtTag;

struct NbtTag {
	struct NbtTag* parent;
	cc_uint8  type;
	cc_string name;
	cc_uint32 dataSize; /* size of data for arrays */

	union {
		cc_uint8  u8;
		cc_int16  i16;
		cc_uint16 u16;
		cc_int32  i32;
		cc_uint32 u32;
		float     f32;
		cc_uint8  small[NBT_SMALL_SIZE];
		cc_uint8* big; /* malloc for big byte arrays */
		struct { cc_string text; char buffer[STRING_SIZE * 2]; } str;
	} value;
	char _nameBuffer[NBT_STRING_SIZE];
	cc_result result;
	int listIndex;
};

static cc_uint8 NbtTag_U8(struct NbtTag* tag) {
	if (tag->type == NBT_I8) return tag->value.u8; 
	
	tag->result = NBT_ERR_EXPECTED_I8;
	return 0;
}

static cc_int16 NbtTag_I16(struct NbtTag* tag) {
	if (tag->type == NBT_I16) return tag->value.i16;
	if (tag->type == NBT_I8)  return tag->value.u8;

	tag->result = NBT_ERR_EXPECTED_I16;
	return 0;
}

static cc_uint16 NbtTag_U16(struct NbtTag* tag) {
	if (tag->type == NBT_I16) return tag->value.u16;
	if (tag->type == NBT_I8)  return tag->value.u8;

	tag->result = NBT_ERR_EXPECTED_I16;
	return 0;
}

static int NbtTag_I32(struct NbtTag* tag) {
	if (tag->type == NBT_I32) return tag->value.i32;
	if (tag->type == NBT_I16) return tag->value.i16;
	if (tag->type == NBT_I8)  return tag->value.u8;

	tag->result = NBT_ERR_EXPECTED_I32;
	return 0;
}

static float NbtTag_F32(struct NbtTag* tag) {
	if (tag->type == NBT_F32) return tag->value.f32;

	tag->result = NBT_ERR_EXPECTED_F32;
	return 0;
}

static cc_uint8* NbtTag_U8_Array(struct NbtTag* tag, int minSize) {
	if (tag->type != NBT_I8S)    { tag->result = NBT_ERR_EXPECTED_ARR;  return NULL; }
	if (tag->dataSize < minSize) { tag->result = NBT_ERR_ARR_TOO_SMALL; return NULL; }

	return NbtTag_IsSmall(tag) ? tag->value.small : tag->value.big;
}

static cc_string NbtTag_String(struct NbtTag* tag) {
	if (tag->type == NBT_STR) return tag->value.str.text;

	tag->result = NBT_ERR_EXPECTED_STR;
	return String_Empty;
}

static cc_result Nbt_ReadString(struct Stream* stream, cc_string* str) {
	cc_uint8 buffer[NBT_STRING_SIZE * 4];
	int len;
	cc_result res;

	if ((res = Stream_Read(stream, buffer, 2)))   return res;
	len = Stream_GetU16_BE(buffer);

	if (len > Array_Elems(buffer)) return CW_ERR_STRING_LEN;
	if ((res = Stream_Read(stream, buffer, len))) return res;

	String_AppendUtf8(str, buffer, len);
	return 0;
}

typedef void (*Nbt_Callback)(struct NbtTag* tag);
static cc_result Nbt_ReadTag(cc_uint8 typeId, cc_bool readTagName, struct Stream* stream, 
							struct NbtTag* parent, Nbt_Callback callback, int listIndex) {
	struct NbtTag tag;
	cc_uint8 childType;
	cc_uint8 tmp[5];	
	cc_result res;
	cc_uint32 i, count;
	
	if (typeId == NBT_END) return 0;
	tag.type      = typeId; 
	tag.parent    = parent;
	tag.dataSize  = 0;
	tag.listIndex = listIndex;
	String_InitArray(tag.name, tag._nameBuffer);

	if (readTagName) {
		res = Nbt_ReadString(stream, &tag.name);
		if (res) return res;
	}

	switch (typeId) {
	case NBT_I8:
		res = stream->ReadU8(stream, &tag.value.u8);
		break;
	case NBT_I16:
		res = Stream_Read(stream, tmp, 2);
		tag.value.u16 = Stream_GetU16_BE(tmp);
		break;
	case NBT_I32:
	case NBT_F32:
		res = Stream_ReadU32_BE(stream, &tag.value.u32);
		break;
	case NBT_I64:
	case NBT_F64:
		res = stream->Skip(stream, 8);
		break; /* (8) data */

	case NBT_I8S:
		if ((res = Stream_ReadU32_BE(stream, &tag.dataSize))) break;

		if (NbtTag_IsSmall(&tag)) {
			res = Stream_Read(stream, tag.value.small, tag.dataSize);
		} else {
			tag.value.big = (cc_uint8*)Mem_TryAlloc(tag.dataSize, 1);
			if (!tag.value.big) return ERR_OUT_OF_MEMORY;

			res = Stream_Read(stream, tag.value.big, tag.dataSize);
			if (res) Mem_Free(tag.value.big);
		}
		break;
	case NBT_STR:
		String_InitArray(tag.value.str.text, tag.value.str.buffer);
		res = Nbt_ReadString(stream, &tag.value.str.text);
		break;

	case NBT_LIST:
		if ((res = Stream_Read(stream, tmp, 5))) break;
		childType = tmp[0];
		count = Stream_GetU32_BE(&tmp[1]);

		for (i = 0; i < count; i++) {
			res = Nbt_ReadTag(childType, false, stream, &tag, callback, i);
			if (res) break;
		}
		break;

	case NBT_DICT:
		for (;;) {
			if ((res = stream->ReadU8(stream, &childType))) break;
			if (childType == NBT_END) break;

			res = Nbt_ReadTag(childType, true, stream, &tag, callback, 0);
			if (res) break;
		}
		break;

	default: return NBT_ERR_UNKNOWN;
	}

	if (res) return res;
	tag.result = 0;
	callback(&tag);
	/* NOTE: callback must set DataBig to NULL, if doesn't want it to be freed */
	if (!NbtTag_IsSmall(&tag)) Mem_Free(tag.value.big);
	return tag.result;
}


static BlockRaw* Nbt_TakeArray(struct NbtTag* tag, const char* type) {
	BlockRaw* ptr;
	if (NbtTag_IsSmall(tag)) {
		/* Small data is stored inline in tha tag, so need to copy it out */
		ptr = (BlockRaw*)Mem_Alloc(tag->dataSize, 1, type);
		Mem_Copy(ptr, tag->value.small, tag->dataSize);
	} else {
		ptr = tag->value.big;
		tag->value.big = NULL; /* So Nbt_ReadTag doesn't call Mem_Free on the array */
	}
	return ptr;
}

static cc_result Nbt_Read(struct Stream* stream, Nbt_Callback callback) {
	struct Stream compStream;
	struct InflateState state;
	cc_result res;
	cc_uint8 tag;

	Inflate_MakeStream2(&compStream, &state, stream);
	if ((res = Map_SkipGZipHeader(stream))) return res;
	if ((res = compStream.ReadU8(&compStream, &tag))) return res;

	if (tag != NBT_DICT) return CW_ERR_ROOT_TAG;
	return Nbt_ReadTag(NBT_DICT, true, &compStream, NULL, callback, 0);
}


/*########################################################################################################################*
*--------------------------------------------------------NBTWriter--------------------------------------------------------*
*#########################################################################################################################*/
static cc_uint8* Nbt_WriteConst(cc_uint8* data, const char* text) {
	int i, len = String_Length(text);
	*data++ = 0;
	*data++ = (cc_uint8)len;

	for (i = 0; i < len; i++) { *data++ = text[i]; }
	return data;
}

static cc_uint8* Nbt_WriteString(cc_uint8* data, const char* name, const cc_string* text) {
	cc_uint8* start; int i;

	*data++ = NBT_STR;
	data    = Nbt_WriteConst(data, name);
	start   = data;
	data += 2; /* length written later */

	for (i = 0; i < text->length; i++) {
		data = Convert_CP437ToUtf8(text->buffer[i], data) + data;
	}

	Stream_SetU16_BE(start, (int)(data - start) - 2);
	return data;
}

static cc_uint8* Nbt_WriteDict(cc_uint8* data, const char* name) {
	*data++ = NBT_DICT;
	data    = Nbt_WriteConst(data, name);

	return data;
}

static cc_uint8* Nbt_WriteArray(cc_uint8* data, const char* name, int size) {
	*data++ = NBT_I8S;
	data    = Nbt_WriteConst(data, name);

	Stream_SetU32_BE(data, size);
	return data + 4;
}

static cc_uint8* Nbt_WriteUInt8(cc_uint8* data, const char* name, cc_uint8 value) {
	*data++ = NBT_I8;
	data  = Nbt_WriteConst(data, name);

	*data = value;
	return data + 1;
}

static cc_uint8* Nbt_WriteUInt16(cc_uint8* data, const char* name, cc_uint16 value) {
	*data++ = NBT_I16;
	data    = Nbt_WriteConst(data, name);

	Stream_SetU16_BE(data, value);
	return data + 2;
}

static cc_uint8* Nbt_WriteInt32(cc_uint8* data, const char* name, int value) {
	*data++ = NBT_I32;
	data    = Nbt_WriteConst(data, name);

	Stream_SetU32_BE(data, value);
	return data + 4;
}

static cc_uint8* Nbt_WriteFloat(cc_uint8* data, const char* name, float value) {
	union IntAndFloat raw;
	*data++ = NBT_F32;
	data    = Nbt_WriteConst(data, name);

	raw.f = value;
	Stream_SetU32_BE(data, raw.u);
	return data + 4;
}


/*########################################################################################################################*
*--------------------------------------------------ClassicWorld format----------------------------------------------------*
*#########################################################################################################################*/
/* ClassicWorld is a NBT tag based map format. Tags not listed below are discarded.
COMPOUND "ClassicWorld" {
	U8* "UUID"
	U16 "X", "Y", "Z"
	COMPOUND "Spawn" {
		I16 "X", "Y", "Z"
		U8  "H", "P"
	}
	U8* "BlockArray"  (lower 8 bits, required)
	U8* "BlockArray2" (upper 8 bits, optional)
	COMPOUND "Metadata" {
		COMPOUND "CPE" {
			COMPOUND "ClickDistance"  { U16 "Reach" }
			COMPOUND "EnvWeatherType" { U8 "WeatherType" }
			COMPOUND "EnvMapAppearance" {
				U8 "SideBlock", "EdgeBlock"
				I16 "SidesLevel"
				STR "TextureURL"
			}
			COMPOUND "EnvColors" {
				COMPOUND "Sky"      { U16 "R", "G", "B" }
				COMPOUND "Cloud"    { U16 "R", "G", "B" }
				COMPOUND "Fog"      { U16 "R", "G", "B" }
				COMPOUND "Sunlight" { U16 "R", "G", "B" }
				COMPOUND "Ambient"  { U16 "R", "G", "B" }
			}
			COMPOUND "BlockDefinitions" {
				COMPOUND "Block_XYZ" { (name must start with 'Block')
					U8  "ID", U16 "ID2"
					STR "Name"
					F32 "Speed"
					U8  "CollideType", "BlockDraw"					
					U8  "TransmitsLight", "FullBright"
					U8  "Shape"	, "WalkSound"	
					U8* "Textures", "Fog", "Coords"
				}
			}
		}
	}
}*/

static void Cw_Callback_1(struct NbtTag* tag) {
	if (IsTag(tag, "X")) { World.Width  = NbtTag_U16(tag); return; }
	if (IsTag(tag, "Y")) { World.Height = NbtTag_U16(tag); return; }
	if (IsTag(tag, "Z")) { World.Length = NbtTag_U16(tag); return; }

	if (IsTag(tag, "UUID")) {
		if (tag->dataSize != WORLD_UUID_LEN) {
			tag->result = CW_ERR_UUID_LEN;
		} else {
			Mem_Copy(World.Uuid, tag->value.small, WORLD_UUID_LEN);
		}
		return;
	}

	if (IsTag(tag, "BlockArray")) {
		World.Volume = tag->dataSize;
		World.Blocks = Nbt_TakeArray(tag, ".cw map blocks");
	}
#ifdef EXTENDED_BLOCKS
	if (IsTag(tag, "BlockArray2")) {
		World_SetMapUpper(Nbt_TakeArray(tag, ".cw map blocks2"));
	}
#endif
}

static void Cw_Callback_2(struct NbtTag* tag) {
	if (IsTag(tag->parent, "MapGenerator")) {
		if (IsTag(tag, "Seed")) { World.Seed = NbtTag_I32(tag); return; }
		return;
	}
	if (!IsTag(tag->parent, "Spawn")) return;
	spawn_point->flags = LU_HAS_POS | LU_HAS_YAW | LU_HAS_PITCH;
	
	if (IsTag(tag, "X")) { spawn_point->pos.x = NbtTag_I16(tag); return; }
	if (IsTag(tag, "Y")) { spawn_point->pos.y = NbtTag_I16(tag); return; }
	if (IsTag(tag, "Z")) { spawn_point->pos.z = NbtTag_I16(tag); return; }
	if (IsTag(tag, "H")) { spawn_point->yaw   = Math_Packed2Deg(NbtTag_U8(tag)); return; }
	if (IsTag(tag, "P")) { spawn_point->pitch = Math_Packed2Deg(NbtTag_U8(tag)); return; }
}

static BlockID cw_curID;
static int cw_colR, cw_colG, cw_colB;
static PackedCol Cw_ParseColor(PackedCol defValue) {
	int r = cw_colR, g = cw_colG, b = cw_colB;
	if (r > 255 || g > 255 || b > 255) return defValue;
	return PackedCol_Make(r, g, b, 255);
}

static void Cw_Callback_4(struct NbtTag* tag) {
	BlockID id = cw_curID;
	struct LocalPlayer* p = &LocalPlayer_Instances[0];

	if (!IsTag(tag->parent->parent, "CPE")) return;
	if (!IsTag(tag->parent->parent->parent, "Metadata")) return;

	if (IsTag(tag->parent, "ClickDistance")) {
		if (IsTag(tag, "Distance")) { p->ReachDistance = NbtTag_U16(tag) / 32.0f; return; }
	}
	if (IsTag(tag->parent, "EnvWeatherType")) {
		if (IsTag(tag, "WeatherType")) { Env.Weather = NbtTag_U8(tag); return; }
	}

	if (IsTag(tag->parent, "EnvMapAppearance")) {
		if (IsTag(tag, "SideBlock")) { Env.SidesBlock = NbtTag_U8(tag);  return; }
		if (IsTag(tag, "EdgeBlock")) { Env.EdgeBlock  = NbtTag_U8(tag);  return; }
		if (IsTag(tag, "SideLevel")) { Env.EdgeHeight = NbtTag_I16(tag); return; }

		if (IsTag(tag, "TextureURL")) {
			cc_string url = NbtTag_String(tag);
			if (url.length) Server_RetrieveTexturePack(&url);
			return;
		}
	}

	if (IsTag(tag->parent, "EnvMapAspect")) {
		if (IsTag(tag, "EdgeBlock"))    { Env.EdgeBlock    = NbtTag_U16(tag); return; }
		if (IsTag(tag, "SideBlock"))    { Env.SidesBlock   = NbtTag_U16(tag); return; }
		if (IsTag(tag, "EdgeHeight"))   { Env.EdgeHeight   = NbtTag_I32(tag); return; }
		if (IsTag(tag, "SidesOffset"))  { Env.SidesOffset  = NbtTag_I32(tag); return; }
		if (IsTag(tag, "CloudsHeight")) { Env.CloudsHeight = NbtTag_I32(tag); return; }
		if (IsTag(tag, "CloudsSpeed"))  { Env.CloudsSpeed  = NbtTag_F32(tag); return; }
		if (IsTag(tag, "WeatherSpeed")) { Env.WeatherSpeed   = NbtTag_F32(tag); return; }
		if (IsTag(tag, "WeatherFade"))  { Env.WeatherFade    = NbtTag_F32(tag); return; }
		if (IsTag(tag, "ExpFog"))       { Env.ExpFog         = NbtTag_U8(tag);  return; }
		if (IsTag(tag, "SkyboxHor"))    { Env.SkyboxHorSpeed = NbtTag_F32(tag); return; }
		if (IsTag(tag, "SkyboxVer"))    { Env.SkyboxVerSpeed = NbtTag_F32(tag); return; }
	}

	/* Callback for compound tag is called after all its children have been processed */
	if (IsTag(tag->parent, "EnvColors")) {
		if (IsTag(tag, "Sky")) {
			Env.SkyCol    = Cw_ParseColor(ENV_DEFAULT_SKY_COLOR); return;
		} else if (IsTag(tag, "Cloud")) {
			Env.CloudsCol = Cw_ParseColor(ENV_DEFAULT_CLOUDS_COLOR); return;
		} else if (IsTag(tag, "Fog")) {
			Env.FogCol    = Cw_ParseColor(ENV_DEFAULT_FOG_COLOR); return;
		} else if (IsTag(tag, "Sunlight")) {
			Env_SetSunCol(Cw_ParseColor(ENV_DEFAULT_SUN_COLOR)); return;
		} else if (IsTag(tag, "Ambient")) {
			Env_SetShadowCol(Cw_ParseColor(ENV_DEFAULT_SHADOW_COLOR)); return;
		} else if (IsTag(tag, "Skybox")) {
			Env.SkyboxCol = Cw_ParseColor(ENV_DEFAULT_SKYBOX_COLOR); return;
		} 
	}

	if (IsTag(tag->parent, "BlockDefinitions") && Game_AllowCustomBlocks) {
		static const cc_string blockStr = String_FromConst("Block");
		if (!String_CaselessStarts(&tag->name, &blockStr)) return;	

		/* hack for sprite draw (can't rely on order of tags when reading) */
		if (Blocks.SpriteOffset[id] == 0) {
			Blocks.SpriteOffset[id] = Blocks.Draw[id];
			Blocks.Draw[id] = DRAW_SPRITE;
		} else {
			Blocks.SpriteOffset[id] = 0;
		}

		Block_DefineCustom(id, false);
		Blocks.CanPlace[id]  = true;
		Blocks.CanDelete[id] = true;
		Event_RaiseVoid(&BlockEvents.PermissionsChanged);

		cw_curID = 0;
	}
}

static void Cw_Callback_5(struct NbtTag* tag) {
	BlockID id = cw_curID;
	cc_uint8* arr;
	cc_uint8 sound;

	if (!IsTag(tag->parent->parent->parent, "CPE")) return;
	if (!IsTag(tag->parent->parent->parent->parent, "Metadata")) return;

	if (IsTag(tag->parent->parent, "EnvColors")) {
		if (IsTag(tag, "R")) { cw_colR = NbtTag_U16(tag); return; }
		if (IsTag(tag, "G")) { cw_colG = NbtTag_U16(tag); return; }
		if (IsTag(tag, "B")) { cw_colB = NbtTag_U16(tag); return; }
	}

	if (IsTag(tag->parent->parent, "BlockDefinitions") && Game_AllowCustomBlocks) {
		if (IsTag(tag, "ID"))             { cw_curID = NbtTag_U8(tag);  return; }
		if (IsTag(tag, "ID2"))            { cw_curID = NbtTag_U16(tag); return; }
		if (IsTag(tag, "CollideType"))    { Blocks.Collide[id] = NbtTag_U8(tag); return; }
		if (IsTag(tag, "Speed"))          { Blocks.SpeedMultiplier[id] = NbtTag_F32(tag); return; }
		if (IsTag(tag, "TransmitsLight")) { Blocks.BlocksLight[id] = NbtTag_U8(tag) == 0; return; }
		if (IsTag(tag, "FullBright"))     { Blocks.Brightness[id] = Block_ReadBrightness(NbtTag_U8(tag)); return; }
		if (IsTag(tag, "BlockDraw"))      { Blocks.Draw[id] = NbtTag_U8(tag); return; }
		if (IsTag(tag, "Shape"))          { Blocks.SpriteOffset[id] = NbtTag_U8(tag); return; }

		if (IsTag(tag, "Name")) {
			cc_string name = NbtTag_String(tag);
			Block_SetName(id, &name);
			return;
		}

		if (IsTag(tag, "Textures")) {
			arr = NbtTag_U8_Array(tag, 6);
			if (!arr) return;

			Block_Tex(id, FACE_YMAX) = arr[0]; Block_Tex(id, FACE_YMIN) = arr[1];
			Block_Tex(id, FACE_XMIN) = arr[2]; Block_Tex(id, FACE_XMAX) = arr[3];
			Block_Tex(id, FACE_ZMIN) = arr[4]; Block_Tex(id, FACE_ZMAX) = arr[5];

			/* hacky way of storing upper 8 bits */
			if (tag->dataSize >= 12) {
				Block_Tex(id, FACE_YMAX) |= arr[6]  << 8; Block_Tex(id, FACE_YMIN) |= arr[7]  << 8;
				Block_Tex(id, FACE_XMIN) |= arr[8]  << 8; Block_Tex(id, FACE_XMAX) |= arr[9]  << 8;
				Block_Tex(id, FACE_ZMIN) |= arr[10] << 8; Block_Tex(id, FACE_ZMAX) |= arr[11] << 8;
			}
			return;
		}
		
		if (IsTag(tag, "WalkSound")) {
			sound = NbtTag_U8(tag);
			Blocks.DigSounds[id]  = sound;
			Blocks.StepSounds[id] = sound;
			if (sound == SOUND_GLASS) Blocks.StepSounds[id] = SOUND_STONE;
			return;
		}

		if (IsTag(tag, "Fog")) {
			arr = NbtTag_U8_Array(tag, 4);
			if (!arr) return;

			Blocks.FogDensity[id] = (arr[0] + 1) / 128.0f;
			/* Backwards compatibility with apps that use 0xFF to indicate no fog */
			if (arr[0] == 0 || arr[0] == 0xFF) Blocks.FogDensity[id] = 0.0f;
			Blocks.FogCol[id] = PackedCol_Make(arr[1], arr[2], arr[3], 255);
			return;
		}

		if (IsTag(tag, "Coords")) {
			arr = NbtTag_U8_Array(tag, 6);
			if (!arr) return;

			Blocks.MinBB[id].x = (cc_int8)arr[0] / 16.0f; Blocks.MaxBB[id].x = (cc_int8)arr[3] / 16.0f;
			Blocks.MinBB[id].y = (cc_int8)arr[1] / 16.0f; Blocks.MaxBB[id].y = (cc_int8)arr[4] / 16.0f;
			Blocks.MinBB[id].z = (cc_int8)arr[2] / 16.0f; Blocks.MaxBB[id].z = (cc_int8)arr[5] / 16.0f;
			return;
		}
	}
}

static void Cw_Callback(struct NbtTag* tag) {
	struct NbtTag* tmp = tag->parent;
	int depth = 0;
	while (tmp) { depth++; tmp = tmp->parent; }

	switch (depth) {
	case 1: Cw_Callback_1(tag); return;
	case 2: Cw_Callback_2(tag); return;
	case 4: Cw_Callback_4(tag); return;
	case 5: Cw_Callback_5(tag); return;
	}
	/* ClassicWorld -> Metadata -> CPE -> ExtName -> [values]
	        0             1         2        3          4   */
}

/* Imports a world from a .cw ClassicWorld map file */
/* Used by ClassiCube/ClassicalSharp */
static cc_result Cw_Load(struct Stream* stream) {
	return Nbt_Read(stream, Cw_Callback);
}


/*########################################################################################################################*
*-----------------------------------------------Java serialisation format-------------------------------------------------*
*#########################################################################################################################*/
/* Rather than bothering following this, I skip a lot of the java serialisation format
     Stream              BlockData        BlockDataTiny      BlockDataLong
|--------------|     |---------------|  |---------------|  |---------------| 
| U16 Magic    |     |>BlockDataTiny |  | TC_BLOCKDATA  |  | TC_BLOCKLONG  |
| U16 Version  |     |>BlockDataLong |  | U8 Size       |  | U32 Size      |
| Content[var] |     |_______________|  | U8 Data[size] |  | U8 Data[size] |
|______________|                        |_______________|  |_______________|

    Content
|--------------| |--------------| 
| >BlockData   | | >NewString   |
| >Object      | | >TC_RESET    |
|______________| | >TC_NULL     |
| >PrevObject     |
| >NewClass     |
| >NewEnum     |

}*/
enum JTypeCode {
	TC_NULL = 0x70, TC_REFERENCE = 0x71, TC_CLASSDESC = 0x72, TC_OBJECT = 0x73, 
	TC_STRING = 0x74, TC_ARRAY = 0x75, TC_BLOCKDATA = 0x77, TC_ENDBLOCKDATA = 0x78
};
enum JFieldType {
	JFIELD_I8 = 'B', JFIELD_F64 = 'D', JFIELD_F32 = 'F', JFIELD_I32 = 'I', JFIELD_I64 = 'J',
	JFIELD_BOOL = 'Z', JFIELD_ARRAY = '[', JFIELD_OBJECT = 'L'
};

#define JNAME_SIZE 48
#define SC_WRITE_METHOD 0x01
#define SC_SERIALIZABLE 0x02

static cc_uint32 reference_id;
#define Java_AddReference() reference_id++;

union JValue {
	cc_uint8  U8;
	cc_int32  I32;
	cc_uint32 U32;
	float     F32;
	struct { cc_uint8* Ptr; cc_uint32 Size; } Array;
};

struct JFieldDesc {
	cc_uint8 Type;
	cc_uint8 FieldName[JNAME_SIZE];
	union JValue Value; 
	/* "Value" field here is not accurate to how java deserialising actually works, */
	/*  but easier to store here since only care about Level class values anyways */
};

struct JClassDesc;
struct JClassDesc {
	cc_uint8 ClassName[JNAME_SIZE];
	cc_uint8 Flags;
	int FieldsCount;
	struct JFieldDesc Fields[38];
	cc_uint32 Reference;
	struct JClassDesc* SuperClass;
	struct JClassDesc* tmp;
};

struct JArray {
	struct JClassDesc* Desc;
	cc_uint8* Data; /* for byte arrays */
	cc_uint32 Size; /* for byte arrays */
};

struct JUnion {
	cc_uint8 Type;
	union {
		cc_uint8 String[JNAME_SIZE]; /* TC_STRING */
		struct JClassDesc* Object;   /* TC_OBJECT */
		struct JArray      Array;    /* TC_ARRAY */
	} Value;
};

static cc_result Java_ReadString(struct Stream* stream, cc_uint8* buffer) {
	int len;
	cc_result res;

	if ((res = Stream_Read(stream, buffer, 2))) return res;
	len = Stream_GetU16_BE(buffer);

	Mem_Set(buffer, 0, JNAME_SIZE);
	if (len > JNAME_SIZE) return JAVA_ERR_JSTRING_LEN;
	return Stream_Read(stream, buffer, len);
}


static cc_result Java_ReadObject(struct Stream* stream,     struct JUnion* object);
static cc_result Java_ReadObjectData(struct Stream* stream, struct JUnion* object);
static cc_result Java_SkipAnnotation(struct Stream* stream) {
	cc_uint8 typeCode, count;
	struct JUnion object;
	cc_result res;

	for (;;)
	{
		if ((res = stream->ReadU8(stream, &typeCode))) return res;

		switch (typeCode)
		{
		case TC_BLOCKDATA:
			if ((res = stream->ReadU8(stream, &count))) return res;
			if ((res = stream->Skip(stream, count)))    return res;
			break;
		case TC_ENDBLOCKDATA: 
			return 0;
		default:
			object.Type = typeCode;
			if ((res = Java_ReadObjectData(stream, &object))) return res;
			break;
		}
	}
	return 0;
}


/* Most .dat maps only use at most 16 different class types */
/*  However some survival test maps can use up to 30 */
#define CLASS_CAPACITY 30
static struct JClassDesc* class_cache;
static int class_count;
static cc_result Java_ReadClassDesc(struct Stream* stream, struct JClassDesc** desc);

static cc_result Java_ReadFieldDesc(struct Stream* stream, struct JFieldDesc* desc) {
	struct JUnion className;
	cc_result res;

	if ((res = stream->ReadU8(stream, &desc->Type)))      return res;
	if ((res = Java_ReadString(stream, desc->FieldName))) return res;

	if (desc->Type == JFIELD_ARRAY || desc->Type == JFIELD_OBJECT) {		
		return Java_ReadObject(stream, &className);
	}
	return 0;
}

static cc_result Java_ReadNewClassDesc(struct Stream* stream, struct JClassDesc* desc) {
	cc_uint8 count[2];
	cc_result res;
	int i;

	if ((res = Java_ReadString(stream, desc->ClassName))) return res;
	if ((res = stream->Skip(stream, 8)))                  return res; /* serial version UID */
	if ((res = stream->ReadU8(stream, &desc->Flags)))     return res;

	desc->Reference = reference_id;
	Java_AddReference();

	if ((res = Stream_Read(stream, count, 2))) return res;
	desc->FieldsCount = Stream_GetU16_BE(count);
	if (desc->FieldsCount > Array_Elems(desc->Fields)) return JAVA_ERR_JCLASS_FIELDS;
	
	for (i = 0; i < desc->FieldsCount; i++) {
		if ((res = Java_ReadFieldDesc(stream, &desc->Fields[i]))) return res;
	}

	if ((res = Java_SkipAnnotation(stream))) return res;
	return Java_ReadClassDesc(stream, &desc->SuperClass);
}

static cc_result Java_ReadClassDesc(struct Stream* stream, struct JClassDesc** desc) {
	cc_uint8 typeCode;
	cc_uint32 reference;
	cc_result res;
	int i;
	if ((res = stream->ReadU8(stream, &typeCode))) return res;

	switch (typeCode)
	{
		case TC_NULL:
			*desc = NULL;
			return 0;

		case TC_REFERENCE:
			if ((res = Stream_ReadU32_BE(stream, &reference))) return res;

			/* Use a previously defined ClassDescriptor */
			for (i = 0; i < class_count; i++) 
			{
				if (class_cache[i].Reference != reference) continue;
				
				*desc = &class_cache[i];
				return 0;
			}
			return JAVA_ERR_JCLASS_REFERENCE;

		case TC_CLASSDESC:
			if (class_count >= CLASS_CAPACITY) return JAVA_ERR_JCLASSES_COUNT;

			*desc = &class_cache[class_count++];
			return Java_ReadNewClassDesc(stream, *desc);
	}
	return JAVA_ERR_JCLASS_TYPE;
}


static cc_result Java_ReadValue(struct Stream* stream, cc_uint8 type, union JValue* value) {
	struct JUnion obj;
	cc_result res;

	switch (type) {
	case JFIELD_I8:
	case JFIELD_BOOL:
		return stream->ReadU8(stream, &value->U8);
	case JFIELD_F32:
	case JFIELD_I32:
		return Stream_ReadU32_BE(stream, &value->U32);
	case JFIELD_F64:
	case JFIELD_I64:
		return stream->Skip(stream, 8); /* (8) data */
	case JFIELD_OBJECT:
		return Java_ReadObject(stream, &obj);

	case JFIELD_ARRAY:
		if ((res = Java_ReadObject(stream, &obj))) return res;
		value->Array.Size = 0;
		value->Array.Ptr  = NULL;

		/* Array is a byte array */
		/* NOTE: This can technically leak memory if this array is discarded, however */
		/*  so far the only observed byte array in .dat files is the map blocks anyways */
		if (obj.Type == TC_ARRAY && obj.Value.Array.Desc->ClassName[1] == JFIELD_I8) {
			value->Array.Size = obj.Value.Array.Size;
			value->Array.Ptr  = obj.Value.Array.Data;
		}
		return 0;
	}
	return JAVA_ERR_JVALUE_TYPE;
}

static cc_result Java_ReadClassData(struct Stream* stream, struct JClassDesc* desc) {
	struct JFieldDesc* field;
	cc_result res;
	int i;

	if (!(desc->Flags & SC_SERIALIZABLE))
		return JAVA_ERR_JOBJECT_FLAGS;

	for (i = 0; i < desc->FieldsCount; i++) 
	{
		field = &desc->Fields[i];
		if ((res = Java_ReadValue(stream, field->Type, &field->Value))) return res;
	}

	if (desc->Flags & SC_WRITE_METHOD)
		return Java_SkipAnnotation(stream);
	return 0;
}

static cc_result Java_ReadNewString(struct Stream* stream, struct JUnion* object) {
	Java_AddReference();
	return Java_ReadString(stream, object->Value.String);
}

static cc_result Java_ReadNewObject(struct Stream* stream, struct JUnion* object) {
	struct JClassDesc* head;
	cc_result res;
	if ((res = Java_ReadClassDesc(stream, &object->Value.Object))) return res;
	Java_AddReference();

	/* Linked list of classes, with most superclass fist as head */
	head = object->Value.Object; head->tmp = NULL;
	while (head->SuperClass) {
		head->SuperClass->tmp = head;
		head = head->SuperClass;
	}

	/* Class data is read with most superclass first */
	while (head) {
		if ((res = Java_ReadClassData(stream, head))) return res;
		head = head->tmp;
	}
	return 0;
}

static cc_result Java_ReadNewArray(struct Stream* stream, struct JUnion* object) {
	struct JArray* array = &object->Value.Array;
	union JValue value;
	cc_uint32 count;
	cc_uint8 type;
	cc_result res;
	int i;

	if ((res = Java_ReadClassDesc(stream, &array->Desc))) return res;
	if ((res = Stream_ReadU32_BE(stream, &count)))        return res;
	type = array->Desc->ClassName[1];
	Java_AddReference();

	if (type != JFIELD_I8) {
		/* Not a byte array, so just discard the unnecessary values */
		for (i = 0; i < count; i++)
		{
			if ((res = Java_ReadValue(stream, type, &value))) return res;
		}
		return 0;
	}

	array->Size = count;
	array->Data = (cc_uint8*)Mem_TryAlloc(count, 1);

	if (!array->Data) return ERR_OUT_OF_MEMORY;
	res = Stream_Read(stream, array->Data, count);
	if (res) { Mem_Free(array->Data); }
	return res;
}

static cc_result Java_ReadObjectData(struct Stream* stream, struct JUnion* object) {
	cc_uint32 reference;
	switch (object->Type) 
	{
		case TC_STRING:    return Java_ReadNewString(stream, object);
		case TC_NULL:      return 0;
		case TC_REFERENCE: return Stream_ReadU32_BE(stream, &reference);
		case TC_OBJECT:    return Java_ReadNewObject(stream, object);
		case TC_ARRAY:     return Java_ReadNewArray(stream,  object);
	}
	return JAVA_ERR_INVALID_TYPECODE;
}

static cc_result Java_ReadObject(struct Stream* stream, struct JUnion* object) {
	cc_result res;
	if ((res = stream->ReadU8(stream, &object->Type))) return res;
	return Java_ReadObjectData(stream, object);
}

static int Java_I32(struct JFieldDesc* field) {
	if (field->Type != JFIELD_I32) Process_Abort("Field type must be Int32");
	return field->Value.I32;
}


/*########################################################################################################################*
*-------------------------------------------------Minecraft .dat format---------------------------------------------------*
*#########################################################################################################################*/
/* Minecraft Classic used 3 different GZIP compressed binary map formats throughout its various versions
Preclassic - Classic 0.12:
    U8* "Blocks"     (256x64x256 array)
Classic 0.13:
	U32 "Identifier" (must be 0x271BB788)
	U8  "Version"    (must be 1)
	STR "Name"       (ignored)
	STR "Author"     (ignored)
	U64 "Creation"   (ignored)
	U16 "Width"
	U16 "Length"
	U16 "Height"
	U8* "Blocks"
Classic 0.15 to Classic 0.30:
	U32 "Identifier" (must be 0x271BB788)
	U8  "Version"    (must be 2)
	VAR "Level"      (Java serialised level object instance)
}*/

static void Dat_Format0And1(void) {
	/* Formats 0 and 1 don't store spawn position, so use default of map centre */
	spawn_point = NULL;

	/* Similiar env to how it appears in preclassic - 0.13 classic client */
	Env.CloudsHeight = -30000;
	Env.SkyCol       = PackedCol_Make(0x7F, 0xCC, 0xFF, 0xFF);
	Env.FogCol       = PackedCol_Make(0x7F, 0xCC, 0xFF, 0xFF);
}

static cc_result Dat_LoadFormat0(struct Stream* stream) {
	Dat_Format0And1();
	/* Similiar env to how it appears in preclassic client */
	Env.EdgeBlock  = BLOCK_AIR;
	Env.SidesBlock = BLOCK_AIR;

	/* Map 'format' is just the 256x64x256 blocks of the level */
	World.Width  = 256;
	World.Height =  64;
	World.Length = 256;

	#define PC_VOLUME (256 * 64 * 256)
	World.Volume = PC_VOLUME;
	World.Blocks = (BlockRaw*)Mem_TryAlloc(PC_VOLUME, 1);
	if (!World.Blocks) return ERR_OUT_OF_MEMORY;

	/* First 5 bytes already read earlier as .dat header */
	Mem_Set(World.Blocks, BLOCK_STONE, 5);
	return Stream_Read(stream, World.Blocks + 5, PC_VOLUME - 5);
}

static cc_result Dat_LoadFormat1(struct Stream* stream) {
	cc_uint8 level_name[JNAME_SIZE];
	cc_uint8 level_author[JNAME_SIZE];
	cc_uint8 header[8 + 2 + 2 + 2];
	cc_result res;

	Dat_Format0And1();
	if ((res = Java_ReadString(stream,   level_name))) return res;
	if ((res = Java_ReadString(stream, level_author))) return res;
	if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
	
	/* bytes 0-8 = created timestamp (currentTimeMillis) */
	World.Width  = Stream_GetU16_BE(header +  8);
	World.Length = Stream_GetU16_BE(header + 10);
	World.Height = Stream_GetU16_BE(header + 12);
	return Map_ReadBlocks(stream);
}

static cc_result Dat_LoadFormat2(struct Stream* stream) {
	struct JClassDesc classes[CLASS_CAPACITY];
	cc_uint8 header[2 + 2];
	struct JUnion obj;
	struct JClassDesc* desc;
	struct JFieldDesc* field;
	cc_string fieldName;
	cc_result res;
	int i;
	if ((res = Stream_Read(stream, header, sizeof(header)))) return res;

	/* Reset state for Java Serialisation */
	class_cache  = classes;
	class_count  = 0;
	reference_id = 0x7E0000;

	/* Java seralisation headers */
	if (Stream_GetU16_BE(header + 0) != 0xACED) return DAT_ERR_JIDENTIFIER;
	if (Stream_GetU16_BE(header + 2) != 0x0005) return DAT_ERR_JVERSION;

	if ((res = Java_ReadObject(stream, &obj)))  return res;
	if (obj.Type != TC_OBJECT)                  return DAT_ERR_ROOT_OBJECT;
	desc = obj.Value.Object;

	for (i = 0; i < desc->FieldsCount; i++) 
	{
		field     = &desc->Fields[i];
		fieldName = String_FromRaw((char*)field->FieldName, JNAME_SIZE);

		if (String_CaselessEqualsConst(&fieldName, "width")) {
			World.Width  = Java_I32(field);
		} else if (String_CaselessEqualsConst(&fieldName, "height")) {
			World.Length = Java_I32(field);
		} else if (String_CaselessEqualsConst(&fieldName, "depth")) {
			World.Height = Java_I32(field);
		} else if (String_CaselessEqualsConst(&fieldName, "blocks")) {
			if (field->Type != JFIELD_ARRAY) Process_Abort("Blocks field must be Array");
			World.Blocks = field->Value.Array.Ptr;
			World.Volume = field->Value.Array.Size;
		} else if (String_CaselessEqualsConst(&fieldName, "xSpawn")) {
			spawn_point->pos.x = (float)Java_I32(field);
			spawn_point->flags = LU_HAS_POS;
		} else if (String_CaselessEqualsConst(&fieldName, "ySpawn")) {
			spawn_point->pos.y = (float)Java_I32(field);
			spawn_point->flags = LU_HAS_POS;
		} else if (String_CaselessEqualsConst(&fieldName, "zSpawn")) {
			spawn_point->pos.z = (float)Java_I32(field);
			spawn_point->flags = LU_HAS_POS;
		}
	}
	return 0;
}

/* Imports a world from a .dat classic map file */
/* Used by Minecraft Classic/WoM client */
static cc_result Dat_Load(struct Stream* stream) {
	cc_uint8 header[4 + 1];
	cc_uint32 signature;
	cc_result res;

	struct Stream compStream;
	struct InflateState state;
	Inflate_MakeStream2(&compStream, &state, stream);
	if ((res = Map_SkipGZipHeader(stream)))                       return res;
	if ((res = Stream_Read(&compStream, header, sizeof(header)))) return res;

	signature = Stream_GetU32_BE(header + 0);
	switch (signature)
	{
		/* Classic map format signature */
	case 0x271BB788: break;
		/* Not an actual signature, but 99% of preclassic */
		/*  to classic 0.12 maps start with these 4 bytes */
	case 0x01010101: return Dat_LoadFormat0(&compStream);
		/* Bogus .dat file */
	default:         return DAT_ERR_IDENTIFIER;
	}

	/* Format version */
	switch (header[4])
	{
		/* Format version 1 = classic 0.13 */
	case 0x01: return Dat_LoadFormat1(&compStream);
		/* Format version 2 = classic 0.15 to 0.30 */
	case 0x02: return Dat_LoadFormat2(&compStream);
		/* Bogus .dat file */
	default:   return DAT_ERR_VERSION;
	}
}


/*########################################################################################################################*
*-----------------------------------------------------MCLevel format------------------------------------------------------*
*#########################################################################################################################*/
/* MCLevel is a NBT tag based map format used by Minecraft Indev. Tags not listed below are discarded.
COMPOUND "MinecraftLevel" {
	COMPOUND "Map" {
		I16 "Width", "Height", "Length"
		I16 "Spawn" [3]
		U8* "Blocks"
	}
	COMPOUND "Environment" {
		I32 "SkyColor"
		I32 "FogColor"
		I32 "CloudColor"
		I16 "CloudHeight"
	}
}*/
static int mcl_edgeHeight, mcl_sidesHeight;

static void MCLevel_ParseMap(struct NbtTag* tag) {
	if (IsTag(tag, "width"))  { World.Width  = NbtTag_U16(tag); return; }
	if (IsTag(tag, "height")) { World.Height = NbtTag_U16(tag); return; }
	if (IsTag(tag, "length")) { World.Length = NbtTag_U16(tag); return; }

	if (IsTag(tag, "blocks")) {
		World.Volume = tag->dataSize;
		World.Blocks = Nbt_TakeArray(tag, ".mclevel map blocks");
	}
}

static PackedCol MCLevel_ParseColor(struct NbtTag* tag) {
	int RGB = NbtTag_I32(tag);
	return PackedCol_Make(RGB >> 16, RGB >> 8, RGB, 255);
}

static void MCLevel_ParseEnvironment(struct NbtTag* tag) {
	if (IsTag(tag, "SkyColor")) {
		Env.SkyCol    = MCLevel_ParseColor(tag);
	} else if (IsTag(tag, "FogColor")) {
		Env.FogCol    = MCLevel_ParseColor(tag);
	} else if (IsTag(tag, "CloudColor")) {
		Env.CloudsCol = MCLevel_ParseColor(tag);
	} else if (IsTag(tag, "CloudHeight")) {
		Env.CloudsHeight = NbtTag_U16(tag);
	} else if (IsTag(tag, "SurroundingGroundType")) {
		Env.SidesBlock  = NbtTag_U8(tag);
		/* TODO need to explore this fully */
		if (Env.SidesBlock == BLOCK_GRASS) Env.SidesBlock = BLOCK_DIRT;
	} else if (IsTag(tag, "SurroundingWaterType")) {
		Env.EdgeBlock   = NbtTag_U8(tag);
	} else if (IsTag(tag, "SurroundingGroundHeight")) {
		mcl_sidesHeight = NbtTag_U16(tag);
	} else if (IsTag(tag, "SurroundingWaterHeight")) {
		mcl_edgeHeight  = NbtTag_U16(tag);
	}
	/* TODO: SkyBrightness */
}


static void MCLevel_Callback_2(struct NbtTag* tag) {
	struct NbtTag* group = tag->parent;
	if (IsTag(group, "Map")) {
		MCLevel_ParseMap(tag);
	} else if (IsTag(group, "Environment")) {
		MCLevel_ParseEnvironment(tag);
	}
}

static void MCLevel_Callback_3(struct NbtTag* tag) {
	struct NbtTag* group = tag->parent->parent;
	struct NbtTag* field = tag->parent;

	if (IsTag(group, "Map") && IsTag(field, "spawn")) {
		cc_int16 value     = NbtTag_I16(tag);
		spawn_point->flags = LU_HAS_POS;

		if (tag->listIndex == 0) spawn_point->pos.x = value;
		if (tag->listIndex == 1) spawn_point->pos.y = value - 1.0f;
		if (tag->listIndex == 2) spawn_point->pos.z = value;
	}
}

static void MCLevel_Callback(struct NbtTag* tag) {
	struct NbtTag* tmp = tag->parent;
	int depth = 0;
	while (tmp) { depth++; tmp = tmp->parent; }

	switch (depth) {
	case 2: MCLevel_Callback_2(tag); return;
	case 3: MCLevel_Callback_3(tag); return;
	}
	/* MinecraftLevel -> Map/Environment -> [value]
			0					1				 2 */
}

/* Imports a world from a .mclevel NBT map file */
/* Used by Minecraft Indev client */
static cc_result MCLevel_Load(struct Stream* stream) {
	cc_result res = Nbt_Read(stream, MCLevel_Callback);

	Env.EdgeHeight  = mcl_edgeHeight;
	Env.SidesOffset = mcl_sidesHeight - mcl_edgeHeight;
	return res;
}


/*########################################################################################################################*
*--------------------------------------------------ClassicWorld export----------------------------------------------------*
*#########################################################################################################################*/
static cc_uint8* Cw_WriteColor(cc_uint8* data, const char* name, PackedCol color) {
	data = Nbt_WriteDict(data, name);
	{
		data  = Nbt_WriteUInt16(data, "R", PackedCol_R(color));
		data  = Nbt_WriteUInt16(data, "G", PackedCol_G(color));
		data  = Nbt_WriteUInt16(data, "B", PackedCol_B(color));
	} *data++ = NBT_END;

	return data;
}

static const cc_uint8 cw_end[4] = {
			NBT_END,
		NBT_END,
	NBT_END,
NBT_END,
};

static cc_result Cw_WriteBockDef(struct Stream* stream, int b) {
	cc_uint8 buffer[1024];
	char nameBuffer[10];
	cc_uint8* cur;
	cc_string name;
	cc_bool sprite = Blocks.Draw[b] == DRAW_SPRITE;
	TextureLoc tex;
	cc_uint8 fog;
	PackedCol col;
	Vec3 minBB, maxBB;	

	/* Hacky unique tag name for each by using hex of block */
	String_InitArray_NT(name, nameBuffer);
	String_AppendConst(&name, "Block");
	String_AppendHex(&name, b >> 8);
	String_AppendHex(&name, b);
	nameBuffer[9] = '\0';

	cur = buffer;
	cur = Nbt_WriteDict(cur, nameBuffer);
	{
		cur  = Nbt_WriteUInt8(cur,  "ID", b);
		/* It would be have been better to just change ID to be a I16 */
		/* Unfortunately this isn't backwards compatible with ClassicalSharp */
		cur  = Nbt_WriteUInt16(cur, "ID2", b);
		cur  = Nbt_WriteUInt8(cur,  "CollideType", Blocks.Collide[b]);
		cur  = Nbt_WriteFloat(cur,  "Speed", Blocks.SpeedMultiplier[b]);

		/* Originally only up to 256 textures were supported, which used up 6 bytes total */
		/*  Later, support for more textures was added, which requires 2 bytes per texture */
		/*   For backwards compatibility, the lower byte of each texture is */
		/*   written into first 6 bytes, then higher byte into next 6 bytes (ugly hack) */
		cur = Nbt_WriteArray(cur, "Textures", 12);
		tex = Block_Tex(b, FACE_YMAX); cur[0] = (cc_uint8)tex; cur[ 6] = (cc_uint8)(tex >> 8);
		tex = Block_Tex(b, FACE_YMIN); cur[1] = (cc_uint8)tex; cur[ 7] = (cc_uint8)(tex >> 8);
		tex = Block_Tex(b, FACE_XMIN); cur[2] = (cc_uint8)tex; cur[ 8] = (cc_uint8)(tex >> 8);
		tex = Block_Tex(b, FACE_XMAX); cur[3] = (cc_uint8)tex; cur[ 9] = (cc_uint8)(tex >> 8);
		tex = Block_Tex(b, FACE_ZMIN); cur[4] = (cc_uint8)tex; cur[10] = (cc_uint8)(tex >> 8);
		tex = Block_Tex(b, FACE_ZMAX); cur[5] = (cc_uint8)tex; cur[11] = (cc_uint8)(tex >> 8);
		cur += 12;

		cur  = Nbt_WriteUInt8(cur,  "TransmitsLight", Blocks.BlocksLight[b] ? 0 : 1);
		cur  = Nbt_WriteUInt8(cur,  "WalkSound",      Blocks.DigSounds[b]);
		cur  = Nbt_WriteUInt8(cur,  "FullBright",     Block_WriteFullBright(Blocks.Brightness[b]));
		cur  = Nbt_WriteUInt8(cur,  "Shape",          sprite ? 0 : (cc_uint8)(Blocks.MaxBB[b].y * 16));
		cur  = Nbt_WriteUInt8(cur,  "BlockDraw",      sprite ? Blocks.SpriteOffset[b] : Blocks.Draw[b]);

		cur = Nbt_WriteArray(cur, "Fog", 4);
		fog = (cc_uint8)(128 * Blocks.FogDensity[b] - 1);
		col = Blocks.FogCol[b];
		cur[0] = Blocks.FogDensity[b] ? fog : 0xFF; /* write 0xFF instead of 0 for backwards compatibility */
		cur[1] = PackedCol_R(col); cur[2] = PackedCol_G(col); cur[3] = PackedCol_B(col);
		cur += 4;

		cur  = Nbt_WriteArray(cur,  "Coords", 6);
		minBB  = Blocks.MinBB[b]; maxBB = Blocks.MaxBB[b];
		cur[0] = (cc_uint8)(minBB.x * 16); cur[1] = (cc_uint8)(minBB.y * 16); cur[2] = (cc_uint8)(minBB.z * 16);
		cur[3] = (cc_uint8)(maxBB.x * 16); cur[4] = (cc_uint8)(maxBB.y * 16); cur[5] = (cc_uint8)(maxBB.z * 16);
		cur += 6;

		name = Block_UNSAFE_GetName(b);
		cur  = Nbt_WriteString(cur, "Name", &name);
	} *cur++ = NBT_END;

	return Stream_Write(stream, buffer, (int)(cur - buffer));
}

cc_result Cw_Save(struct Stream* stream) {
	struct LocalPlayer* p = Entities.CurPlayer;
	cc_uint8 buffer[2048];
	cc_uint8* cur;
	cc_result res;
	int b;

	cur = buffer;
	cur = Nbt_WriteDict(cur,   "ClassicWorld");
	cur = Nbt_WriteUInt8(cur,  "FormatVersion", 1);
	cur = Nbt_WriteArray(cur,  "UUID", WORLD_UUID_LEN); Mem_Copy(cur, World.Uuid, WORLD_UUID_LEN); cur += WORLD_UUID_LEN;
	cur = Nbt_WriteUInt16(cur, "X", World.Width);
	cur = Nbt_WriteUInt16(cur, "Y", World.Height);
	cur = Nbt_WriteUInt16(cur, "Z", World.Length);

	cur = Nbt_WriteDict(cur, "MapGenerator");
	{
		cur  = Nbt_WriteInt32(cur, "Seed", World.Seed);
	} *cur++ = NBT_END;
	

	/* TODO: Maybe keep real spawn too? */
	cur = Nbt_WriteDict(cur, "Spawn");
	{
		cur  = Nbt_WriteUInt16(cur, "X", (cc_uint16)p->Base.Position.x);
		cur  = Nbt_WriteUInt16(cur, "Y", (cc_uint16)p->Base.Position.y);
		cur  = Nbt_WriteUInt16(cur, "Z", (cc_uint16)p->Base.Position.z);
		cur  = Nbt_WriteUInt8(cur,  "H", Math_Deg2Packed(p->SpawnYaw));
		cur  = Nbt_WriteUInt8(cur,  "P", Math_Deg2Packed(p->SpawnPitch));
	} *cur++ = NBT_END;
	cur = Nbt_WriteArray(cur, "BlockArray", World.Volume);

	if ((res = Stream_Write(stream, buffer, (int)(cur - buffer)))) return res;
	if ((res = Stream_Write(stream, World.Blocks, World.Volume)))  return res;

#ifdef EXTENDED_BLOCKS
	if (World.Blocks != World.Blocks2) {
		cur = buffer;
		cur = Nbt_WriteArray(cur, "BlockArray2", World.Volume);

		if ((res = Stream_Write(stream, buffer, (int)(cur - buffer)))) return res;
		if ((res = Stream_Write(stream, World.Blocks2, World.Volume))) return res;
	}
#endif

	cur = buffer;
	cur = Nbt_WriteDict(cur, "Metadata");
	cur = Nbt_WriteDict(cur, "CPE");
	{
		cur = Nbt_WriteDict(cur, "ClickDistance");
		{
			cur  = Nbt_WriteUInt16(cur, "Distance", (cc_uint16)(p->ReachDistance * 32));
		} *cur++ = NBT_END;

		cur = Nbt_WriteDict(cur, "EnvWeatherType");
		{
			cur  = Nbt_WriteUInt8(cur, "WeatherType", Env.Weather);
		} *cur++ = NBT_END;

		cur = Nbt_WriteDict(cur, "EnvColors");
		{
			cur = Cw_WriteColor(cur, "Sky",      Env.SkyCol);
			cur = Cw_WriteColor(cur, "Cloud",    Env.CloudsCol);
			cur = Cw_WriteColor(cur, "Fog",      Env.FogCol);
			cur = Cw_WriteColor(cur, "Ambient",  Env.ShadowCol);
			cur = Cw_WriteColor(cur, "Sunlight", Env.SunCol);
			cur = Cw_WriteColor(cur, "Skybox",   Env.SkyboxCol);
		} *cur++ = NBT_END;

		cur = Nbt_WriteDict(cur, "EnvMapAppearance");
		{
			cur  = Nbt_WriteUInt8(cur, "SideBlock", (BlockRaw)Env.SidesBlock);
			cur  = Nbt_WriteUInt8(cur, "EdgeBlock", (BlockRaw)Env.EdgeBlock);
			cur  = Nbt_WriteUInt16(cur, "SideLevel", Env.EdgeHeight);
			cur  = Nbt_WriteString(cur, "TextureURL", &TexturePack_Url);
		} *cur++ = NBT_END;

		cur = Nbt_WriteDict(cur, "EnvMapAspect");
		{
			cur  = Nbt_WriteUInt16(cur, "EdgeBlock",    Env.EdgeBlock);
			cur  = Nbt_WriteUInt16(cur, "SideBlock",    Env.SidesBlock);
			cur  = Nbt_WriteInt32(cur,  "EdgeHeight",   Env.EdgeHeight);
			cur  = Nbt_WriteInt32(cur,  "SidesOffset",  Env.SidesOffset);
			cur  = Nbt_WriteInt32(cur,  "CloudsHeight", Env.CloudsHeight);
			cur  = Nbt_WriteFloat(cur,  "CloudsSpeed",  Env.CloudsSpeed);
			cur  = Nbt_WriteFloat(cur,  "WeatherSpeed", Env.WeatherSpeed);
			cur  = Nbt_WriteFloat(cur,  "WeatherFade",  Env.WeatherFade);
			cur  = Nbt_WriteUInt8(cur,  "ExpFog",       (cc_uint8)Env.ExpFog);
			cur  = Nbt_WriteFloat(cur,  "SkyboxHor",    Env.SkyboxHorSpeed);
			cur  = Nbt_WriteFloat(cur,  "SkyboxVer",    Env.SkyboxVerSpeed);
		} *cur++ = NBT_END;

		cur = Nbt_WriteDict(cur, "BlockDefinitions");
		if ((res = Stream_Write(stream, buffer, (int)(cur - buffer)))) return res;

		{
			/* Write block definitions in reverse order so that software that only reads byte 'ID' */
			/* still loads correct first 256 block defs when saving a map with over 256 block defs */
			for (b = BLOCK_MAX_DEFINED; b >= 1; b--) {
				if (!Block_IsCustomDefined(b)) continue;
				if ((res = Cw_WriteBockDef(stream, b))) return res;
			}
		}
	}
	return Stream_Write(stream, cw_end, sizeof(cw_end));
}


/*########################################################################################################################*
*---------------------------------------------------Schematic export------------------------------------------------------*
*#########################################################################################################################*/
static cc_uint8 sc_begin[] = {
NBT_DICT, 0,9, 'S','c','h','e','m','a','t','i','c',
	NBT_STR,  0,9,  'M','a','t','e','r','i','a','l','s', 0,7, 'C','l','a','s','s','i','c',
	NBT_I16,  0,5,  'W','i','d','t','h',                 0,0,
	NBT_I16,  0,6,  'H','e','i','g','h','t',             0,0,
	NBT_I16,  0,6,  'L','e','n','g','t','h',             0,0,
	NBT_I8S,  0,6,  'B','l','o','c','k','s',             0,0,0,0,
};
static cc_uint8 sc_data[] = {
	NBT_I8S,  0,4,  'D','a','t','a',                     0,0,0,0,
};
static cc_uint8 sc_end[] = {
	NBT_LIST, 0,8,  'E','n','t','i','t','i','e','s',                 NBT_DICT, 0,0,0,0,
	NBT_LIST, 0,12, 'T','i','l','e','E','n','t','i','t','i','e','s', NBT_DICT, 0,0,0,0,
NBT_END,
};

cc_result Schematic_Save(struct Stream* stream) {
	cc_uint8 tmp[256], chunk[8192] = { 0 };
	cc_result res;
	int i;

	Mem_Copy(tmp, sc_begin, sizeof(sc_begin));
	{
		Stream_SetU16_BE(&tmp[41], World.Width);
		Stream_SetU16_BE(&tmp[52], World.Height);
		Stream_SetU16_BE(&tmp[63], World.Length);
		Stream_SetU32_BE(&tmp[74], World.Volume);
	}
	if ((res = Stream_Write(stream, tmp, sizeof(sc_begin)))) return res;
	if ((res = Stream_Write(stream, World.Blocks, World.Volume))) return res;

	Mem_Copy(tmp, sc_data, sizeof(sc_data));
	{
		Stream_SetU32_BE(&tmp[7], World.Volume);
	}
	if ((res = Stream_Write(stream, tmp, sizeof(sc_data)))) return res;

	for (i = 0; i < World.Volume; i += sizeof(chunk)) {
		int count = World.Volume - i; count = min(count, sizeof(chunk));
		if ((res = Stream_Write(stream, chunk, count))) return res;
	}
	return Stream_Write(stream, sc_end, sizeof(sc_end));
}


/*########################################################################################################################*
*------------------------------------------------------Dat export---------------------------------------------------------*
*#########################################################################################################################*/
static const struct JField {
	cc_uint8 type, isFloat;
	const char* name;
	void* value;
} level_fields[] = {
	{ JFIELD_I32, false, "width",  &World.Width  },
	{ JFIELD_I32, false, "depth",  &World.Height },
	{ JFIELD_I32, false, "height", &World.Length },
	{ JFIELD_I32, true,  "xSpawn", &LocalPlayer_Instances[0].Base.Position.x },
	{ JFIELD_I32, true,  "ySpawn", &LocalPlayer_Instances[0].Base.Position.y },
	{ JFIELD_I32, true,  "zSpawn", &LocalPlayer_Instances[0].Base.Position.z },
	{ JFIELD_ARRAY,0, "blocks" }
	/* TODO classic only blocks */
};

static int WriteJavaString(cc_uint8* dst, const char* value) {
	int length = String_Length(value);
	dst[0] = 0;
	dst[1] = length;
	Mem_Copy(dst + 2, value, length);
	return length;
}

static cc_result WriteClassDesc(struct Stream* stream, cc_uint8 typecode, const char* klass, 
								int numFields, const struct JField* fields) {
	cc_uint8 header[256] = { 0 };
	static const cc_uint8 footer[] = {
		TC_ENDBLOCKDATA, /* classAnnotations */
		TC_NULL          /* superClassDesc */
	};
	int i, length;
	cc_result res;

	header[0] = typecode;
	header[1] = TC_CLASSDESC;
	length    = WriteJavaString(header + 2, klass);
	header[4 + length +  8] = SC_SERIALIZABLE;
	header[4 + length +  9] = 0;
	header[4 + length + 10] = numFields;

	if ((res = Stream_Write(stream, header, 15 + length))) return res;

	for (i = 0; i < numFields; i++) 
	{
		header[0] = fields[i].type;
		length    = WriteJavaString(header + 1, fields[i].name);

		if (fields[i].type == JFIELD_ARRAY) {
			header[3 + length + 0] = TC_STRING;
			WriteJavaString(&header[3 + length + 1], "[B");
			length += 5;
		}
		if ((res = Stream_Write(stream, header, 3 + length))) return res;
	}

	if ((res = Stream_Write(stream, footer, sizeof(footer)))) return res;
	return 0;
}

static const cc_uint8 cpe_fallback[] = {
	BLOCK_SLAB, BLOCK_BROWN_SHROOM, BLOCK_SAND, BLOCK_AIR, BLOCK_STILL_LAVA, BLOCK_PINK,
	BLOCK_GREEN, BLOCK_DIRT, BLOCK_BLUE, BLOCK_CYAN, BLOCK_GLASS, BLOCK_IRON, BLOCK_OBSIDIAN, BLOCK_WHITE,
	BLOCK_WOOD, BLOCK_STONE
};

#define DAT_BUFFER_SIZE (32 * 1024)
static cc_result WriteLevelBlocks(struct Stream* stream) {
	cc_uint8 buffer[DAT_BUFFER_SIZE];
	int i, bIndex = 0;
	cc_result res;
	BlockID b;

	for (i = 0; i < World.Volume; i++)
	{
		b = World_GetRawBlock(i);
		/* TODO: Better fallback decision (e.g. air if custom block is 'gas' type) */
		if (b > BLOCK_STONE_BRICK) b = BLOCK_STONE;
		/* TODO: Move to GameVersion.c and account for game version */
		if (b > BLOCK_OBSIDIAN) b = cpe_fallback[b - BLOCK_COBBLE_SLAB];

		buffer[bIndex] = (cc_uint8)b;
		bIndex++;
		if (bIndex < DAT_BUFFER_SIZE) continue;

		if ((res = Stream_Write(stream, buffer, DAT_BUFFER_SIZE))) return res;
		bIndex = 0;
	}

	if (bIndex == 0) return 0;
	return Stream_Write(stream, buffer, bIndex);
}

cc_result Dat_Save(struct Stream* stream) {
	static const cc_uint8 header[] = {
		0x27,0x1B,0xB7,0x88, 0x02, /* DAT signature + version */
		0xAC,0xED, 0x00,0x05       /* JSF signature + version */
	};
	const struct JField* field;
	cc_uint8 tmp[4];
	cc_result res;
	int i, value;

	if ((res = Stream_Write(stream, header, sizeof(header)))) return res;
	if ((res = WriteClassDesc(stream, TC_OBJECT, "com.mojang.minecraft.level.Level", 
					Array_Elems(level_fields), level_fields))) return res;

	/* Write field values */
	for (i = 0; i < Array_Elems(level_fields); i++) 
	{
		field = &level_fields[i];

		if (field->type == JFIELD_I32) {
			value = field->isFloat ? *((float*)field->value) : *((int*)field->value);
			Stream_SetU32_BE(tmp, value);
			if ((res = Stream_Write(stream, tmp, 4))) return res;
		} else {
			if ((res = WriteClassDesc(stream, TC_ARRAY, "[B", 0, NULL)))  return res;
			Stream_SetU32_BE(tmp, World.Volume);
			if ((res = Stream_Write(stream, tmp, 4))) return res;
			if ((res = WriteLevelBlocks(stream)))     return res;
		}
	}
	return 0;
}


/*########################################################################################################################*
*-------------------------------------------------------Formats component-------------------------------------------------*
*#########################################################################################################################*/
static struct MapImporter cw_imp    = { ".cw",      Cw_Load };
static struct MapImporter dat_imp   = { ".dat",     Dat_Load };
static struct MapImporter lvl_imp   = { ".lvl",     Lvl_Load };
static struct MapImporter mine_imp  = { ".mine",    Dat_Load };
static struct MapImporter fcm_imp   = { ".fcm",     Fcm_Load };
static struct MapImporter mclvl_imp = { ".mclevel", MCLevel_Load };

static void OnInit(void) {
	MapImporter_Register(&cw_imp);
	MapImporter_Register(&dat_imp);
	MapImporter_Register(&lvl_imp);
	MapImporter_Register(&mine_imp);
	MapImporter_Register(&fcm_imp);
	MapImporter_Register(&mclvl_imp);
}

static void OnFree(void) {
	imp_head = NULL;
}
#else
/* No point including map format code when can't save/load maps anyways */
struct MapImporter* MapImporter_Find(const cc_string* path) { return NULL; }
cc_result Map_LoadFrom(const cc_string* path) { return ERR_NOT_SUPPORTED; }

cc_result Cw_Save(struct Stream* stream)  { return ERR_NOT_SUPPORTED; }
cc_result Dat_Save(struct Stream* stream) { return ERR_NOT_SUPPORTED; }
cc_result Schematic_Save(struct Stream* stream) { return ERR_NOT_SUPPORTED; }

static void OnInit(void) { }
static void OnFree(void) { }
#endif

struct IGameComponent Formats_Component = {
	OnInit, /* Init  */
	OnFree  /* Free  */
};
