#include "Player.h"
#include "debug.h"
#include "game_world.h"
#include "input_axis.h"
#include "physics_world.h"

#include "PlayerStates.h"
#include "Layers.h"

const Vector PLAYER_SPEED = { 1.0f, 0.50f };

START_REFLECT(Player)
    REFLECT_TYPECLASS(Player, Drop)
    REFLECT_TYPECLASS(Player, PhysicsEntity)
    REFLECT_TYPECLASS(Player, BehaviourEntity)
    REFLECT_TYPECLASS(Player, Transformable)
END_REFLECT(Player)

impl_Drop_for(Player,
    DestroyPlayer
)

impl_BehaviourEntity_for(Player,
    PlayerStart,
    PlayerUpdate,
    PlayerDraw,
    PlayerGetDepth
)

impl_Transformable_for(Player,
    PlayerGetTransform
)

impl_PhysicsEntity_for(Player,
    PlayerGetRigidBody,
    PlayerOnCollision,
    PlayerOnOverlap
)

static inline
void Internal_PlayerInitInput(Player* self) {
    playerinput_add(self->playerInput,
                    CompositeAxis1D_as_InputAxis(compositeaxis1d_from_keys(SDL_SCANCODE_A, SDL_SCANCODE_D)),
                    (InputDelegateFn)PlayerHorizontalInput);
    playerinput_add(self->playerInput,
                    CompositeAxis1D_as_InputAxis(compositeaxis1d_from_keys(SDL_SCANCODE_S, SDL_SCANCODE_W)),
                    (InputDelegateFn)PlayerVerticalInput);
    playerinput_add(self->playerInput,
                    KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_SPACE)),
                    (InputDelegateFn)PlayerJumpInput);
    playerinput_add(self->playerInput,
                    KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_J)),
                    (InputDelegateFn)PlayerLightAttackInput);
    playerinput_add(self->playerInput,
                    KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_K)),
                    (InputDelegateFn)PlayerHeavyAttackInput);
}

Player* MakePlayer() {
    Player* self = malloc(sizeof(Player));
    ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for new Player instance");

    *self = (Player) {
        .transform = IdentityTransform,
        .height = 0.f,

        .rigidbody = NULL,
        .physicsCollider = NULL,
        .hitbox = NULL,

        .verticalVelocity = 0.f,

        .playerInput = playerinput_new(self, -1),
        .moveInput = ZeroVector,
        .attackInput = 0,
        .animationTriggers = 0,

        .facing = 1,

        .sprite = sprite_new_empty(),

        .idle = NULL,
        .walk = NULL,
        .jump = NULL,
        .jab_a = NULL,
        .jab_b = NULL,
        .kick_a = NULL,
        .air_heavy = NULL,

        .animationStateMachine = NULL,
    };

    self->rigidbody = rigidbody_make(Player_as_PhysicsEntity(self));
    self->physicsCollider = collider_new(Player_as_PhysicsEntity(self), shape_new((Vector[]){
        MakeVector(-0.2f, -0.065f),
        MakeVector( 0.2f, -0.065f),
        MakeVector( 0.2f,  0.065f),
        MakeVector(-0.2f,  0.065f)
    }, 4), 0, PHYSICS_LAYER_CHARACTERS, PHYSICS_LAYER_DEFAULT);
    self->hitbox = collider_new(Player_as_PhysicsEntity(self), shape_new((Vector[]){
        MakeVector(-0.1f, -0.9f),
        MakeVector( 0.1f, -0.9f),
        MakeVector( 0.1f,  0.00f),
        MakeVector(-0.1f,  0.00f)
    }, 4), 1, PHYSICS_LAYER_COMBAT, 0x0);

    sprite_set_origin(self->sprite, MakeVector(0.45f, 0.925f));

    self->idle = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Idle.png", IVectorFrom(512)), 1.5f, LoopMode_Loop);
    self->walk = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Walk.png", IVectorFrom(512)), 5.f, LoopMode_Loop);
    self->jump = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jumping.png", IVectorFrom(512)), 1.f, LoopMode_Stop);
    self->jab_a = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jab_A.png", IVectorFrom(512)), 10.f, LoopMode_Stop);
    self->jab_b = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jab_B.png", IVectorFrom(512)), 10.f, LoopMode_Stop);
    self->kick_a = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Kick_A.png", IVectorFrom(512)), 12.f, LoopMode_Stop);
    self->air_heavy = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Air_Heavy.png", IVectorFrom(512)), 10.f, LoopMode_Stop);

    self->animationStateMachine = state_machine_init(self, PlayerIdle());

    return self;
}

Player* SpawnPlayer(Vector location) {
    Player* self = MakePlayer();
    self->transform.position = location;

    game_world_add_entity(Player_as_BehaviourEntity(self));
    physics_world_add_entity(Player_as_PhysicsEntity(self));

    Internal_PlayerInitInput(self);
    return self;
}

void DestroyPlayer(Player* self) {
    physics_world_remove_entity(Player_as_PhysicsEntity(self));
    collider_destroy(self->physicsCollider);
    rigidbody_destroy(self->rigidbody);

    playerinput_drop(self->playerInput);

    animation_sprite_destroy(self->idle);
    animation_sprite_destroy(self->walk);
    animation_sprite_destroy(self->jab_a);
    animation_sprite_destroy(self->jab_b);
    sprite_destroy(self->sprite);

    state_machine_destroy(self->animationStateMachine);
}

void PlayerStart(Player* self) {
    animation_sprite_play_from(self->currentAnimation, 0.0f);
}

void PlayerUpdate(Player* self, float deltaTime) {
    state_machine_update(self->animationStateMachine, deltaTime);
    self->height += self->verticalVelocity * deltaTime;
    if(self->height <= 0.f)
         self->height = 0.f;
}

void PlayerDraw(Player* self) {
    Transform trans = self->transform;
    trans.position.y -= self->height;
    animation_sprite_draw(self->currentAnimation, &trans);
}

void PlayerHorizontalInput(Player* self, InputEvent value) {
    self->moveInput.x = value.as_float;
}

void PlayerVerticalInput(Player* self, InputEvent value) {
    self->moveInput.y = -value.as_float;
}

void PlayerJumpInput(Player* self, InputEvent value) {
    self->jumpInput = value.as_bool;
}

void PlayerLightAttackInput(Player* self, InputEvent value) {
    if(value.as_bool)
        self->attackInput = 1;
}

void PlayerHeavyAttackInput(Player* self, InputEvent value) {
    if(value.as_bool)
        self->attackInput = 2;
}

void PlayerOnCollision(Player* self, Collision collision) {}
void PlayerOnOverlap(Player* self, Collider* other) {}

Transform* PlayerGetTransform(Player* self) {
    return &self->transform;
}

RigidBody* PlayerGetRigidBody(Player* self) {
    return self->rigidbody;
}
