#include "decomp/global_state.h"

#include "debug_print.h"
#include "load_surfaces.h"
#include "gfx_adapter.h"
#include "load_anim_data.h"
#include "load_tex_data.h"
#include "obj_pool.h"

#include "decomp/game/interaction.h"
#include "decomp/include/object_fields.h"
#include "decomp/include/sm64.h"
#include "decomp/shim.h"
#include "decomp/game/mario.h"
#include "decomp/engine/math_util.h"
#include "fake_interaction.h"

#define INT_GROUND_POUND_OR_TWIRL (1 << 0) // 0x01
#define INT_PUNCH                 (1 << 1) // 0x02
#define INT_KICK                  (1 << 2) // 0x04
#define INT_TRIP                  (1 << 3) // 0x08
#define INT_SLIDE_KICK            (1 << 4) // 0x10
#define INT_FAST_ATTACK_OR_SHELL  (1 << 5) // 0x20
#define INT_HIT_FROM_ABOVE        (1 << 6) // 0x40
#define INT_HIT_FROM_BELOW        (1 << 7) // 0x80

#define INT_ATTACK_NOT_FROM_BELOW                                                 \
    (INT_GROUND_POUND_OR_TWIRL | INT_PUNCH | INT_KICK | INT_TRIP | INT_SLIDE_KICK \
     | INT_FAST_ATTACK_OR_SHELL | INT_HIT_FROM_ABOVE)

#define INT_ANY_ATTACK                                                            \
    (INT_GROUND_POUND_OR_TWIRL | INT_PUNCH | INT_KICK | INT_TRIP | INT_SLIDE_KICK \
     | INT_FAST_ATTACK_OR_SHELL | INT_HIT_FROM_ABOVE | INT_HIT_FROM_BELOW)

#define INT_ATTACK_NOT_WEAK_FROM_ABOVE                                                \
    (INT_GROUND_POUND_OR_TWIRL | INT_PUNCH | INT_KICK | INT_TRIP | INT_HIT_FROM_BELOW)

#define sDelayInvincTimer (g_state->msDelayInvincTimer)
#define sInvulnerable     (g_state->msInvulnerable)

static u32 sForwardKnockbackActions[][3] = {
    {ACT_SOFT_FORWARD_GROUND_KB, ACT_FORWARD_GROUND_KB, ACT_HARD_FORWARD_GROUND_KB},
    {ACT_FORWARD_AIR_KB, ACT_FORWARD_AIR_KB, ACT_HARD_FORWARD_AIR_KB},
    {ACT_FORWARD_WATER_KB, ACT_FORWARD_WATER_KB, ACT_FORWARD_WATER_KB},
};

static u32 sBackwardKnockbackActions[][3] = {
    {ACT_SOFT_BACKWARD_GROUND_KB, ACT_BACKWARD_GROUND_KB, ACT_HARD_BACKWARD_GROUND_KB},
    {ACT_BACKWARD_AIR_KB, ACT_BACKWARD_AIR_KB, ACT_HARD_BACKWARD_AIR_KB},
    {ACT_BACKWARD_WATER_KB, ACT_BACKWARD_WATER_KB, ACT_BACKWARD_WATER_KB},
};

uint32_t fake_damage_knock_back(struct MarioState *m, uint32_t damage, uint32_t interactionSubtype, float xSrc, float ySrc, float zSrc)
{
    if (!sInvulnerable && !(m->flags & MARIO_VANISH_CAP) && !(interactionSubtype & INT_SUBTYPE_DELAY_INVINCIBILITY))
    {
        //o->oInteractStatus = INT_STATUS_INTERACTED | INT_STATUS_ATTACKED_MARIO;
        //m->interactObj = o;

        // calculate damage, substitute for take_damage_from_interact_object
        if (!(m->flags & MARIO_CAP_ON_HEAD))
        {
            damage += (damage + 1) / 2;
        }

        if (m->flags & MARIO_METAL_CAP)
        {
            damage = 0;
        }

        m->hurtCounter += 4 * damage; // apply hurt counter

        if (interactionSubtype & INT_SUBTYPE_BIG_KNOCKBACK)
        {
            m->forwardVel = 40.0f;
        }

        if (damage > 0)
        {
            play_sound(SOUND_MARIO_ATTACKED, m->marioObj->header.gfx.cameraToObject);
        }

        //update_mario_sound_and_camera(m);
        return drop_and_set_mario_action(m, fake_determine_knockback_action(m, damage, xSrc, ySrc, zSrc), damage);
    }

    return FALSE;
}

s16 fake_mario_obj_angle_to_object(struct MarioState *m, float xSrc, float zSrc)
{
    f32 dx = xSrc - m->pos[0];
    f32 dz = zSrc - m->pos[2];

    return atan2s(dz, dx);
}

u32 fake_determine_knockback_action(struct MarioState *m, s32 damage, float xSrc, float ySrc, float zSrc)
{
    u32 bonkAction;

    s16 terrainIndex = 0; // 1 = air, 2 = water, 0 = default
    s16 strengthIndex = 0;

    s16 angleToObject = fake_mario_obj_angle_to_object(m, xSrc, zSrc);
    s16 facingDYaw = angleToObject - m->faceAngle[1];
    s16 remainingHealth = m->health - 0x40 * m->hurtCounter;

    if (m->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER))
    {
        terrainIndex = 2;
    }
    else if (m->action & (ACT_FLAG_AIR | ACT_FLAG_ON_POLE | ACT_FLAG_HANGING))
    {
        terrainIndex = 1;
    }

    if (remainingHealth < 0x100 || damage >= 2)
    {
        strengthIndex = 2;
    }

    m->faceAngle[1] = angleToObject;

    if (terrainIndex == 2)
    {
        if (m->forwardVel < 28.0f)
        {
            mario_set_forward_vel(m, 28.0f);
        }

        if (m->pos[1] >= ySrc)
        {
            if (m->vel[1] < 20.0f)
            {
                m->vel[1] = 20.0f;
            }
        }
        else
        {
            if (m->vel[1] > 0.0f)
            {
                m->vel[1] = 0.0f;
            }
        }
    }
    else
    {
        if (m->forwardVel < 16.0f)
        {
            mario_set_forward_vel(m, 16.0f);
        }
    }

    if (-0x4000 <= facingDYaw && facingDYaw <= 0x4000)
    {
        m->forwardVel *= -1.0f;
        bonkAction = sBackwardKnockbackActions[terrainIndex][strengthIndex];
    }
    else
    {
        m->faceAngle[1] += 0x8000;
        bonkAction = sForwardKnockbackActions[terrainIndex][strengthIndex];
    }

    return bonkAction;
}

s16 fake_mario_obj_angle_to_pos(struct MarioState *m, float x, float z)
{
    f32 dx = x - m->pos[0];
    f32 dz = z - m->pos[2];

    return atan2s(dz, dx);
}

void fake_bounce_back_from_attack(struct MarioState *m, u32 interaction)
{
    if (interaction & (INT_PUNCH | INT_KICK | INT_TRIP))
    {
        if (m->action == ACT_PUNCHING)
        {
            m->action = ACT_MOVE_PUNCHING;
        }

        if (m->action & ACT_FLAG_AIR)
        {
            mario_set_forward_vel(m, -16.0f);
        }
        else
        {
            mario_set_forward_vel(m, -48.0f);
        }

        //set_camera_shake_from_hit(SHAKE_ATTACK);
        m->particleFlags |= PARTICLE_TRIANGLE;
    }

    if (interaction & (INT_PUNCH | INT_KICK | INT_TRIP | INT_FAST_ATTACK_OR_SHELL))
    {
        play_sound(SOUND_ACTION_HIT_2, m->marioObj->header.gfx.cameraToObject);
    }
}

u32 fake_determine_interaction(struct MarioState *m, float x, float y, float z)
{
    u32 interaction = 0;
    u32 action = m->action;

    s16 dYawToObject = fake_mario_obj_angle_to_pos(m, x, z) - m->faceAngle[1];
    if (action & ACT_FLAG_ATTACKING)
    {
        if (action == ACT_PUNCHING || action == ACT_MOVE_PUNCHING || action == ACT_JUMP_KICK)
        {
            if (m->flags & MARIO_PUNCHING)
            {
                // 120 degrees total, or 60 each way
                if (-0x2AAA <= dYawToObject && dYawToObject <= 0x2AAA)
                {
                    interaction = INT_PUNCH;
                }
            }
            if (m->flags & MARIO_KICKING)
            {
                // 120 degrees total, or 60 each way
                if (-0x2AAA <= dYawToObject && dYawToObject <= 0x2AAA)
                {
                    interaction = INT_KICK;
                }
            }
            if (m->flags & MARIO_TRIPPING)
            {
                // 180 degrees total, or 90 each way
                if (-0x4000 <= dYawToObject && dYawToObject <= 0x4000)
                {
                    interaction = INT_TRIP;
                }
            }
        }
        else if (action == ACT_GROUND_POUND || action == ACT_TWIRLING)
        {
            if (m->vel[1] < 0.0f)
            {
                interaction = INT_GROUND_POUND_OR_TWIRL;
            }
        }
        else if (action == ACT_GROUND_POUND_LAND || action == ACT_TWIRL_LAND)
        {
            // Neither ground pounding nor twirling change Mario's vertical speed on landing.,
            // so the speed check is nearly always true (perhaps not if you land while going upwards?)
            // Additionally, actionState it set on each first thing in their action, so this is
            // only true prior to the very first frame (i.e. active 1 frame prior to it run).
            if (m->vel[1] < 0.0f && m->actionState == 0)
            {
                interaction = INT_GROUND_POUND_OR_TWIRL;
            }
        }
        else if (action == ACT_SLIDE_KICK || action == ACT_SLIDE_KICK_SLIDE)
        {
            interaction = INT_SLIDE_KICK;
        }
        else if (action & ACT_FLAG_RIDING_SHELL || m->forwardVel <= -26.0f || 26.0f <= m->forwardVel)
        {
            interaction = INT_FAST_ATTACK_OR_SHELL;
        }
    }

    // Prior to this, the interaction type could be overwritten. This requires, however,
    // that the interaction not be set prior. This specifically overrides turning a ground
    // pound into just a bounce.
    if (interaction == 0 && action & ACT_FLAG_AIR)
    {
        if (m->vel[1] < 0.0f)
        {
            if (m->pos[1] > y)
            {
                interaction = INT_HIT_FROM_ABOVE;
            }
        }
        else
        {
            if (m->pos[1] < y)
            {
                interaction = INT_HIT_FROM_BELOW;
            }
        }
    }

    return interaction;
}

void fake_bounce_off_object(struct MarioState *m, float x, float y, float z, float hitboxHeight, f32 velY)
{
    //m->pos[1] = y + hitboxHeight;
    m->vel[1] = velY;

    m->flags &= ~MARIO_UNKNOWN_08;

    play_sound(SOUND_ACTION_BOUNCE_OFF_OBJECT, m->marioObj->header.gfx.cameraToObject);
}

u32 fake_interact_hit_from_below(struct MarioState *m, float x, float y, float z, float hitboxHeight)
{
    u32 interaction;
    if (m->flags & MARIO_METAL_CAP)
    {
        interaction = INT_FAST_ATTACK_OR_SHELL;
    }
    else
    {
        interaction = fake_determine_interaction(m, x, y, z);
    }

    if (interaction & INT_ANY_ATTACK)
    {
        fake_bounce_back_from_attack(m, interaction);

        if (interaction & INT_HIT_FROM_BELOW)
        {
            m->vel[1] = 0.0f;
        }

        if (interaction & INT_HIT_FROM_ABOVE)
        {
            fake_bounce_off_object(m, x, y, z, hitboxHeight, 30.0f);
        }
        return TRUE;
    }
    else /* if (fake_damage_knock_back(m, 0, 0, x, y, z))*/
    {
        return FALSE;
    }

    return FALSE;
}

u32 fake_interact_bounce_top(struct MarioState *m, float x, float y, float z, float hitboxHeight)
{
    u32 interaction;
    if (m->flags & MARIO_METAL_CAP)
    {
        interaction = INT_FAST_ATTACK_OR_SHELL;
    }
    else
    {
        interaction = fake_determine_interaction(m, x, y, z);
    }

    if (interaction & INT_ATTACK_NOT_FROM_BELOW)
    {
        fake_bounce_back_from_attack(m, interaction);

        if (interaction & INT_HIT_FROM_ABOVE)
        {
            fake_bounce_off_object(m, x, y, z, hitboxHeight, 30.0f);
        }
        return TRUE;
    }
    else /* if (fake_damage_knock_back(m, 0, 0, x, y, z))*/
    {
        return FALSE;
    }

    return FALSE;
}
