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

#include "PROJECT_NAME/Atoms.h"
#include "PROJECT_NAME/GameState.h"
#include <assert.h>
#include <stdio.h>

#define stemobject_implementation GameState

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

GameState * GameState_create(GameData * gameData, GameSavedState * savedState) {
	stemobject_create_implementation(init, gameData, savedState)
}

static bool passthroughCallback(Atom eventID, void * eventData, void * context) {
	GameState * self = context;
	return EventDispatcher_dispatchEvent(self->eventDispatcher, eventID, eventData);
}

static void registerForRoomStateEvents(GameState * self, RoomState * room) {
	EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_player_bumped_wall, passthroughCallback, self);
	EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_player_moved, passthroughCallback, self);
	EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_player_move_failed, passthroughCallback, self);
	EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_tile_destroyed, passthroughCallback, self);
	EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_pickup_collected, passthroughCallback, self);
	EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_block_pushed, passthroughCallback, self);
	EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_enemy_killed, passthroughCallback, self);
	EventDispatcher_registerForEvent(room->eventDispatcher, ATOM_event_player_killed, passthroughCallback, self);
}

static EntityPlayer * createDefaultPlayer(GameState * self) {
	Vector2i startTilePosition = {0, 0};
	FacingDirection facing = FACING_SOUTH;
	DataHashTable * roomEntrance = RoomData_getRoomEntranceByName(&self->gameData->rooms[self->currentRoomIndex], self->gameData->startingRoomEntranceName, &startTilePosition);
	if (roomEntrance == NULL) {
#ifdef DEBUG
		fprintf(stderr, "Error: Couldn't find room entrance ID \"%s\"\n", self->gameData->startingRoomEntranceName);
#endif
	} else {
		facing = valueGetInt32(hashGet(roomEntrance, "facing"));
	}
	EntityPlayer * player = EntityPlayer_create(startTilePosition, self->rooms[self->currentRoomIndex]);
	EntityPlayer_setFacing(player, facing);
	call_virtual(postinit, player);
	return player;
}

static EntityPlayer * createPlayerFromSaveData(void * playerData, unsigned int playerDataSize, struct RoomState * roomState) {
	struct memreadContext context = memreadContextInit(playerData, playerDataSize);
	return EntityPlayer_createWithEncodedState(&context, roomState);
}

bool GameState_init(GameState * self, GameData * gameData, GameSavedState * savedState) {
	call_super(init, self);
	self->eventDispatcher = EventDispatcher_create();
	self->gameData = gameData;
	self->roomCount = self->gameData->roomCount;
	self->rooms = malloc(self->roomCount * sizeof(*self->rooms));
	for (unsigned int roomIndex = 0; roomIndex < self->roomCount; roomIndex++) {
		RoomSavedState * roomSavedState = NULL;
		if (savedState != NULL) {
			roomSavedState = savedState->roomSaves[roomIndex];
		}
		self->rooms[roomIndex] = RoomState_create(roomIndex, &self->gameData->rooms[roomIndex], self, roomSavedState == NULL);
		if (roomSavedState != NULL) {
			RoomState_loadSavedState(self->rooms[roomIndex], roomSavedState);
		}
		registerForRoomStateEvents(self, self->rooms[roomIndex]);
	}
	if (savedState != NULL) {
		self->currentRoomIndex = savedState->currentRoomIndex;
		self->player = createPlayerFromSaveData(savedState->playerData, savedState->playerDataSize, self->rooms[self->currentRoomIndex]);
		
	} else {
		if (GameData_getRoomDataByIdentifier(self->gameData, self->gameData->startingRoomIdentifier, &self->currentRoomIndex) == NULL) {
#ifdef DEBUG
			fprintf(stderr, "Error: Couldn't find starting room ID %u\n", self->gameData->startingRoomIdentifier);
#endif
			self->currentRoomIndex = 0;
		}
		self->player = createDefaultPlayer(self);
	}
	RoomState_insertEntity(self->rooms[self->currentRoomIndex], 0, self->player, false);
	call_virtual(postinit, self->player);
	if (savedState == NULL) {
		RoomState_entered(self->rooms[self->currentRoomIndex]);
		self->currentRoomStateCopy = RoomState_copy(self->rooms[self->currentRoomIndex]);
		RoomState_removeEntity(self->currentRoomStateCopy, self->player);
		self->roomResetPlayer = call_virtual(copy, self->player, self->rooms[self->currentRoomIndex]);
		
	} else {
		self->currentRoomStateCopy = RoomState_create(self->currentRoomIndex, &self->gameData->rooms[self->currentRoomIndex], self, false);
		RoomState_loadSavedState(self->currentRoomStateCopy, savedState->roomResetSave);
		self->roomResetPlayer = createPlayerFromSaveData(savedState->roomResetPlayerData, savedState->roomResetPlayerDataSize, self->rooms[self->currentRoomIndex]);
	}
	return true;
}

void GameState_dispose(GameState * self) {
	for (unsigned int roomIndex = 0; roomIndex < self->roomCount; roomIndex++) {
		call_virtual(dispose, self->rooms[roomIndex]);
	}
	free(self->rooms);
	RoomState_dispose(self->currentRoomStateCopy);
	call_virtual(dispose, self->player);
	call_virtual(dispose, self->roomResetPlayer);
	EventDispatcher_dispose(self->eventDispatcher);
	call_super_virtual(dispose, self);
}

void GameState_advanceTurn(GameState * self) {
	EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_turn_started, NULL);
	RoomState_advanceTurn(self->rooms[self->currentRoomIndex]);
	EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_turn_ended, NULL);
	Rect4i roomBounds = RoomData_getLocalBounds(self->rooms[self->currentRoomIndex]->roomData);
	Vector2i playerPosition = self->player->positionComponent.position;
	if (playerPosition.x < roomBounds.xMin || playerPosition.x >= roomBounds.xMax ||
	    playerPosition.y < roomBounds.yMin || playerPosition.y >= roomBounds.yMax) {
		unsigned int newRoomIndex = GameData_getRoomIndexAtZonePosition(self->gameData, self->rooms[self->currentRoomIndex]->roomData->zoneLayer, VECTOR2i(playerPosition.x + self->rooms[self->currentRoomIndex]->roomData->zonePosition.x, playerPosition.y + self->rooms[self->currentRoomIndex]->roomData->zonePosition.y));
		if (newRoomIndex != UINT_MAX) {
			GameState_changeRoom(self, newRoomIndex);
		} else {
			// TODO: Handle out of bounds without a corresponding room (force undo?)
			//GameState_respawnPlayerOutOfBounds(self);
		}
	}
}

void GameState_changeRoom(GameState * self, unsigned int newRoomIndex) {
	Vector2i tileOffset = Vector2i_subtract(self->rooms[self->currentRoomIndex]->roomData->zonePosition, self->rooms[newRoomIndex]->roomData->zonePosition);
	EntityPlayer_offsetLocalPosition(self->player, tileOffset);
	RoomState_removeEntity(self->rooms[self->currentRoomIndex], self->player);
	RoomState_insertEntity(self->rooms[newRoomIndex], 0, self->player, false);
	self->player->roomState = self->rooms[newRoomIndex];
	struct GameState_roomChangedEvent eventData = {self->currentRoomIndex, newRoomIndex};
	RoomState_exited(self->rooms[self->currentRoomIndex]);
	self->currentRoomIndex = newRoomIndex;
	RoomState_entered(self->rooms[self->currentRoomIndex]);
	RoomState_dispose(self->currentRoomStateCopy);
	self->currentRoomStateCopy = RoomState_copy(self->rooms[self->currentRoomIndex]);
	RoomState_removeEntity(self->currentRoomStateCopy, self->player);
	call_virtual(dispose, self->roomResetPlayer);
	self->roomResetPlayer = call_virtual(copy, self->player, self->rooms[self->currentRoomIndex]);
	EventDispatcher_dispatchEvent(self->eventDispatcher, ATOM_event_room_changed, &eventData);
}

void GameState_resetCurrentRoom(GameState * self) {
	RoomState_dispose(self->rooms[self->currentRoomIndex]);
	self->rooms[self->currentRoomIndex] = RoomState_copy(self->currentRoomStateCopy);
	call_virtual(dispose, self->player);
	self->player = call_virtual(copy, self->roomResetPlayer, self->rooms[self->currentRoomIndex]);
	RoomState_insertEntity(self->rooms[self->currentRoomIndex], 0, self->player, false);
	registerForRoomStateEvents(self, self->rooms[self->currentRoomIndex]);
}

GameSavedState * GameState_createSave(GameState * self) {
	GameSavedState * save = GameSavedState_create(self->roomCount, self->currentRoomIndex, self->player, self->roomResetPlayer);
	for (unsigned int roomIndex = 0; roomIndex < self->roomCount; roomIndex++) {
		if (self->rooms[roomIndex]->visited) {
			save->roomSaves[roomIndex] = RoomState_createSave(self->rooms[roomIndex]);
		}
	}
	save->roomResetSave = RoomState_createSave(self->currentRoomStateCopy);
	return save;
}
