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

#include "PROJECT_NAME/EntityComponent_movement.h"
#include "PROJECT_NAME/EntityComponent_pushable.h"
#include "PROJECT_NAME/GameEntity.h"
#include "PROJECT_NAME/RoomState.h"
#include <assert.h>

#define stemobject_implementation EntityComponent_movement

stemobject_vtable_begin();
stemobject_vtable_entry(dispose);
stemobject_vtable_entry(initCopy);
stemobject_vtable_entry(update);
stemobject_vtable_end();

EntityComponent_movement * EntityComponent_movement_create(bool canExitRoom) {
	stemobject_create_implementation(init, canExitRoom)
}

bool EntityComponent_movement_init(EntityComponent_movement * self, bool canExitRoom) {
	call_super(init, self);
	self->lastMoveResult = MOVE_RESULT_IDLE;
	self->canExitRoom = canExitRoom;
	return true;
}

void EntityComponent_movement_dispose(EntityComponent_movement * self) {
	call_super_virtual(dispose, self);
}

void EntityComponent_movement_initCopy(EntityComponent_movement * self, compat_type(GameEntityComponent *) originalUntyped) {
	call_super_virtual(initCopy, self, originalUntyped);
	EntityComponent_movement * original = originalUntyped;
	self->lastMoveResult = original->lastMoveResult;
	self->canExitRoom = original->canExitRoom;
}

void EntityComponent_movement_update(EntityComponent_movement * self, struct GameEntity * entity) {
	EntityComponent_position * positionComponent = call_virtual(getComponent, entity, COMPONENT_POSITION);
	EntityComponent_steering * steeringComponent = call_virtual(getComponent, entity, COMPONENT_STEERING);
	assert(positionComponent != NULL);
	assert(steeringComponent != NULL);
	
	Vector2i move = call_virtual(getMoveVector, steeringComponent, entity);
	if (move.x == 0 && move.y == 0) {
		self->lastMoveResult = MOVE_RESULT_IDLE;
	} else {
		Vector2i newPosition = Vector2i_add(positionComponent->position, move);
		EntityComponent_pusher * pusherComponent = call_virtual(getComponent, entity, COMPONENT_PUSHER);
		TilePropertyBits tileProperties = RoomState_getTilePropertiesAtPosition(entity->roomState, newPosition);
		bool traversible = !!(tileProperties & TILE_STATIC_FLOOR);
		if (self->canExitRoom && (tileProperties & TILE_OUT_OF_BOUNDS)) {
			traversible = true;
		}
		bool blocked = !!(tileProperties & TILE_STATIC_WALL);
		unsigned int entityCount = RoomState_getEntityCountAtPosition(entity->roomState, newPosition);
		// Pushing may affect entities at target position; need to list all before mutating
		GameEntity * entities[entityCount];
		for (unsigned int entityIndex = 0; entityIndex < entityCount; entityIndex++) {
			entities[entityIndex] = RoomState_getEntityAtPositionAtIndex(entity->roomState, newPosition, entityIndex);
		}
		for (unsigned int entityIndex = 0; entityIndex < entityCount; entityIndex++) {
			GameEntity * entityAtTargetPosition = entities[entityIndex];
			if (!entityAtTargetPosition->markedForRemoval) {
				EntityComponent_collidable * collidableComponent = call_virtual(getComponent, entityAtTargetPosition, COMPONENT_COLLIDABLE);
				if (collidableComponent != NULL && call_virtual(collision, collidableComponent, entity, move)) {
					if (pusherComponent != NULL) {
						EntityComponent_pushable * pushableComponent = call_virtual(getComponent, entityAtTargetPosition, COMPONENT_PUSHABLE);
						if (pushableComponent == NULL || !call_virtual(push, pushableComponent, entityAtTargetPosition, entity, move)) {
							blocked = true;
						}
					} else {
						blocked = true;
					}
				}
			}
		}
		if (blocked) {
			positionComponent->lastBumpDirection = move;
			self->lastMoveResult = MOVE_RESULT_BLOCKED;
			
		} else if (!traversible) {
			self->lastMoveResult = MOVE_RESULT_REFUSED;
			
		} else {
			call_virtual(moveTo, positionComponent, newPosition);
			self->lastMoveResult = MOVE_RESULT_MOVED;
		}
	}
}
