// Copyright (c) 2023 Alex Diener. All rights reserved.

#include "PROJECT_NAME/FileUtilities.h"
#include "PROJECT_NAME/GameData.h"
#include "PROJECT_NAME/Globals.h"
#include "MetadataKeys.h"
#include <assert.h>
#include <limits.h>
#include <string.h>

#define TILE_ID_ROOM_ENTRANCE 3

GameData * GameData_create(void) {
	extern const char EMBEDDATA_tilemaps_tilemap_bundle[];
	extern unsigned int EMBEDSIZE_tilemaps_tilemap_bundle;
	FileBundle * tileMapBundle = FileBundle_loadData(EMBEDDATA_tilemaps_tilemap_bundle, EMBEDSIZE_tilemaps_tilemap_bundle);
	unsigned int tileMapCount = tileMapBundle->entryCount;
	TileMapEditData * tileMaps[tileMapCount];
	for (unsigned int entryIndex = 0; entryIndex < tileMapCount; entryIndex++) {
		uint32_t entrySize;
		const void * entry = FileBundle_getFileAtIndex(tileMapBundle, entryIndex, &entrySize);
		tileMaps[entryIndex] = readTileMapEditData(entry, entrySize);
	}
	
	extern const char EMBEDDATA_world_zonemap[];
	extern unsigned int EMBEDSIZE_world_zonemap;
	TileZoneMap * zoneMap = readTileZoneMap(EMBEDDATA_world_zonemap, EMBEDSIZE_world_zonemap);
	GameData * zone = malloc(sizeof(*zone));
	zone->roomCount = zoneMap->roomCount;
	zone->rooms = malloc(zone->roomCount * sizeof(*zone->rooms));
	for (unsigned int roomIndex = 0; roomIndex < zoneMap->roomCount; roomIndex++) {
		zone->rooms[roomIndex].layers = NULL;
		for (unsigned int tileMapIndex = 0; tileMapIndex < tileMapCount; tileMapIndex++) {
			if (tileMaps[tileMapIndex]->identifier == zoneMap->rooms[roomIndex].tileMapID) {
				RoomData room;
				room.zoneLayer = zoneMap->rooms[roomIndex].zoneLayer;
				room.zonePosition = zoneMap->rooms[roomIndex].offset;
				room.identifier = tileMaps[tileMapIndex]->identifier;
				room.size = tileMaps[tileMapIndex]->size;
				room.layerCount = tileMaps[tileMapIndex]->layerCount;
				room.layers = malloc(room.layerCount * sizeof(*room.layers));
				for (unsigned int layerIndex = 0; layerIndex < room.layerCount; layerIndex++) {
					room.layers[layerIndex].type = valueGetInt32(hashGet(tileMaps[tileMapIndex]->layers[layerIndex].metadata, METADATA_KEY_type));
					room.layers[layerIndex].offset = Vector2i_subtract(tileMaps[tileMapIndex]->layers[layerIndex].offset, tileMaps[tileMapIndex]->origin);
					room.layers[layerIndex].tileGrid = TileInstanceGrid_copy(&tileMaps[tileMapIndex]->layers[layerIndex].grid);
				}
				room.musicID = valueGetInt32(hashGet(tileMaps[tileMapIndex]->metadata, METADATA_KEY_music));
				zone->rooms[roomIndex] = room;
				break;
			}
		}
		assert(zone->rooms[roomIndex].layers != NULL);
	}
	zone->startingRoomIdentifier = valueGetUInt32(hashGet(zoneMap->metadata, METADATA_KEY_start_room));
	zone->startingRoomEntranceName = strdup(valueGetString(hashGet(zoneMap->metadata, METADATA_KEY_start_entrance)));
	TileZoneMap_dispose(zoneMap);
	return zone;
}

void GameData_dispose(GameData * zone) {
	for (unsigned int roomIndex = 0; roomIndex < zone->roomCount; roomIndex++) {
		for (unsigned int layerIndex = 0; layerIndex < zone->rooms[roomIndex].layerCount; layerIndex++) {
			TileInstanceGrid_dispose(&zone->rooms[roomIndex].layers[layerIndex].tileGrid);
		}
		free(zone->rooms[roomIndex].layers);
	}
	free(zone->rooms);
	free(zone->startingRoomEntranceName);
	free(zone);
}

RoomData * GameData_getRoomDataByIdentifier(GameData * zone, TileMapID roomIdentifier, unsigned int * outRoomIndex) {
	for (unsigned int roomIndex = 0; roomIndex < zone->roomCount; roomIndex++) {
		if (zone->rooms[roomIndex].identifier == roomIdentifier) {
			if (outRoomIndex != NULL) {
				*outRoomIndex = roomIndex;
			}
			return &zone->rooms[roomIndex];
		}
	}
	return NULL;
}

unsigned int GameData_getRoomIndexAtZonePosition(GameData * zone, int zoneLayer, Vector2i zonePosition) {
	for (unsigned int roomIndex = 0; roomIndex < zone->roomCount; roomIndex++) {
		if (zone->rooms[roomIndex].zoneLayer == zoneLayer &&
		    zone->rooms[roomIndex].zonePosition.x <= zonePosition.x &&
		    zone->rooms[roomIndex].zonePosition.y <= zonePosition.y &&
		    zone->rooms[roomIndex].zonePosition.x + (int) zone->rooms[roomIndex].size.x > zonePosition.x &&
		    zone->rooms[roomIndex].zonePosition.y + (int) zone->rooms[roomIndex].size.y > zonePosition.y) {
			return roomIndex;
		}
	}
	return UINT_MAX;
}

DataHashTable * RoomData_getRoomEntranceByName(RoomData * room, const char * entranceName, Vector2i * outPosition) {
	for (unsigned int layerIndex = 0; layerIndex < room->layerCount; layerIndex++) {
		if (room->layers[layerIndex].type == ROOM_LAYER_TYPE_ENTITY) {
			TileInstance * tileInstances = room->layers[layerIndex].tileGrid.tileInstances;
			Vector2u layerSize = room->layers[layerIndex].tileGrid.size;
			for (unsigned int rowIndex = 0; rowIndex < layerSize.y; rowIndex++) {
				for (unsigned int columnIndex = 0; columnIndex < layerSize.x; columnIndex++) {
					if (tileInstances[rowIndex * layerSize.x + columnIndex].tileID == TILE_ID_ROOM_ENTRANCE &&
					    !strcmp(valueGetString(hashGet(tileInstances[rowIndex * layerSize.x + columnIndex].metadata, METADATA_KEY_identifier)), entranceName)) {
						if (outPosition != NULL) {
							outPosition->x = room->layers[layerIndex].offset.x + columnIndex;
							outPosition->y = room->layers[layerIndex].offset.y + rowIndex;
						}
						return tileInstances[rowIndex * layerSize.x + columnIndex].metadata;
					}
				}
			}
		}
	}
	return NULL;
}

Rect4i RoomData_getLocalBounds(RoomData * room) {
	return RECT4i(0, room->size.x, 0, room->size.y);
}

TilePropertyBits getTileProperties(TileID tileID) {
	TilePropertyBits * tileProperties = HashTable_get(g_tileProperties, HashTable_uint32Key(tileID));
	if (tileProperties != NULL) {
		return *tileProperties;
	}
	return 0;
}
