#include "patches.h"
#include "functions.h"
#include "transform_ids.h"
#include "bk_api.h"

#define IDS_PER_BEE 8
#define IDS_PER_PIECE 8

typedef struct {
    f32 unk0[3];
    f32 unkC[3];
    f32 unk18[3];
    f32 unk24[3];
}Struct_core2_47BD0_0;

typedef struct {
    s32 unk0;
    u8 unk4;
    u8 unk5;
    u8 unk6;
    u8 unk7;
    Struct_core2_47BD0_0 *unk8;
    f32 unkC[3];
    f32 unk18;
    f32 unk1C;
    BKModelBin *unk20;
    s32 unk24;
}ActorLocal_core2_47BD0;

typedef struct struct_24_s {
    s32 unk0;
    BKModelBin* model_bin;
    f32 unk8[3];
    f32 unk14[3];
    f32 unk20[3];
    f32 unk2C;
    f32 unk30[3];
    ParticleEmitter* unk3C;
    s32 unk40[4];
    f32 unk50;
} Struct24s;

typedef struct struct_25_s {
    Struct24s* begin;
    Struct24s* current;
    Struct24s* end;
    Struct24s data[];
} Struct25s;

extern struct0* D_8037C200;

extern f32 func_8028E82C(void);
extern void func_8028E84C(f32 arg0[3]);
extern bool func_8028F070(void);
extern bool func_8028F150(void);
extern bool func_8028F2DC(void);
extern void func_802CA790(Actor* this);
extern s32 func_8033A170(void);
extern enum map_e map_get(void);
extern enum bswatergroup_e player_getWaterState(void);

extern u32 get_cutscene_counter(void);

// @recomp Patched to give the bees in the bee swarm individual IDs. The bees can show interpolation glitches otherwise, as they all share one matrix
// group and can get culled individually based on distance. This is easily reproduceable by walking into a beehive with bees from a far away distance.
RECOMP_PATCH Actor *chBeeSwarm_draw(ActorMarker *marker, Gfx **gfx, Mtx **mtx, Vtx **vtx) {
    Actor *this;
    ActorLocal_core2_47BD0 *local;
    BKModelBin *phi_fp;
    s32 phi_s2;
    f32 sp8C[3];
    f32 sp80[3];
    Struct_core2_47BD0_0 *phi_s0;

    this = marker_getActor(marker);
    local = (ActorLocal_core2_47BD0 *)&this->local;
    phi_fp = marker_loadModelBin(marker);
    for (phi_s2 = 0, phi_s0 = local->unk8; phi_s2 < local->unk0; phi_s2++) {
        sp80[0] = 0.0f;
        sp80[1] = phi_s0->unk24[1] - 90.0f;
        sp80[2] = 0.0f;

        sp8C[0] = this->position[0] + phi_s0->unk0[0];
        sp8C[1] = this->position[1] + phi_s0->unk0[1];
        sp8C[2] = this->position[2] + phi_s0->unk0[2];

        // @recomp Set the model transform ID.
        s32 cur_drawn_marker_spawn_index = bkrecomp_get_marker_spawn_index(marker);
        u32 transform_id = MARKER_TRANSFORM_ID_START + cur_drawn_marker_spawn_index * MARKER_TRANSFORM_ID_COUNT + phi_s2 * IDS_PER_BEE;
        u32 prev_transform_id = cur_drawn_model_transform_id;
        cur_drawn_model_transform_id = transform_id;

        modelRender_setDepthMode(MODEL_RENDER_DEPTH_COMPARE);
        modelRender_setAlpha(0xFF);
        modelRender_draw(gfx, mtx, sp8C, sp80, 0.25f, NULL, phi_fp);

        local->unk5 |= func_8033A170();
        if (phi_s2 < 10) {
            sp8C[1] = local->unk18 + 6.0f;
            modelRender_setAlpha(0xC0);
            modelRender_setDepthMode(MODEL_RENDER_DEPTH_COMPARE);
            modelRender_draw(gfx, mtx, sp8C, sp80, 0.1f, NULL, local->unk20);
            local->unk5 |= func_8033A170();
        }
        phi_s0++;

        // @recomp Reset the model transform ID.
        cur_drawn_model_transform_id = prev_transform_id;
    }
    return this;
}

// @recomp Patched to give the actors which spawn multiple pieces individual IDs for each piece. This function is primarily used by explosions of wood and others.
RECOMP_PATCH Actor* func_802C8484(ActorMarker* marker, Gfx** gfx, Mtx** mtx, Vtx** vtx) {
    Struct25s* temp_s1;
    Struct24s* phi_s0;
    f32 sp5C;
    Actor* sp58;
    u32 phi_v1;
    s32 phi_s4;
    
    sp58 = marker_getActorAndRotation(marker, &sp5C);
    temp_s1 = (Struct25s*)(sp58->unk40);
    phi_s4 = FALSE;
    for (phi_s0 = temp_s1->begin; phi_s0 < temp_s1->current; phi_s0++) {
        if ((phi_s0->unk0 != 0) && (phi_s0->model_bin != NULL)) {
            // @recomp Set the model transform ID.
            s32 cur_drawn_marker_spawn_index = bkrecomp_get_marker_spawn_index(marker);
            u32 transform_id = MARKER_TRANSFORM_ID_START + cur_drawn_marker_spawn_index * MARKER_TRANSFORM_ID_COUNT + (phi_s0 - temp_s1->begin) * IDS_PER_PIECE;
            u32 prev_transform_id = cur_drawn_model_transform_id;
            cur_drawn_model_transform_id = transform_id;

            modelRender_setDepthMode(MODEL_RENDER_DEPTH_FULL);
            modelRender_draw(gfx, mtx, phi_s0->unk8, phi_s0->unk14, phi_s0->unk2C / 10.0f, NULL, phi_s0->model_bin);
            phi_s4 = TRUE;

            // @recomp Reset the model transform ID.
            cur_drawn_model_transform_id = prev_transform_id;
        }
    }
    if (phi_s4 == FALSE) {
        marker_despawn(marker);
    }
    return sp58;
}

extern f32 D_8036E580[3];
extern void actor_postdrawMethod(ActorMarker *);
extern void actor_predrawMethod(Actor *);
extern BKModelBin *func_803257B4(ActorMarker *marker);

// @recomp Patch to skip interpolation on the Christmas tree while the lights
// turn on to prevent visual glitches.
RECOMP_PATCH Actor *actor_draw(ActorMarker *marker, Gfx **gfx, Mtx **mtx, Vtx **vtx) {
    f32 sp3C[3];
    Actor *this;

    this = marker_getActorAndRotation(marker, sp3C);

    // @recomp Check for the xmas tree model
    if (marker->modelId == ASSET_488_MODEL_XMAS_TREE) {
        // @recomp Check that the lights are currently turning on, flickering or turning off.
        if (this->state == 2 || this->state == 3 || this->state == 5) {
            // @recomp Skip interpolation
            cur_drawn_model_skip_interpolation = TRUE;
        }
    }
    // @recomp Check for the hut model.
    else if (marker->modelId == ASSET_7D7_MODEL_MM_HUT) {
        // @recomp Check if the hut is destroyed (HUT_STATE_2_DESTROYED).
        if (this->state == 2) {
            cur_drawn_model_skip_interpolation = TRUE;
        }
    }


    modelRender_preDraw((GenFunction_1)actor_predrawMethod, (s32)this);
    modelRender_postDraw((GenFunction_1)actor_postdrawMethod, (s32)marker);
    modelRender_draw(gfx, mtx, this->position, sp3C, this->scale, (this->unk104 != NULL) ? D_8036E580 : NULL, func_803257B4(marker));
    // @recomp Re-enable interpolation
    cur_drawn_model_skip_interpolation = FALSE;
    return this;
}

extern void func_802E0710(Actor *this);

// @recomp Make sure the model being skipped are the bulls during the intro cutscene, and only during the
// first few seconds of the cutscene while the camera is panning.
bool skip_drawing_intro_bulls(u32 model_id) {
    return 
        (map_get() == MAP_1E_CS_START_NINTENDO) && 
        (model_id == ASSET_353_MODEL_BIGBUTT || model_id == ASSET_354_MODEL_BULL_INTRO) &&
        get_cutscene_counter() < 180;
}

// @recomp Patched to skip drawing the bull actors on the intro cutscene's start.
RECOMP_PATCH Actor *func_802E0738(ActorMarker *marker, Gfx **gfx, Mtx **mtx, Vtx **vtx) {
    f32 sp34[3];
    Actor *this;

    this = marker_getActorAndRotation(marker, &sp34);
    
    // @recomp Check the condition for not drawing this actor if it's one of the bulls during the intro.
    if (skip_drawing_intro_bulls(marker->modelId)) {
        return this;
    }

    modelRender_preDraw((GenFunction_1)func_802E0710, (s32)this);
    modelRender_postDraw((GenFunction_1)actor_postdrawMethod, (s32)marker);
    modelRender_draw(gfx, mtx, this->position, sp34, this->scale, NULL, marker_loadModelBin(marker));
    return this;
}

// @recomp Patched to skip interpolating the player shadow on large surface changes.
RECOMP_PATCH Actor* func_802CA7BC(ActorMarker* marker, Gfx** gfx, Mtx** mtx, Vtx** vtx) {
    Actor* this;
    f32 sp60;
    f32 sp54[3];
    f32 rotation[3];
    f32 sp44;
    f32 sp40;
    f32 sp34[3];

    sp60 = (player_getWaterState() == BSWATERGROUP_2_UNDERWATER) ? 8.0f : 4.0f;
    this = marker_getActor(marker);
    if (!func_8028F070()
        || !func_8028F150()
        || !func_8028F2DC()
        ) {
        return this;
    }

    player_getPosition(sp54);
    sp40 = func_8028E82C();
    func_8028E84C(sp34);
    this->position_x = sp54[0];
    this->position_y = sp40 + sp60;
    this->position_z = sp54[2];

    func_80258108(sp34, &this->yaw, &this->pitch);

    rotation[0] = this->pitch;
    rotation[1] = this->yaw;
    rotation[2] = this->roll;
    sp44 = ml_map_f(sp54[1] - sp40, 0.0f, 300.0f, 0.43f, 0.28f);
    
    // @recomp Check for the player shadow model.
    if (marker->modelId == ASSET_3BF_MODEL_PLAYER_SHADOW) {
        // @recomp Skip interpolation if the player's projected position on the floor has changed too quickly. Only do this check when the triangle has changed.
        static f32 previousPosY = 0.0f;
        static f32 previousNormY = 1.0f;
        static s16 previousTri[3] = { 0, 0, 0 };
        s16* currentTri = &D_8037C200->unk4.unk0[0];
        if (currentTri[0] != previousTri[0] || currentTri[1] != previousTri[1] || currentTri[2] != previousTri[2]) {
            f32 projectedHeightDistance = previousNormY * mlAbsF(this->position_y - previousPosY);
            const f32 skipHeightThreshold = 20.0f;
            cur_drawn_model_skip_interpolation = projectedHeightDistance >= skipHeightThreshold;

            previousNormY = D_8037C200->normY;
            previousTri[0] = currentTri[0];
            previousTri[1] = currentTri[1];
            previousTri[2] = currentTri[2];
        }

        previousPosY = this->position_y;
    }

    modelRender_preDraw((GenFunction_1)func_802CA790, (s32)this);
    modelRender_draw(gfx, mtx, this->position, rotation, sp44, NULL, marker_loadModelBin(marker));

    // @recomp Re-enable interpolation
    cur_drawn_model_skip_interpolation = FALSE;

    return this;
}
