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

#include "PROJECT_NAME/EntityComponent_collidable.h"
#include "PROJECT_NAME/EntityComponent_pushable.h"
#include "PROJECT_NAME/GameEntity.h"
#include "PROJECT_NAME/Utilities.h"
#include <assert.h>

#define stemobject_implementation EntityComponent_pushable

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

EntityComponent_pushable * EntityComponent_pushable_create(bool multipush) {
	stemobject_create_implementation(init, multipush)
}

bool EntityComponent_pushable_init(EntityComponent_pushable * self, bool multipush) {
	call_super(init, self);
	self->multipush = multipush;
	self->pushed = false;
	return true;
}

void EntityComponent_pushable_dispose(EntityComponent_pushable * self) {
	call_super_virtual(dispose, self);
}

void EntityComponent_pushable_initCopy(EntityComponent_pushable * self, compat_type(GameEntityComponent *) originalUntyped) {
	call_super_virtual(initCopy, self, originalUntyped);
	EntityComponent_pushable * original = originalUntyped;
	self->multipush = original->multipush;
	self->pushed = original->pushed;
}

void EntityComponent_pushable_update(EntityComponent_pushable * self, struct GameEntity * entity) {
	self->pushed = false;
}

bool EntityComponent_pushable_push(EntityComponent_pushable * self, struct GameEntity * entity, struct GameEntity * pushingEntity, Vector2i direction) {
	EntityComponent_position * positionComponent = call_virtual(getComponent, entity, COMPONENT_POSITION);
	assert(positionComponent != NULL);
	Vector2i newPosition = Vector2i_add(positionComponent->position, direction);
	if (!self->multipush) {
		if (isMoveBlockedAtPosition(entity->roomState, newPosition)) {
			return false;
		}
	} else {
		bool blocked = false;
		TilePropertyBits tileProperties = RoomState_getTilePropertiesAtPosition(entity->roomState, newPosition);
		if ((tileProperties & TILE_STATIC_WALL) || !(tileProperties & TILE_STATIC_FLOOR)) {
			blocked = true;
		}
		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 * blockingEntity = entities[entityIndex];
			if (!blockingEntity->markedForRemoval) {
				EntityComponent_collidable * collidableComponent = call_virtual(getComponent, blockingEntity, COMPONENT_COLLIDABLE);
				if (collidableComponent != NULL && call_virtual(collision, collidableComponent, entity, direction)) {
					EntityComponent_pushable * pushableComponent = call_virtual(getComponent, blockingEntity, COMPONENT_PUSHABLE);
					if (pushableComponent == NULL || !call_virtual(push, pushableComponent, blockingEntity, entity, direction)) {
						blocked = true;
					}
				}
			}
		}
		if (blocked) {
			positionComponent->lastBumpDirection = direction;
			return false;
		}
	}
	call_virtual(moveTo, positionComponent, newPosition);
	self->pushed = true;
	return true;
}
