#include "ac_ins_goki.h"

#include "m_field_info.h"
#include "m_name_table.h"
#include "m_common_data.h"
#include "m_player_lib.h"
#include "ac_set_ovl_insect.h"

enum {
    aIGK_INIT_TREE,
    aIGK_INIT_CEDAR,

    aIGK_INIT_NUM
};

enum {
    aIGK_ACTION_AVOID,
    aIGK_ACTION_LET_ESCAPE,
    aIGK_ACTION_WAIT_ON_FLOWER,
    aIGK_ACTION_MOVE_ON_FLOWER,
    aIGK_ACTION_WAIT_ON_TREE,
    aIGK_ACTION_MOVE_ON_TREE,
    aIGK_ACTION_WAIT_ON_ITEM,
    aIGK_ACTION_MOVE_ON_ITEM,

    aIGK_ACTION_NUM
};

#define aIGK_PLACE_TREE 4
#define aIGK_PLACE_FLOWER 5
#define aIGK_PLACE_ITEM 6

#define aIGK_BALL_SCARE_DIST (60.0f)
#define aIGK_NET_SCARE_DIST (70.0f)
#define aIGK_SCOOP_SCARE_DIST (30.0f)
#define aIGK_VIB_SCARE_DIST (150.0f)
#define aIGK_MAX_PATIENCE (90.0f) /* Patience threshold for scaring away */

#define aIGK_RANDOM_ANGLE() ((int)((fqrand() - 0.5f) * 2 * (f32)DEG2SHORT_ANGLE2(90.0f)))
#define aIGK_RANDOM_ANGLE2() ((s16)((fqrand() - 0.5f) * (f32)DEG2SHORT_ANGLE2(120.0f)))

#define aIGK_TARGET_ANGLE(insect) ((insect)->s32_work0)
#define aIGK_CHANGE_WAIT_TIMER(insect) ((insect)->s32_work1)
#define aIGK_MOVE_TIMER(insect) ((insect)->s32_work2)
#define aIGK_GET_ITEM_P(insect) ((insect)->s32_work3)
#define aIGK_SET_ITEM_P(insect, item_p) ((insect)->s32_work3 = (int)item_p)

static void aIGK_actor_move(ACTOR* actorx, GAME* game);
static void aIGK_setupAction(aINS_INSECT_ACTOR* insect, int action, GAME* game);

extern void aIGK_actor_init(ACTOR* actorx, GAME* game) {
    aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx;
    int act;

    insect->item = ITM_INSECT28;
    actorx->mv_proc = &aIGK_actor_move;
    insect->bg_range = 6.0f;

    switch (actorx->actor_specific) {
        case aSOI_SPAWN_NORMAL: {
            /* Spawn on flower */
            insect->bg_type = 2;
            actorx->world.position.y = 6.0f + mCoBG_GetBgY_OnlyCenter_FromWpos(actorx->world.position, -19.0f);
            xyz_t_move(&actorx->home.position, &actorx->world.position);
            actorx->shape_info.draw_shadow = FALSE;
            insect->flag = aIGK_PLACE_FLOWER;
            actorx->actor_specific = 0;
            act = aIGK_ACTION_WAIT_ON_FLOWER;
            break;
        }
        case aSOI_SPAWN_TREE: {
            /* Spawn insect on tree */
            static f32 init_posY[aIGK_INIT_NUM] = { 35.0f, 30.0f };
            static f32 init_posZ[aIGK_INIT_NUM] = { -2.0f, 8.0f };
            mActor_name_t* fg_p = mFI_GetUnitFG(actorx->world.position);
            int type;

            if (fg_p != NULL && *fg_p == CEDAR_TREE) {
                type = aIGK_INIT_CEDAR;
            } else {
                type = aIGK_INIT_TREE;
            }

            actorx->world.position.y = mCoBG_GetBgY_OnlyCenter_FromWpos2(actorx->world.position, -init_posY[type]);
            actorx->world.position.z += init_posZ[type];
            xyz_t_move(&actorx->home.position, &actorx->world.position);
            actorx->shape_info.draw_shadow = FALSE;
            insect->flag = aIGK_PLACE_TREE;
            actorx->actor_specific = 0;
            actorx->shape_info.rotation.y = DEG2SHORT_ANGLE2(-180.0f);
            actorx->shape_info.rotation.x = DEG2SHORT_ANGLE2(90.0f);
            act = aIGK_ACTION_WAIT_ON_TREE;
            break;
        }
        case aSOI_SPAWN_ITEM: {
            /* Spawn insect on ground item */
            actorx->world.position.y = 5.0f + mCoBG_GetBgY_OnlyCenter_FromWpos2(actorx->world.position, -2.0f);
            xyz_t_move(&actorx->home.position, &actorx->world.position);
            actorx->shape_info.draw_shadow = FALSE;
            insect->flag = aIGK_PLACE_ITEM;
            actorx->actor_specific = 0;

            aIGK_SET_ITEM_P(insect, mFI_GetUnitFG(actorx->world.position));
            if (aIGK_GET_ITEM_P(insect) == 0) {
                Actor_delete(actorx);
                return;
            }

            actorx->shape_info.rotation.y = DEG2SHORT_ANGLE2(-180.0f);
            actorx->shape_info.rotation.x = DEG2SHORT_ANGLE2(45.0f);
            act = aIGK_ACTION_WAIT_ON_ITEM;
            break;
        }
        default: {
            act = aIGK_ACTION_LET_ESCAPE;
            break;
        }
    }

    aIGK_setupAction(insect, act, game);
}

static void aIGK_anime_proc(aINS_INSECT_ACTOR* insect) {
    insect->_1E0 += 0.5f;
    if (insect->_1E0 >= 2.0f) {
        insect->_1E0 -= 2.0f;
    }
}

static int aIGK_check_tree_shaken(aINS_INSECT_ACTOR* insect) {
    int res = FALSE;

    if (insect->flag == aIGK_PLACE_TREE) {
        if (mPlib_Check_tree_shaken(&insect->tools_actor.actor_class.world.position) == TRUE) {
            res = TRUE;
        } else if (mPlib_Check_VibUnit_OneFrame(&insect->tools_actor.actor_class.world.position) == TRUE &&
                   insect->tools_actor.actor_class.player_distance_xz < aIGK_VIB_SCARE_DIST) {
            res = TRUE;
        }
    }

    return res;
}

static int aIGK_check_ball(aINS_INSECT_ACTOR* insect) {
    f32 dX = Common_Get(ball_pos).x - insect->tools_actor.actor_class.world.position.x;
    f32 dZ = Common_Get(ball_pos).z - insect->tools_actor.actor_class.world.position.z;
    int res = FALSE;

    if ((SQ(dX) + SQ(dZ)) < SQ(aIGK_BALL_SCARE_DIST)) {
        res = TRUE;
    }

    return res;
}

static int aIGK_check_player_net(aINS_INSECT_ACTOR* insect) {
    xyz_t net_pos;
    int res = FALSE;

    if (mPlib_Check_StopNet(&net_pos) == TRUE) {
        u32 label = mPlib_Get_item_net_catch_label();

        if (label != (u32)insect) {
            f32 dX = net_pos.x - insect->tools_actor.actor_class.world.position.x;
            f32 dZ = net_pos.z - insect->tools_actor.actor_class.world.position.z;

            if ((SQ(dX) + SQ(dZ)) < SQ(aIGK_NET_SCARE_DIST)) {
                res = TRUE;
            }
        }
    }

    return res;
}

static int aIGK_check_player_scoop(aINS_INSECT_ACTOR* insect) {
    xyz_t scoop_pos;
    int res = FALSE;

    if (mPlib_Check_DigScoop(&scoop_pos) == TRUE) {
        f32 dX = scoop_pos.x - insect->tools_actor.actor_class.world.position.x;
        f32 dZ = scoop_pos.z - insect->tools_actor.actor_class.world.position.z;

        if ((SQ(dX) + SQ(dZ)) < SQ(aIGK_SCOOP_SCARE_DIST)) {
            res = TRUE;
        }
    }

    return res;
}

static int aIGK_check_patience(aINS_INSECT_ACTOR* insect) {
    int res = FALSE;

    /* Check that no action which would scare the insect away has occurred */
    if (aIGK_check_tree_shaken(insect) == TRUE) {
        insect->patience = 100.0f;
    } else if (aIGK_check_ball(insect) == TRUE) {
        insect->patience = 100.0f;
    } else if (aIGK_check_player_net(insect) == TRUE) {
        insect->patience = 100.0f;
    } else if (aIGK_check_player_scoop(insect) == TRUE) {
        insect->patience = 100.0f;
    } else if (insect->flag == aIGK_PLACE_ITEM && *(mActor_name_t*)aIGK_GET_ITEM_P(insect) != ITM_KABU_SPOILED) {
        insect->patience = 100.0f;
    }

    if (insect->patience >= aIGK_MAX_PATIENCE) {
        res = TRUE;
    }

    return res;
}

static void aIGK_avoid(ACTOR* actorx, GAME* game) {
    aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx;
    f32 grav;

    grav = actorx->gravity;
    grav *= 1.1f;
    if (grav > 12.0f) {
        grav = 12.0f;
    }

    actorx->gravity = grav;
    sAdo_OngenPos((u32)actorx, NA_SE_26, &actorx->world.position);
    aIGK_anime_proc(insect);

    if (insect->bg_type == 0) {
        int h_ut_x;
        int h_ut_z;
        int now_ut_x;
        int now_ut_z;

        mFI_Wpos2UtNum(&h_ut_x, &h_ut_z, actorx->home.position);
        mFI_Wpos2UtNum(&now_ut_x, &now_ut_z, actorx->world.position);

        if (h_ut_x != now_ut_x || h_ut_z != now_ut_z) {
            insect->bg_type = 1;
        }
    }
}

static void aIGK_wait_on_flower(ACTOR* actorx, GAME* game) {
    aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx;

    if (aIGK_check_patience(insect) == TRUE) {
        aIGK_setupAction(insect, aIGK_ACTION_AVOID, game);
    } else {
        insect->timer--;

        if (insect->timer <= 0) {
            aIGK_setupAction(insect, aIGK_ACTION_MOVE_ON_FLOWER, game);
        } else {
            chase_angle(&actorx->world.angle.y, aIGK_TARGET_ANGLE(insect), DEG2SHORT_ANGLE2(8.4375f));
            actorx->shape_info.rotation.y = actorx->world.angle.y;
        }
    }
}

static void aIGK_move_on_flower(ACTOR* actorx, GAME* game) {
    aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx;

    if (aIGK_check_patience(insect) == TRUE) {
        aIGK_setupAction(insect, aIGK_ACTION_AVOID, game);
    } else {
        aIGK_MOVE_TIMER(insect)--;

        if (aIGK_MOVE_TIMER(insect) <= 0) {
            sAdo_OngenPos((u32)actorx, NA_SE_GOKI_MOVE, &actorx->world.position);
            insect->timer = (int)(2 * (90.0f + RANDOM_F(90.0f)));
            aIGK_setupAction(insect, aIGK_ACTION_WAIT_ON_FLOWER, game);
        } else {
            int flag = 0;
            f32 dX = actorx->world.position.x - actorx->home.position.x;
            f32 dZ = actorx->world.position.z - actorx->home.position.z;

            if (ABS(dX) >= 15.0f) {
                if (dX < 0.0f) {
                    actorx->world.position.x = actorx->home.position.x - 14.0f;
                    flag = 1;
                } else {
                    actorx->world.position.x = actorx->home.position.x + 14.0f;
                    flag = 2;
                }
            }

            if (ABS(dZ) >= 15.0f) {
                if (dZ < 0.0f) {
                    actorx->world.position.z = actorx->home.position.z - 14.0f;
                    flag |= 4;
                } else {
                    actorx->world.position.z = actorx->home.position.z + 14.0f;
                    flag |= 8;
                }
            }

            if (flag != 0) {
                // clang-format off
                static int ref_angl[16] = {
                    DEG2SHORT_ANGLE2(0.0f), DEG2SHORT_ANGLE2(90.0f), DEG2SHORT_ANGLE2(-90.0f), DEG2SHORT_ANGLE2(0.0f),
                    DEG2SHORT_ANGLE2(0.0f), DEG2SHORT_ANGLE2(45.0f), DEG2SHORT_ANGLE2(-45.0f), DEG2SHORT_ANGLE2(0.0f),
                    DEG2SHORT_ANGLE2(180.0f), DEG2SHORT_ANGLE2(135.0f), DEG2SHORT_ANGLE2(-135.0f), DEG2SHORT_ANGLE2(180.0f),
                    DEG2SHORT_ANGLE2(90.0f), DEG2SHORT_ANGLE2(90.0f), DEG2SHORT_ANGLE2(-90.0f), DEG2SHORT_ANGLE2(0.0f),
                };
                // clang-format on

                aIGK_TARGET_ANGLE(insect) = ref_angl[flag];
                insect->timer = 10;
                aIGK_setupAction(insect, aIGK_ACTION_WAIT_ON_FLOWER, game);
            } else {
                int done = chase_angle(&actorx->world.angle.y, aIGK_TARGET_ANGLE(insect), DEG2SHORT_ANGLE2(8.4375f));

                if (done == TRUE) {
                    aIGK_TARGET_ANGLE(insect) = actorx->world.angle.y + aIGK_RANDOM_ANGLE();
                }

                actorx->shape_info.rotation.y = actorx->world.angle.y;
            }
        }
    }
}

static void aIGK_wait_on_tree(ACTOR* actorx, GAME* game) {
    aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx;

    if (aIGK_check_patience(insect) == TRUE) {
        aIGK_setupAction(insect, aIGK_ACTION_AVOID, game);
    } else {
        insect->timer--;
        if (insect->timer <= 0) {
            aIGK_setupAction(insect, insect->action + 1, game);
        }
    }
}

static void aIGK_move_on_tree(ACTOR* actorx, GAME* game) {
    aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx;

    if (aIGK_check_patience(insect) == TRUE) {
        aIGK_setupAction(insect, aIGK_ACTION_AVOID, game);
    } else {
        chase_angle(&actorx->shape_info.rotation.y, aIGK_TARGET_ANGLE(insect), 128);
        aIGK_MOVE_TIMER(insect)--;

        if (aIGK_MOVE_TIMER(insect) <= 0) {
            aIGK_CHANGE_WAIT_TIMER(insect)--;
            if (aIGK_CHANGE_WAIT_TIMER(insect) <= 0) {
                aIGK_setupAction(insect, insect->action - 1, game);
            } else {
                aIGK_TARGET_ANGLE(insect) = -aIGK_TARGET_ANGLE(insect);
                aIGK_MOVE_TIMER(insect) = 30;
            }
        }
    }
}

static void aIGK_avoid_init(aINS_INSECT_ACTOR* insect, GAME* game) {
    static f32 base_angl[2] = { DEG2SHORT_ANGLE2(-22.5f), DEG2SHORT_ANGLE2(22.5f) };
    f32 rnd;
    s16 angl;

    insect->tools_actor.actor_class.shape_info.draw_shadow = TRUE;
    insect->tools_actor.actor_class.speed = 4.0f;
    insect->tools_actor.actor_class.max_velocity_y = 12.0f;
    insect->tools_actor.actor_class.gravity = 0.06f;
    insect->tools_actor.actor_class.shape_info.rotation.x = 0;
    insect->tools_actor.actor_class.position_speed.y = 0.0f;
    rnd = fqrand();
    insect->life_time = 0;
    insect->alpha_time = 80;
    angl = (int)(rnd * DEG2SHORT_ANGLE2(90.0f) - (f32)DEG2SHORT_ANGLE2(45.0f) + base_angl[rnd >= 0.5f]);

    insect->tools_actor.actor_class.world.angle.y = angl;
    insect->tools_actor.actor_class.shape_info.rotation.y = angl;
    insect->insect_flags.bit_1 = TRUE;
}

static void aIGK_let_escape_init(aINS_INSECT_ACTOR* insect, GAME* game) {
    PLAYER_ACTOR* player;

    insect->tools_actor.actor_class.shape_info.draw_shadow = TRUE;
    insect->life_time = 0;
    insect->alpha_time = 80;
    insect->tools_actor.actor_class.speed = 4.0f;
    insect->tools_actor.actor_class.max_velocity_y = 12.0f;
    insect->tools_actor.actor_class.gravity = 0.06f;
    insect->tools_actor.actor_class.shape_info.rotation.x = 0;
    insect->tools_actor.actor_class.position_speed.y = 0.0f;

    player = GET_PLAYER_ACTOR_GAME(game);
    if (player != NULL) {
        s16 angl = player->actor_class.shape_info.rotation.y + aIGK_RANDOM_ANGLE2();

        insect->tools_actor.actor_class.world.angle.y = angl;
        insect->tools_actor.actor_class.shape_info.rotation.y = angl;
    }

    insect->insect_flags.bit_1 = TRUE;
    insect->insect_flags.bit_2 = TRUE;
}

static void aIGK_wait_on_flower_init(aINS_INSECT_ACTOR* insect, GAME* game) {
    insect->tools_actor.actor_class.speed = 0.0f;
    insect->speed_step = 0.0f;
    insect->target_speed = 0.0f;
}

static void aIGK_move_on_flower_init(aINS_INSECT_ACTOR* insect, GAME* game) {
    if (aIGK_MOVE_TIMER(insect) == 0) {
        aIGK_MOVE_TIMER(insect) = (int)(2 * (90.0f + RANDOM_F(210.0f)));
    }

    insect->target_speed = 0.4f;
    insect->speed_step = 0.1f;
}

static void aIGK_wait_on_tree_init(aINS_INSECT_ACTOR* insect, GAME* game) {
    insect->timer = (int)(2 * (10.0f + RANDOM_F(10.0f)));
}

static void aIGK_move_on_tree_init(aINS_INSECT_ACTOR* insect, GAME* game) {
    aIGK_TARGET_ANGLE(insect) = DEG2SHORT_ANGLE2(175.78125f);
    aIGK_CHANGE_WAIT_TIMER(insect) = 3 + RANDOM(2);
    aIGK_MOVE_TIMER(insect) = 30;
}

typedef void (*aIGK_INIT_PROC)(aINS_INSECT_ACTOR* insect, GAME* game);

static void aIGK_setupAction(aINS_INSECT_ACTOR* insect, int action, GAME* game) {
    // clang-format off
    static aIGK_INIT_PROC init_proc[aIGK_ACTION_NUM] = {
        &aIGK_avoid_init,
        &aIGK_let_escape_init,
        &aIGK_wait_on_flower_init,
        &aIGK_move_on_flower_init,
        &aIGK_wait_on_tree_init,
        &aIGK_move_on_tree_init,
        &aIGK_wait_on_tree_init,
        &aIGK_move_on_tree_init,
    };
    // clang-format on

    // clang-format off
    static aINS_ACTION_PROC act_proc[aIGK_ACTION_NUM] = {
        &aIGK_avoid,
        &aIGK_avoid,
        &aIGK_wait_on_flower,
        &aIGK_move_on_flower,
        &aIGK_wait_on_tree,
        &aIGK_move_on_tree,
        &aIGK_wait_on_tree,
        &aIGK_move_on_tree,
    };
    // clang-format on

    insect->action = action;
    insect->action_proc = act_proc[action];
    (*init_proc[action])(insect, game);
}

static void aIGK_actor_move(ACTOR* actorx, GAME* game) {
    aINS_INSECT_ACTOR* insect = (aINS_INSECT_ACTOR*)actorx;
    u32 label = mPlib_Get_item_net_catch_label();

    if (label == (u32)actorx) {
        aIGK_setupAction(insect, aIGK_ACTION_LET_ESCAPE, game);
    } else if (insect->insect_flags.bit_3 == TRUE && insect->insect_flags.bit_2 == FALSE) {
        aIGK_setupAction(insect, aIGK_ACTION_LET_ESCAPE, game);
    } else {
        (*insect->action_proc)(actorx, game);
    }
}
