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

#include "gamemath/Vector3i.h"
#include "gamemath/VectorConversions.h"
#include "PROJECT_NAME/Atoms.h"
#include "PROJECT_NAME/EntityDestructibleTile.h"
#include "PROJECT_NAME/EntityPushBlock.h"
#include "PROJECT_NAME/EntityToken.h"
#include "PROJECT_NAME/EntityWallCrawler.h"
#include "PROJECT_NAME/GameState.h"
#include "PROJECT_NAME/RoomState.h"
#include "PROJECT_NAME/SharedDefinitions.h"
#include "utilities/IOUtilities.h"
#include "MetadataKeys.h"
#include <assert.h>
#include <limits.h>
#include <stdio.h>

#define stemobject_implementation RoomState

stemobject_vtable_begin();
stemobject_vtable_entry(dispose);
stemobject_vtable_end();

typedef enum TileIDWithEntity {
	TILE_ID_TOKEN           = 4,
	TILE_ID_WALL_CRUMBLY    = 6,
	TILE_ID_WALL_CRAWLER    = 7,
	TILE_ID_PUSH_BLOCK      = 8,
	TILE_ID_MULTIPUSH_BLOCK = 9
} TileIDWithEntity;

RoomState * RoomState_create(unsigned int roomID, RoomData * roomData, GameState * gameState, bool initEntities) {
	stemobject_create_implementation(init, roomID, roomData, gameState, initEntities)
}

static bool hasTileEntity(TileIDWithEntity tileID) {
	switch (tileID) {
		case TILE_ID_TOKEN:
		case TILE_ID_WALL_CRUMBLY:
		case TILE_ID_WALL_CRAWLER:
		case TILE_ID_PUSH_BLOCK:
		case TILE_ID_MULTIPUSH_BLOCK:
			return true;
	}
	return false;
}

static compat_type(GameEntity *) createGameEntityForTileID(RoomState * self, TileIDWithEntity tileID, Vector2i position, DataHashTable * metadata) {
	switch (tileID) {
		case TILE_ID_TOKEN:
			return EntityToken_create(position, self);
			
		case TILE_ID_WALL_CRUMBLY:
			return EntityDestructibleTile_create(position, false, self);
			
		case TILE_ID_WALL_CRAWLER:
			return EntityWallCrawler_create(position, valueGetInt32(hashGet(metadata, METADATA_KEY_facing)), self);
			
		case TILE_ID_PUSH_BLOCK:
			return EntityPushBlock_create(position, false, self);
			
		case TILE_ID_MULTIPUSH_BLOCK:
			return EntityPushBlock_create(position, true, self);
	}
	return NULL;
}

static void initRoomEntities(RoomState * self, TileInstanceGrid * tileGrid, Vector2i offset) {
	for (unsigned int rowIndex = 0; rowIndex < tileGrid->size.y; rowIndex++) {
		for (unsigned int columnIndex = 0; columnIndex < tileGrid->size.x; columnIndex++) {
			TileID tileID = tileGrid->tileInstances[rowIndex * tileGrid->size.x + columnIndex].tileID;
			DataHashTable * metadata = tileGrid->tileInstances[rowIndex * tileGrid->size.x + columnIndex].metadata;
			Vector2i position = VECTOR2i(columnIndex + offset.x, rowIndex + offset.y);
			GameEntity * entity = createGameEntityForTileID(self, tileID, position, metadata);
			if (entity != NULL) {
				self->entities[self->entityCount].entity = entity;
				self->entities[self->entityCount].owned = true;
				self->entityCount++;
			}
		}
	}
}

static void postinitEntities(RoomState * self) {
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		call_virtual(postinit, self->entities[entityIndex].entity);
	}
}

bool RoomState_init(RoomState * self, unsigned int roomID, RoomData * roomData, GameState * gameState, bool initEntities) {
	call_super(init, self);
	self->eventDispatcher = EventDispatcher_create();
	self->roomID = roomID;
	self->roomData = roomData;
	self->gameState = gameState;
	self->entities = NULL;
	self->entityCount = 0;
	self->visited = false;
	if (initEntities) {
		for (unsigned int layerIndex = 0; layerIndex < self->roomData->layerCount; layerIndex++) {
			if (self->roomData->layers[layerIndex].type == ROOM_LAYER_TYPE_ENTITY) {
				TileInstanceGrid * tileGrid = &self->roomData->layers[layerIndex].tileGrid;
				for (unsigned int rowIndex = 0; rowIndex < tileGrid->size.y; rowIndex++) {
					for (unsigned int columnIndex = 0; columnIndex < tileGrid->size.x; columnIndex++) {
						if (hasTileEntity(tileGrid->tileInstances[rowIndex * tileGrid->size.x + columnIndex].tileID)) {
							self->entityCount++;
						}
					}
				}
			}
		}
		self->private_ivar(entityAllocatedCount) = self->entityCount;
		if (self->entityCount > 0) {
			self->entities = malloc(self->private_ivar(entityAllocatedCount) * sizeof(*self->entities));
			self->entityCount = 0;
			for (unsigned int layerIndex = 0; layerIndex < self->roomData->layerCount; layerIndex++) {
				if (self->roomData->layers[layerIndex].type == ROOM_LAYER_TYPE_ENTITY) {
					initRoomEntities(self, &self->roomData->layers[layerIndex].tileGrid, self->roomData->layers[layerIndex].offset);
				}
			}
		}
		postinitEntities(self);
	}
	return true;
}

RoomState * RoomState_copy(RoomState * self) {
	RoomState * copy = RoomState_create(self->roomID, self->roomData, self->gameState, false);
	copy->entityCount = copy->private_ivar(entityAllocatedCount) = self->entityCount;
	if (copy->entityCount > 0) {
		copy->entities = malloc(copy->private_ivar(entityAllocatedCount) * sizeof(*copy->entities));
		for (unsigned int entityIndex = 0; entityIndex < copy->entityCount; entityIndex++) {
			copy->entities[entityIndex] = self->entities[entityIndex];
			if (self->entities[entityIndex].owned) {
				copy->entities[entityIndex].entity = call_virtual(copy, self->entities[entityIndex].entity, copy);
			}
		}
		postinitEntities(copy);
	}
	copy->visited = self->visited;
	return copy;
}

void RoomState_dispose(RoomState * self) {
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		if (self->entities[entityIndex].owned) {
			call_virtual(dispose, self->entities[entityIndex].entity);
		}
	}
	free(self->entities);
	call_super_virtual(dispose, self);
}

static void clearRemovedEntities(RoomState * self) {
	unsigned int entityOffset = 0;
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		self->entities[entityIndex] = self->entities[entityIndex + entityOffset];
		if (self->entities[entityIndex].entity->markedForRemoval) {
			EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_entity_removed, self->entities[entityIndex].entity);
			if (self->entities[entityIndex].owned) {
				call_virtual(dispose, self->entities[entityIndex].entity);
			}
			self->entityCount--;
			entityIndex--;
			entityOffset++;
		}
	}
}

void RoomState_entered(RoomState * self) {
	self->visited = true;
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		call_virtual(roomEntered, self->entities[entityIndex].entity);
	}
	clearRemovedEntities(self);
}

void RoomState_exited(RoomState * self) {
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		call_virtual(roomExited, self->entities[entityIndex].entity);
	}
	clearRemovedEntities(self);
}

void RoomState_advanceTurn(RoomState * self) {
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		call_virtual(preadvance, self->entities[entityIndex].entity);
	}
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		if (!self->entities[entityIndex].entity->markedForRemoval) {
			call_virtual(advanceTurn, self->entities[entityIndex].entity);
		}
	}
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		if (!self->entities[entityIndex].entity->markedForRemoval) {
			call_virtual(postadvance, self->entities[entityIndex].entity);
		}
	}
	clearRemovedEntities(self);
}

void RoomState_undoActionPerformed(RoomState * self) {
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		call_virtual(undoActionPerformed, self->entities[entityIndex].entity);
	}
}

void RoomState_addEntity(RoomState * self, compat_type(GameEntity *) entity, bool takeOwnership) {
	RoomState_insertEntity(self, self->entityCount, entity, takeOwnership);
}

void RoomState_insertEntity(RoomState * self, unsigned int insertIndex, compat_type(GameEntity *) entity, bool takeOwnership) {
	assert(insertIndex <= self->entityCount);
	if (self->entityCount >= self->private_ivar(entityAllocatedCount)) {
		self->private_ivar(entityAllocatedCount) = self->private_ivar(entityAllocatedCount) * 2 + (self->private_ivar(entityAllocatedCount) == 0);
		self->entities = realloc(self->entities, self->private_ivar(entityAllocatedCount) * sizeof(*self->entities));
	}
	for (unsigned int entityIndex = self->entityCount; entityIndex > insertIndex; entityIndex--) {
		self->entities[entityIndex] = self->entities[entityIndex - 1];
	}
	self->entities[insertIndex].entity = entity;
	self->entities[insertIndex].owned = takeOwnership;
	self->entityCount++;
	EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_entity_added, self->entities[insertIndex].entity);
}

void RoomState_removeEntity(RoomState * self, compat_type(GameEntity *) entity) {
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		if (self->entities[entityIndex].entity == entity) {
			RoomState_removeEntityAtIndex(self, entityIndex);
			break;
		}
	}
}

void RoomState_removeEntityAtIndex(RoomState * self, unsigned int entityIndex) {
	assert(entityIndex < self->entityCount);
	EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_entity_removed, self->entities[entityIndex].entity);
	if (self->entities[entityIndex].owned) {
		call_virtual(dispose, self->entities[entityIndex].entity);
	}
	self->entityCount--;
	for (; entityIndex < self->entityCount; entityIndex++) {
		self->entities[entityIndex] = self->entities[entityIndex + 1];
	}
}

TilePropertyBits RoomState_getTilePropertiesAtPosition(RoomState * self, Vector2i position) {
	TilePropertyBits tileProperties = 0;
	bool outOfBounds = true;
	for (unsigned int layerIndex = 0; layerIndex < self->roomData->layerCount; layerIndex++) {
		if (self->roomData->layers[layerIndex].type == ROOM_LAYER_TYPE_GROUND || self->roomData->layers[layerIndex].type == ROOM_LAYER_TYPE_OBSTACLE) {
			Vector2i positionInTileGrid = Vector2i_subtract(position, self->roomData->layers[layerIndex].offset);
			TileInstanceGrid * tileGrid = &self->roomData->layers[layerIndex].tileGrid;
			if (positionInTileGrid.x >= 0 && (unsigned int) positionInTileGrid.x < tileGrid->size.x &&
			    positionInTileGrid.y >= 0 && (unsigned int) positionInTileGrid.y < tileGrid->size.y) {
				outOfBounds = false;
				tileProperties |= getTileProperties(tileGrid->tileInstances[positionInTileGrid.y * tileGrid->size.x + positionInTileGrid.x].tileID);
			}
		}
	}
	if (outOfBounds) {
		tileProperties |= TILE_OUT_OF_BOUNDS;
	}
	return tileProperties;
}

unsigned int RoomState_getEntityCountAtPosition(RoomState * self, Vector2i position) {
	unsigned int entityCount = 0;
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		EntityComponent_position * positionComponent = call_virtual(getComponent, self->entities[entityIndex].entity, COMPONENT_POSITION);
		if (positionComponent != NULL && positionComponent->position.x == position.x && positionComponent->position.y == position.y) {
			entityCount++;
		}
	}
	return entityCount;
}

GameEntity * RoomState_getEntityAtPositionAtIndex(RoomState * self, Vector2i position, unsigned int index) {
	unsigned int entityCount = 0;
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		EntityComponent_position * positionComponent = call_virtual(getComponent, self->entities[entityIndex].entity, COMPONENT_POSITION);
		if (positionComponent != NULL && positionComponent->position.x == position.x && positionComponent->position.y == position.y) {
			if (entityCount == index) {
				return self->entities[entityIndex].entity;
			}
			entityCount++;
		}
	}
	return NULL;
}

RoomSavedState * RoomState_createSave(RoomState * self) {
	RoomSavedState * save = RoomSavedState_create();
	for (unsigned int entityIndex = 0; entityIndex < self->entityCount; entityIndex++) {
		if (self->entities[entityIndex].owned) {
			RoomSavedState_encodeEntity(save, self->entities[entityIndex].entity);
		}
	}
	return save;
}

void RoomState_loadSavedState(RoomState * self, RoomSavedState * savedState) {
	self->entityCount = RoomSavedState_getEntityCount(savedState);
	self->private_ivar(entityAllocatedCount) = self->entityCount;
	if (self->entityCount > 0) {
		self->entities = malloc(self->private_ivar(entityAllocatedCount) * sizeof(*self->entities));
		self->entityCount = 0;
		for (unsigned int entityIndex = 0; entityIndex < self->private_ivar(entityAllocatedCount); entityIndex++) {
			self->entities[entityIndex].entity = RoomSavedState_decodeEntityAtIndex(savedState, entityIndex, self);
			self->entities[entityIndex].owned = true;
			self->entityCount++;
		}
	}
	postinitEntities(self);
	self->visited = true;
}

RoomStateUndoData RoomState_createUndoData(RoomState * self) {
	RoomStateUndoData undoData = {self->entityCount, memdup(self->entities, self->entityCount * sizeof(*self->entities))};
	for (unsigned int entityIndex = 0; entityIndex < undoData.entityCount; entityIndex++) {
		undoData.entities[entityIndex].entity = call_virtual(copy, undoData.entities[entityIndex].entity, self);
	}
	return undoData;
}

void disposeRoomStateUndoData(RoomStateUndoData * undoData) {
	for (unsigned int entityIndex = 0; entityIndex < undoData->entityCount; entityIndex++) {
		if (undoData->entities[entityIndex].owned) {
			call_virtual(dispose, undoData->entities[entityIndex].entity);
		}
	}
	free(undoData->entities);
}
