#include "PlayerStates.h"
#include "Hurtbox.h"

#include "Damagable.h"
#include "Player.h"

static inline
void InternalSpriteFlipWithMovement(Player* self) {
    if(self->moveInput.x > 0.f)
        self->facing = 1;
    if(self->moveInput.x < -0.1f)
        self->facing = -1;
    sprite_flip_horizontal(self->sprite, self->facing != 1);
}

void PlayerAnimationExit(Player* self) {
    self->animationTriggers = 0;
    self->attackInput = 0;
}

void PlayerIdleEnter(Player* self) {
    self->currentAnimation = self->idle;
    rigidbody_set_velocity(self->rigidbody, ZeroVector);
    animation_sprite_play_from(self->currentAnimation, 0.f);
}

const State* PlayerIdleUpdate(Player* self, float deltaTime) {
    if(!veqf(self->moveInput, ZeroVector))
        return PlayerWalk();
    if(self->attackInput == 1)
        return PlayerJabA();
    if(self->attackInput == 2)
        return PlayerKickA();
    if(self->jumpInput)
        return PlayerJump();
    return PlayerIdle();
}

void PlayerWalk_Enter(Player* self) {
    self->currentAnimation = self->walk;
    animation_sprite_play_from(self->currentAnimation, 0.f);
}

const State* PlayerWalk_Update(Player* self, float deltaTime) {
    rigidbody_set_velocity(self->rigidbody, vmulf(vnormalizedf(self->moveInput), PLAYER_SPEED));

    if(veqf(self->moveInput, ZeroVector))
        return PlayerIdle();
    if(self->attackInput == 1)
        return PlayerJabA();
    if(self->attackInput == 2)
        return PlayerKickA();
    if(self->jumpInput)
        return PlayerJump();
    InternalSpriteFlipWithMovement(self);
    return PlayerWalk();
}

void PlayerAttackEnter(Player* self) {
    self->attackInput = 0;
    rigidbody_set_velocity(self->rigidbody, ZeroVector);
    InternalSpriteFlipWithMovement(self);
}

static
void PlayerHurtbox(Player* self, DamageEventData damage, Vector hitbox_size, Vector offset) {
    Hurtbox box = {
        .owner = Player_as_BehaviourEntity(self),
        .damage = damage,
        .size = hitbox_size,
        .offset = vaddf(offset, MakeVector(0.f, self->height)),
        .depth_extent = 0.15f,
    };
    HurtboxCast(&box, self->facing);
}

void PlayerJabA_Enter(Player* self) {
    PlayerAttackEnter(self);
    self->currentAnimation = self->jab_a;
    animation_sprite_play_from(self->currentAnimation, 0.f);
}

const State* PlayerJabA_Update(Player* self, float deltaTime) {
    const static DamageEventData damage = {
        .damageAmount = 1,
        .knockdown = 0,
        .stun = 0.15f,
        .knockback = 0.07f,
    };
    const float ntime = animation_sprite_get_time_normalized(self->currentAnimation);
    if(self->animationTriggers == 0 && ntime > 0.33f) {
        PlayerHurtbox(self, damage, MakeVector(0.1f, 0.1f), MakeVector(0.265f, -0.6f));
        ++self->animationTriggers;
    }
    if(ntime > 1.0f) {
        if(self->attackInput == 1)
            return PlayerJabB();
        else if(self->attackInput == 2)
            return PlayerKickA();
    }
    if(!veqf(self->moveInput, ZeroVector) && ntime > 1.05f)
        return PlayerWalk();
    if(ntime >= 2.0f)
        return PlayerIdle();
    return PlayerJabA();
}

void PlayerJabB_Enter(Player* self) {
    PlayerAttackEnter(self);
    self->currentAnimation = self->jab_b;
    animation_sprite_play_from(self->currentAnimation, 0.f);
}

const State* PlayerJabB_Update(Player* self, float deltaTime) {
    static const DamageEventData damage = {
        .damageAmount = 1,
        .knockdown = 0,
        .stun = 0.15f,
        .knockback = 0.05f,
    };
    const float ntime = animation_sprite_get_time_normalized(self->currentAnimation);
    const size_t frame = sprite_get_tile(self->sprite);
    if(self->animationTriggers == 0 && ntime > 0.33f) {
        PlayerHurtbox(self, damage, MakeVector(0.13f, 0.05f), MakeVector(0.32f, -0.7f));
        ++self->animationTriggers;
    }
    if(ntime > 1.0f && self->attackInput == 1)
        return PlayerKickA();
    if(!veqf(self->moveInput, ZeroVector) && ntime > 1.05f)
        return PlayerWalk();
    if(ntime >= 2.0f)
        return PlayerIdle();
    return PlayerJabB();
}

void PlayerKickA_Enter(Player* self) {
    PlayerAttackEnter(self);
    self->currentAnimation = self->kick_a;
    animation_sprite_play_from(self->currentAnimation, 0.f);
}

const State* PlayerKickA_Update(Player* self, float deltaTime) {
    static const DamageEventData damage = {
        .damageAmount = 3,
        .knockdown = 1,
        .stun = 0.5f,
        .knockback = 0.15f,
    };
    const float ntime = animation_sprite_get_time_normalized(self->currentAnimation);
    const size_t frame = sprite_get_tile(self->sprite);
    if(frame >= 2 && frame <= 3) {
        PlayerHurtbox(self, damage, MakeVector(0.16f, 0.06f), MakeVector(0.33f, -0.4f));
    }
    if(!veqf(self->moveInput, ZeroVector) && ntime > 1.05f)
        return PlayerWalk();
    if(ntime >= 1.f)
        return PlayerIdle();
    return PlayerKickA();
}

void PlayerJump_Enter(Player* self) {
    if(self->jumpInput) {
        self->currentAnimation = self->jump;
        self->verticalVelocity = 3.f;
    }
    animation_sprite_play_from(self->currentAnimation, 0.f);
}

const State* PlayerJump_Update(Player* self, float deltaTime) {
    self->verticalVelocity -= 5.f * deltaTime;
    if(self->height == 0.f) {
        self->verticalVelocity = 0.f;
        return PlayerIdle();
    }
    if(self->attackInput == 2)
        return PlayerAirHeavy();
    return PlayerJump();
}

void PlayerAirHeavy_Enter(Player* self) {
    self->currentAnimation = self->air_heavy;
    animation_sprite_play_from(self->currentAnimation, 0.f);
}

const State* PlayerAirHeavy_Update(Player* self, float deltaTime) {
    const static DamageEventData damage = {
        .damageAmount = 4,
        .knockdown = 1,
        .stun = 0.75f,
        .knockback = 0.15f
    };
    const float ntime = animation_sprite_get_time_normalized(self->currentAnimation);
    size_t frame = sprite_get_tile(self->sprite);
    if(frame == 1) PlayerHurtbox(self, damage, MakeVector(0.12f, 0.12f), MakeVector(0.12f, -0.48f));
    const State* result = PlayerJump_Update(self, deltaTime);
    return (result == PlayerJump()) ? PlayerAirHeavy() : result;
}
