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

#include "PROJECT_NAME/Atoms.h"
#include "PROJECT_NAME/EntityComponent_pickup.h"
#include "PROJECT_NAME/EntityPlayer.h"
#include "PROJECT_NAME/GameState.h"
#include "PROJECT_NAME/RoomState.h"
#include "PROJECT_NAME/SharedDefinitions.h"
#include "PROJECT_NAME/Sprites.h"
#include "PROJECT_NAME/Utilities.h"

#define stemobject_implementation EntityPlayer

stemobject_vtable_begin();
stemobject_vtable_entry(createWithEncodedState);
stemobject_vtable_entry(initWithEncodedState);
stemobject_vtable_entry(copy);
stemobject_vtable_entry(initCopy);
stemobject_vtable_entry(dispose);
stemobject_vtable_entry(getComponent);
stemobject_vtable_entry(preadvance);
stemobject_vtable_entry(advanceTurn);
stemobject_vtable_entry(isEqual);
stemobject_vtable_entry(enumerateProperties);
stemobject_vtable_entry(undoActionPerformed);
stemobject_vtable_end();

EntityPlayer * EntityPlayer_create(Vector2i position, struct RoomState * roomState) {
	stemobject_create_implementation(init, position, roomState)
}

static bool collisionCallback(struct GameEntity * collidingEntity, Vector2i fromDirection, void * context) {
	EntityPlayer * self = context;
	if (self->dead) {
		return false;
	}
	EntityComponent_attack * attackComponent = call_virtual(getComponent, collidingEntity, COMPONENT_ATTACK);
	if (attackComponent != NULL && (attackComponent->type & ATTACK_HURT_PLAYER)) {
		self->dead = true;
		self->spriteComponent.spriteID = SPRITE_NONE;
		EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_player_killed, self);
		if (attackComponent->type & ATTACK_OVERRIDE_COLLISION) {
			return false;
		}
	}
	return true;
}

static void updateSprite(EntityPlayer * self) {
	if (self->dead) {
		self->spriteComponent.spriteID = SPRITE_NONE;
	} else {
		switch (self->facingComponent.facing) {
			case FACING_NORTH:
				self->spriteComponent.spriteID = SPRITE_ID_PLAYER_NORTH;
				break;
			case FACING_EAST:
				self->spriteComponent.spriteID = SPRITE_ID_PLAYER_EAST;
				break;
			case FACING_SOUTH:
				self->spriteComponent.spriteID = SPRITE_ID_PLAYER_SOUTH;
				break;
			case FACING_WEST:
				self->spriteComponent.spriteID = SPRITE_ID_PLAYER_WEST;
				break;
		}
	}
}

static void initComponents(EntityPlayer * self, Vector2i position) {
	stemobject_assign_vtable(self->attackComponent, EntityComponent_attack);
	stemobject_assign_vtable(self->collidableComponent, EntityComponent_collidable);
	stemobject_assign_vtable(self->facingComponent, EntityComponent_facing);
	stemobject_assign_vtable(self->movementComponent, EntityComponent_movement);
	stemobject_assign_vtable(self->positionComponent, EntityComponent_position);
	stemobject_assign_vtable(self->spriteComponent, EntityComponent_sprite);
	stemobject_assign_vtable(self->steeringComponent, EntityComponent_playerSteering);
	stemobject_assign_vtable(self->pusherComponent, EntityComponent_pusher);
	EntityComponent_attack_init(&self->attackComponent, ATTACK_DESTROY_WALL | ATTACK_HURT_ENEMY);
	EntityComponent_collidable_init(&self->collidableComponent, collisionCallback, self);
	EntityComponent_facing_init(&self->facingComponent, FACING_SOUTH);
	EntityComponent_movement_init(&self->movementComponent, true);
	EntityComponent_position_init(&self->positionComponent, position);
	EntityComponent_sprite_init(&self->spriteComponent, SPRITE_ID_PLAYER_SOUTH, DRAW_LAYER_ENTITY, 1);
	EntityComponent_playerSteering_init(&self->steeringComponent);
	EntityComponent_pusher_init(&self->pusherComponent);
}

bool EntityPlayer_init(EntityPlayer * self, Vector2i position, struct RoomState * roomState) {
	call_super(init, self, roomState);
	self->dead = false;
	initComponents(self, position);
	updateSprite(self);
	return true;
}

EntityPlayer * EntityPlayer_createWithEncodedState(struct memreadContext * memreadContext, struct RoomState * roomState) {
	GameEntity_createWithEncodedState_implementation();
}

void EntityPlayer_initWithEncodedState(EntityPlayer * self, struct memreadContext * memreadContext, struct RoomState * roomState) {
	initComponents(self, VECTOR2i_ZERO);
	call_super_virtual(initWithEncodedState, self, memreadContext, roomState);
	self->positionComponent.lastPosition = self->positionComponent.position;
	updateSprite(self);
}

EntityPlayer * EntityPlayer_copy(EntityPlayer * self, struct RoomState * roomState) {
	stemobject_copy_implementation(initCopy, roomState)
}

void EntityPlayer_initCopy(EntityPlayer * self, compat_type(EntityPlayer *) originalUntyped, struct RoomState * roomState) {
	call_super_virtual(initCopy, self, originalUntyped, roomState);
	EntityPlayer * original = originalUntyped;
	self->dead = original->dead;
	initComponents(self, original->positionComponent.position);
	call_virtual(initCopy, &self->attackComponent, &original->attackComponent);
	call_virtual(initCopy, &self->collidableComponent, &original->collidableComponent);
	self->collidableComponent.callbackContext = self;
	call_virtual(initCopy, &self->facingComponent, &original->facingComponent);
	call_virtual(initCopy, &self->movementComponent, &original->movementComponent);
	call_virtual(initCopy, &self->positionComponent, &original->positionComponent);
	call_virtual(initCopy, &self->spriteComponent, &original->spriteComponent);
	call_virtual(initCopy, &self->steeringComponent, &original->steeringComponent);
	call_virtual(initCopy, &self->pusherComponent, &original->pusherComponent);
	updateSprite(self);
}

void EntityPlayer_dispose(EntityPlayer * self) {
	call_super_virtual(dispose, self);
}

compat_type(GameEntityComponent *) EntityPlayer_getComponent(EntityPlayer * self, ComponentType type) {
	if (self->dead && type != COMPONENT_POSITION) {
		return NULL;
	}
	switch (type) {
		case COMPONENT_ATTACK:
			return &self->attackComponent;
		case COMPONENT_COLLIDABLE:
			return &self->collidableComponent;
		case COMPONENT_FACING:
			return &self->facingComponent;
		case COMPONENT_MOVEMENT:
			return &self->movementComponent;
		case COMPONENT_POSITION:
			return &self->positionComponent;
		case COMPONENT_PUSHER:
			return &self->pusherComponent;
		case COMPONENT_SPRITE:
			return &self->spriteComponent;
		case COMPONENT_STEERING:
			return &self->steeringComponent;
		default:
			break;
	}
	return NULL;
}

void EntityPlayer_preadvance(EntityPlayer * self) {
	call_virtual(update, &self->positionComponent, (GameEntity *) self);
}

void EntityPlayer_advanceTurn(EntityPlayer * self) {
	if (self->dead) {
		return;
	}
	call_virtual(update, &self->movementComponent, (GameEntity *) self);
	switch (self->movementComponent.lastMoveResult) {
		case MOVE_RESULT_IDLE:
			break;
		case MOVE_RESULT_MOVED:
			EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_player_moved, self);
			break;
		case MOVE_RESULT_REFUSED:
			EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_player_move_failed, self);
			break;
		case MOVE_RESULT_BLOCKED:
			EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_player_bumped_wall, self);
			break;
	}
	
	unsigned int entityCount = RoomState_getEntityCountAtPosition(self->roomState, self->positionComponent.position);
	for (unsigned int entityIndex = 0; entityIndex < entityCount; entityIndex++) {
		GameEntity * entity = RoomState_getEntityAtPositionAtIndex(self->roomState, self->positionComponent.position, entityIndex);
		if (!entity->markedForRemoval) {
			EntityComponent_pickup * pickupComponent = call_virtual(getComponent, entity, COMPONENT_PICKUP);
			if (pickupComponent != NULL) {
				EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_pickup_collected, entity);
				entity->markedForRemoval = true;
			}
		}
	}
}

bool EntityPlayer_isEqual(EntityPlayer * self, EntityPlayer * compare) {
	return self->facingComponent.facing == compare->facingComponent.facing &&
	       self->positionComponent.position.x == compare->positionComponent.position.x &&
	       self->positionComponent.position.y == compare->positionComponent.position.y &&
	       self->dead == compare->dead;
}

void EntityPlayer_enumerateProperties(EntityPlayer * self, bool transient, GameEntity_enumeratePropertiesCallback callback, void * context) {
	call_super_virtual(enumerateProperties, self, transient, callback, context);
	callback(&self->positionComponent.position, PROPERTY_TYPE_Vector2i_int8_t, context);
	callback(&self->facingComponent.facing, PROPERTY_TYPE_uint8_t, context);
	callback(&self->dead, PROPERTY_TYPE_bool, context);
}

void EntityPlayer_undoActionPerformed(EntityPlayer * self) {
	updateSprite(self);
}

void EntityPlayer_setFacing(EntityPlayer * self, FacingDirection facing) {
	self->facingComponent.facing = facing;
	updateSprite(self);
}

static FacingDirection moveInputToFacingDirection(MoveInput_axis axis, MoveInput_direction direction) {
	if (axis == MOVE_AXIS_X) {
		return direction == MOVE_DIRECTION_POSITIVE ? FACING_EAST : FACING_WEST;
	}
	return direction == MOVE_DIRECTION_POSITIVE ? FACING_SOUTH : FACING_NORTH;
}

bool EntityPlayer_startMoveInput(EntityPlayer * self, MoveInput_axis axis, MoveInput_direction direction, bool repeat) {
	bool changed = false;
	if (!repeat) {
		changed = call_virtual(startMoveInput, &self->steeringComponent, axis, direction);
	}
	if (changed || call_virtual(isTurnAdvancingInput, &self->steeringComponent, axis, direction)) {
		EntityPlayer_setFacing(self, moveInputToFacingDirection(axis, direction));
		GameState_advanceTurn(self->roomState->gameState);
		return true;
	}
	return changed;
}

bool EntityPlayer_stopMoveInput(EntityPlayer * self, MoveInput_axis axis, MoveInput_direction direction) {
	return call_virtual(stopMoveInput, &self->steeringComponent, axis, direction);
}

bool EntityPlayer_startActionInput(EntityPlayer * self, ActionInput_action action, bool repeat) {
	bool changed = false;
	if (!repeat) {
		changed = call_virtual(startActionInput, &self->steeringComponent, action);
	}
	switch (action) {
		case ACTION_WAIT:
			call_virtual(cancelAllMoveInputs, &self->steeringComponent);
			GameState_advanceTurn(self->roomState->gameState);
			return true;
	}
	return changed;
}

bool EntityPlayer_stopActionInput(EntityPlayer * self, ActionInput_action action) {
	return call_virtual(stopActionInput, &self->steeringComponent, action);
}

void EntityPlayer_offsetLocalPosition(EntityPlayer * self, Vector2i offset) {
	self->positionComponent.position.x += offset.x;
	self->positionComponent.position.y += offset.y;
	self->positionComponent.lastPosition.x += offset.x;
	self->positionComponent.lastPosition.y += offset.y;
}
