#include "common.h"
#include "effects.h"
#include "sprite.h"
#include "sprite/player.h"

enum {
    SUBSTATE_HAMMER_0   = 0,
    SUBSTATE_HAMMER_1   = 1,
    SUBSTATE_HAMMER_2   = 2
};

typedef struct HammerHitData {
    /* 0x00 */ Vec3f hitPos;
    /* 0x0C */ s32 unk_0C;
    /* 0x10 */ s32 hitID;
    /* 0x14 */ s32 unk_14;
    /* 0x18 */ s32 timer;
    /* 0x1C */ s32 unk_1C;
} HammerHitData;

BSS HammerHitData D_802B6E90;

HammerHitData* HammerHit = &D_802B6E90;

void action_hammer_end_swing(void);

s32 action_hammer_is_swinging_away(s32 animID) {
    if (animID & SPRITE_ID_BACK_FACING) {
        return TRUE;
    }

    // back facing swing anims
    switch (animID) {
        case ANIM_MarioW1_Smash1_Miss_Back:
        case ANIM_MarioW1_Smash1_Hit_Back:
        case ANIM_MarioW1_Smash2_Miss_Back:
        case ANIM_MarioW1_Smash2_Hit_Back:
        case ANIM_MarioW1_Smash3_Miss_Back:
        case ANIM_MarioW1_Smash3_Hit_Back:
            return TRUE;
    }
    return FALSE;
}

void action_hammer_play_hit_fx(s32 hitID) {
    PlayerStatus* playerStatus = &gPlayerStatus;
    f32 shakeAmt;
    s32 time;
    s32 radius;
    s32 soundID;
    f32 theta, sinTheta, cosTheta;
    s32 numParticles;
    f32 x, y, z;

    if (gPlayerData.hammerLevel == 2) {
        shakeAmt = 1.2f;
        time = 1;
        radius = 28;
        soundID = SOUND_HAMMER_STRIKE_3;
    } else if (gPlayerData.hammerLevel == 1) {
        shakeAmt = 0.8f;
        time = 1;
        radius = 16;
        soundID = SOUND_HAMMER_STRIKE_2;
    } else {
        shakeAmt = 0.4f;
        time = 1;
        radius = 4;
        soundID = SOUND_HAMMER_STRIKE_1;
    }

    theta = DEG_TO_RAD(player_get_side_angle());
    sinTheta = sin_rad(theta) * 25.0f;
    cosTheta = cos_rad(theta) * -25.0f;

    if (hitID <= NO_COLLIDER) {
        numParticles = 6;
        x = playerStatus->pos.x + sinTheta;
        y = playerStatus->pos.y;
        z = playerStatus->pos.z + cosTheta;
    } else {
        numParticles = 3;
        x = HammerHit->hitPos.x + sinTheta;
        y = HammerHit->hitPos.y + playerStatus->colliderHeight - 5.0f;
        z = HammerHit->hitPos.z + cosTheta;
        radius = 1;
    }

    exec_ShakeCamX(CAM_DEFAULT, CAM_SHAKE_DECAYING_VERTICAL, time, shakeAmt);
    fx_smoke_impact(0, x, y, z, radius, numParticles, 0, (time + 3) * 3);

    switch (is_ability_active(ABILITY_ATTACK_FX)) {
        case 1:
            soundID = SOUND_LIFE_SHROOM_CHIME;
            break;
        case 2:
            soundID = SOUND_PLANTS_BELL;
            break;
        case 3:
            soundID = SOUND_SLIDE_WHISTLE_OUT;
            break;
        case 4:
            soundID = SOUND_YOSHI;
            break;
        case 5:
            soundID = SOUND_HIT_WHACKA;
            break;
        case 6:
            soundID = SOUND_FLOWERS_LAUGH;
            break;
    }

    sfx_play_sound_at_player(soundID, SOUND_SPACE_DEFAULT);
    start_rumble(256, 50);
}

HitID action_hammer_test_swing_collision(void) {
    PlayerStatus* playerStatus = &gPlayerStatus;
    HitID ret;
    f32 yaw;
    f32 angle;
    f32 outSinTheta;
    f32 outCosTheta;
    f32 playerX, playerY, playerZ;
    f32 x, y, z;
    s32 i;

    // first attempt
    yaw = player_get_side_angle();
    if (action_hammer_is_swinging_away(playerStatus->trueAnimation)) {
        angle = clamp_angle(yaw + 90.0f - gCameras[gCurrentCameraID].curYaw);
        if (angle >= 90.0f && angle < 270.0f) {
            yaw += -30.0f;
        } else {
            yaw += 30.0f;
        }
    }

    sin_cos_rad(DEG_TO_RAD(yaw), &outSinTheta, &outCosTheta);
    playerX = playerStatus->pos.x;
    playerY = playerStatus->pos.y;
    playerZ = playerStatus->pos.z;

    // check collision along 16 points in a line away from the player
    for (i = 1; i < 16; i++) {
        x = playerX + (outSinTheta * i);
        y = playerY;
        z = playerZ - (outCosTheta * i);
        ret = player_test_lateral_overlap(PLAYER_COLLISION_HAMMER, playerStatus, &x, &y, &z, 4.0f, yaw);
        if (ret > NO_COLLIDER) {
            HammerHit->hitPos.x = x;
            HammerHit->hitPos.y = y;
            HammerHit->hitPos.z = z;
            break;
        }
    }

    // second attempt
    if (i >= 16) {
        yaw = player_get_side_angle();
        if (!action_hammer_is_swinging_away(playerStatus->trueAnimation)) {
            angle = clamp_angle(yaw + 90.0f - gCameras[gCurrentCameraID].curYaw);
            if (angle >= 90.0f && angle < 270.0f) {
                yaw += 15.0f;
            } else {
                yaw += -15.0f;
            }
        }

        // check collision along 16 points in a line away from the player
        sin_cos_rad(DEG_TO_RAD(yaw), &outSinTheta, &outCosTheta);
        for (i = 1; i < 16; i++) {
            x = playerX + (outSinTheta * i);
            y = playerY;
            z = playerZ - (outCosTheta * i);

            ret = player_test_lateral_overlap(PLAYER_COLLISION_HAMMER, playerStatus, &x, &y, &z, 4.0f, yaw);
            if (ret > NO_COLLIDER) {
                HammerHit->hitPos.x = x;
                HammerHit->hitPos.y = y;
                HammerHit->hitPos.z = z;
                break;
            }
        }
    }

    if (ret > NO_COLLIDER && (ret & COLLISION_WITH_ENTITY_BIT)) {
        s32 hammerLevel = gPlayerData.hammerLevel;

        switch (get_entity_type(ret)) {
            case ENTITY_TYPE_HAMMER1_BLOCK:
            case ENTITY_TYPE_HAMMER1_BLOCK_TINY:
                if (hammerLevel >= 0) {
                    ret = -1;
                } else {
                    playerStatus->animFlags |= PA_FLAG_SHIVERING;
                }
                break;
            case ENTITY_TYPE_HAMMER2_BLOCK:
            case ENTITY_TYPE_HAMMER2_BLOCK_TINY:
                if (hammerLevel >= 1) {
                    ret = -1;
                } else {
                    playerStatus->animFlags |= PA_FLAG_SHIVERING;
                }
                break;
            case ENTITY_TYPE_HAMMER3_BLOCK:
            case ENTITY_TYPE_HAMMER3_BLOCK_TINY:
                if (hammerLevel >= 2) {
                    ret = -1;
                } else {
                    playerStatus->animFlags |= PA_FLAG_SHIVERING;
                }
                break;
            case ENTITY_TYPE_WOODEN_CRATE:
            case ENTITY_TYPE_BOMBABLE_ROCK:
                playerStatus->animFlags |= PA_FLAG_SHIVERING;
                break;
            case ENTITY_TYPE_BLUE_SWITCH:
            case ENTITY_TYPE_RED_SWITCH:
            case ENTITY_TYPE_BRICK_BLOCK:
                ret = -1;
                break;
        }
    }

    return ret;
}

void action_update_hammer(void) {
    PlayerStatus* playerStatus = &gPlayerStatus;

    HammerHit->unk_1C = 0;

    if (playerStatus->flags & PS_FLAG_ACTION_STATE_CHANGED) {
        AnimID anim;
        s32 soundID;

        playerStatus->flags &= ~PS_FLAG_ACTION_STATE_CHANGED;
        playerStatus->flags |= PS_FLAG_NO_FLIPPING;
        HammerHit->timer = 0;
        playerStatus->actionSubstate = SUBSTATE_HAMMER_0;
        playerStatus->curSpeed = 0.0f;
        playerStatus->animNotifyValue = 0;
        HammerHit->hitID = action_hammer_test_swing_collision();

        if (gPlayerData.hammerLevel == 2) {
            soundID = SOUND_HAMMER_SWING_3;
            anim = ANIM_MarioW1_Smash3_Hit;
            if (HammerHit->hitID <= NO_COLLIDER) {
                soundID = SOUND_HAMMER_SWING_3;
                anim = ANIM_MarioW1_Smash3_Miss;
            }
        } else if (gPlayerData.hammerLevel == 1) {
            soundID = SOUND_HAMMER_SWING_2;
            anim = ANIM_MarioW1_Smash2_Hit;
            if (HammerHit->hitID <= NO_COLLIDER) {
                soundID = SOUND_HAMMER_SWING_2;
                anim = ANIM_MarioW1_Smash2_Miss;
            }
        } else {
            soundID = SOUND_HAMMER_SWING_1;
            anim = ANIM_MarioW1_Smash1_Hit;
            if (HammerHit->hitID <= NO_COLLIDER) {
                soundID = SOUND_HAMMER_SWING_1;
                anim = ANIM_MarioW1_Smash1_Miss;
            }
        }

        suggest_player_anim_allow_backward(anim);
        sfx_play_sound_at_player(soundID, SOUND_SPACE_DEFAULT);
        HammerHit->unk_0C = 0;
        HammerHit->unk_14 = 0;
    }

    playerStatus->flags &= ~PS_FLAG_HAMMER_CHECK;
    if (HammerHit->timer < 3 && (playerStatus->flags & PS_FLAG_ENTERING_BATTLE)) {
        // This is probably to stop Mario from triggering multiple battles at once by hammering while one is starting.
        playerStatus->flags |= PS_FLAG_TIME_STOPPED;
    } else if (HammerHit->timer < 2) {
        HammerHit->timer++;
    } else {
        action_hammer_end_swing();
    }
}

void action_hammer_end_swing(void) {
    PlayerStatus* playerStatus = &gPlayerStatus;
    CollisionStatus* collisionStatus;
    f32 yaw;
    f32 angle;
    f32 outSinTheta;
    f32 outCosTheta;
    f32 playerX, playerY, playerZ;
    f32 x, y, z;
    s32 result;
    s32 hammerLevel;
    s32 soundID;
    u32 unk_BC;
    s32 ten;
    s32 ret;
    s32 i;

    do {
        collisionStatus = &gCollisionStatus;
    } while (0); // required to match;

    yaw = player_get_side_angle();
    if (action_hammer_is_swinging_away(playerStatus->trueAnimation)) {
        angle = clamp_angle(yaw + 90.0f - gCameras[gCurrentCameraID].curYaw);
        if (angle >= 90.0f && angle < 270.0f) {
            yaw += -30.0f;
        } else {
            yaw += 30.0f;
        }
    }

    sin_cos_rad(DEG_TO_RAD(yaw), &outSinTheta, &outCosTheta);
    playerX = playerStatus->pos.x;
    playerY = playerStatus->pos.y;
    playerZ = playerStatus->pos.z;

    // check collision along 16 points in a line away from the player
    for (i = 1; i < 16; i++) {
        x = playerX + (outSinTheta * i);
        y = playerY;
        z = playerZ - (outCosTheta * i);
        result = player_test_lateral_overlap(PLAYER_COLLISION_HAMMER, playerStatus, &x, &y, &z, 4.0f, yaw);
        if (HammerHit->unk_14 == 0) {
            collisionStatus->lastWallHammered = result;
            if (result > NO_COLLIDER) {
                if (result & COLLISION_WITH_ENTITY_BIT) {
                    get_entity_by_index(result)->collisionTimer = 0;
                }
            }
        }

        if (result > NO_COLLIDER) {
            break;
        }
    }

    if (i >= 16) {
        yaw = player_get_side_angle();
        if (action_hammer_is_swinging_away(playerStatus->trueAnimation) == 0) {
            angle = clamp_angle(yaw + 90.0f - gCameras[gCurrentCameraID].curYaw);
            if (angle >= 90.0f && angle < 270.0f) {
                yaw += 15.0f;
            } else {
                yaw += -15.0f;
            }
        }
        sin_cos_rad(DEG_TO_RAD(yaw), &outSinTheta, &outCosTheta);

        // check collision along 16 points in a line away from the player
        for (i = 1; i < 16; i++) {
            x = playerX + (outSinTheta * i);
            y = playerY;
            z = playerZ - (outCosTheta * i);
            result = player_test_lateral_overlap(PLAYER_COLLISION_HAMMER, playerStatus, &x, &y, &z, 4.0f, yaw);
            if (HammerHit->unk_14 == 0) {
                collisionStatus->lastWallHammered = result;
                if (result > NO_COLLIDER) {
                    if (result & COLLISION_WITH_ENTITY_BIT) {
                        get_entity_by_index(result)->collisionTimer = 0;
                    }
                }
            }

            if (result > NO_COLLIDER) {
                break;
            }
        }
    }

    if (HammerHit->timer == 2) {
        hammerLevel = gPlayerData.hammerLevel;
        if (hammerLevel == 2) {
            soundID = SOUND_HAMMER_SWING_3;
        } else if (hammerLevel == 1) {
            soundID = SOUND_HAMMER_SWING_2;
        } else {
            soundID = SOUND_HAMMER_SWING_1;
        }
        sfx_play_sound_at_player(soundID, SOUND_SPACE_DEFAULT);

        action_hammer_play_hit_fx(HammerHit->hitID);

        if (collisionStatus->lastWallHammered > NO_COLLIDER && (collisionStatus->lastWallHammered & COLLISION_WITH_ENTITY_BIT)) {
            get_entity_by_index(collisionStatus->lastWallHammered)->collisionTimer = 0;
            playerStatus->flags |= PS_FLAG_HAMMER_CHECK;
        } else if (HammerHit->hitID <= NO_COLLIDER) {
            playerStatus->flags |= PS_FLAG_HAMMER_CHECK;
        }

        if (HammerHit->hitID <= NO_COLLIDER && gPlayerData.hammerLevel >= 2) {
            gCurrentHiddenPanels.tryFlipTrigger = TRUE;
            gCurrentHiddenPanels.flipTriggerPosY = playerStatus->pos.y;
        }
    }

    if (playerStatus->actionSubstate == SUBSTATE_HAMMER_0 && result > NO_COLLIDER && HammerHit->unk_14 == 0) {
        playerStatus->actionSubstate++;
    }
    if (playerStatus->actionSubstate == SUBSTATE_HAMMER_1 && result <= NO_COLLIDER) {
        playerStatus->actionSubstate = SUBSTATE_HAMMER_2;
    }
    HammerHit->timer++;
    if (result > NO_COLLIDER) {
        HammerHit->unk_14 = 1;
    }
    if (HammerHit->timer == 6) {
        playerStatus->flags &= ~PS_FLAG_NO_FLIPPING;
    }

    if (playerStatus->animNotifyValue == 1) {
        if (HammerHit->timer >= 7 && (playerStatus->pressedButtons & BUTTON_B)) {
            HammerHit->unk_1C = 1;
        }

        HammerHit->unk_14 = 0;
        ten = 10; // required to match
        if (HammerHit->unk_1C != 0 || HammerHit->timer > ten) {
            playerStatus->flags &= ~PS_FLAG_HAMMER_CHECK;
            set_action_state(ACTION_STATE_IDLE);
        }
        playerStatus->flags &= ~PS_FLAG_NO_FLIPPING;
    }
}
