/**
 * Behavior for MIPS (everyone's favorite yellow rabbit).
 */

/**
 * Initializes MIPS' physics parameters and checks if he should be active,
 * hiding him if necessary.
 */
void bhv_mips_init(void) {
    // Retrieve star flags for Castle Secret Stars on current save file.
    u8 starFlags;
    starFlags = save_file_get_star_flags(gCurrSaveFileNum - 1, -1);

    // If the player has >= 15 stars and hasn't collected first MIPS star...
    if (save_file_get_total_star_count(gCurrSaveFileNum - 1, 0, 24) >= 15 && (starFlags & 0x08) == 0) {
        o->oBehParams2ndByte = 0;
#ifndef VERSION_JP
        o->oMipsForwardVelocity = 40.0f;
#endif
    }
    // If the player has >= 50 stars and hasn't collected second MIPS star...
    else if (save_file_get_total_star_count(gCurrSaveFileNum - 1, 0, 24) >= 50
             && (starFlags & 0x10) == 0) {
        o->oBehParams2ndByte = 1;
#ifndef VERSION_JP
        o->oMipsForwardVelocity = 45.0f;
#endif
    } else {
        // No MIPS stars are available, hide MIPS.
        o->activeFlags = 0;
    }

    o->oInteractionSubtype = INT_SUBTYPE_HOLDABLE_NPC;

#ifndef VERSION_JP
    o->oGravity = 15.0f;
#else
    o->oGravity = 2.5f;
#endif
    o->oFriction = 0.89f;
    o->oBuoyancy = 1.2f;

    SetObjAnimation(0);
}

/**
 * Helper function that finds the waypoint that is both within 800 units of MIPS
 * and furthest from Mario's current location.
 */
s16 bhv_mips_find_furthest_waypoint_to_mario(void) {
    s8 i;
    s16 x, y, z;
    s16 furthestWaypointIndex = -1;
    f32 furthestWaypointDistance = -10000.0f;
    f32 distanceToMario;
    struct Waypoint **pathBase;
    struct Waypoint *waypoint;

    pathBase = segmented_to_virtual(&inside_castle_seg7_trajectory_mips);

    // For each waypoint in MIPS path...
    for (i = 0; i < 10; i++) {
        waypoint = segmented_to_virtual(*(pathBase + i));
        x = waypoint->pos[0];
        y = waypoint->pos[1];
        z = waypoint->pos[2];

        // Is the waypoint within 800 units of MIPS?
        if (is_point_close_to_object(o, x, y, z, 800)) {
            // Is this further from Mario than the last waypoint?
            distanceToMario =
                sqr(x - gMarioObject->header.gfx.pos[0]) + sqr(z - gMarioObject->header.gfx.pos[2]);
            if (furthestWaypointDistance < distanceToMario) {
                furthestWaypointIndex = i;
                furthestWaypointDistance = distanceToMario;
            }
        }
    }

    // Set MIPS' next waypoint to be the closest waypoint to Mario.
    o->oMipsStartWaypointIndex = furthestWaypointIndex;
    return (s16) o->oMipsStartWaypointIndex;
}

/**
 * Wait until Mario comes close, then resume following our path.
 */
void bhv_mips_act_wait_for_nearby_mario(void) {
    UNUSED s16 collisionFlags = 0;

    o->oForwardVel = 0.0f;
    collisionFlags = object_step();

    // If Mario is within 500 units...
    if (is_point_within_radius_of_mario(o->oPosX, o->oPosY, o->oPosZ, 500)) {
        // If we fail to find a suitable waypoint...
        if (bhv_mips_find_furthest_waypoint_to_mario() == -1) {
            // Call it quits.
            o->oAction = MIPS_ACT_WAIT_FOR_ANIMATION_DONE;
        } else {
            // Resume path following.
            SetObjAnimation(1);
            o->oAction = MIPS_ACT_FOLLOW_PATH;
        }
    }
}

/**
 * Continue to follow our path around the basement area.
 */
void bhv_mips_act_follow_path(void) {
    s16 collisionFlags = 0;
    s32 followStatus;
    struct Waypoint **pathBase;
    struct Waypoint *waypoint;

    // Retrieve current waypoint.
    pathBase = segmented_to_virtual(&inside_castle_seg7_trajectory_mips);
    waypoint = segmented_to_virtual(*(pathBase + o->oMipsStartWaypointIndex));

    // Set start waypoint and follow the path from there.
    o->oPathedStartWaypoint = waypoint;
    followStatus = obj_follow_path(followStatus);

    // Update velocity and angle and do movement.
#ifndef VERSION_JP
    o->oForwardVel = o->oMipsForwardVelocity;
#else
    o->oForwardVel = 45.0f;
#endif
    o->oMoveAngleYaw = o->oPathedTargetYaw;
    collisionFlags = object_step();

    // If we are at the end of the path, do idle animation and wait for Mario.
    if (followStatus == PATH_REACHED_END) {
        SetObjAnimation(0);
        o->oAction = MIPS_ACT_WAIT_FOR_NEARBY_MARIO;
    }

    // Play sounds during walk animation.
    if (func_8029F788() == 1 && (collisionFlags & OBJ_COL_FLAG_UNDERWATER)) {
        PlaySound2(SOUND_OBJ_MIPS_RABBIT_WATER);
        spawn_object(o, MODEL_NONE, bhvSurfaceWaveShrinking);
    } else if (func_8029F788() == 1) {
        PlaySound2(SOUND_OBJ_MIPS_RABBIT);
    }
}

/**
 * Seems to wait until the current animation is done, then go idle.
 */
void bhv_mips_act_wait_for_animation_done(void) {
    if (func_8029F788() == 1) {
        SetObjAnimation(0);
        o->oAction = MIPS_ACT_IDLE;
    }
}

/**
 * Handles MIPS falling down after being thrown.
 */
void bhv_mips_act_fall_down(void) {
#ifdef VERSION_EU
    s32 collisionFlags = 0;
#else
    s16 collisionFlags = 0;
#endif

    collisionFlags = object_step();
    o->header.gfx.unk38.animFrame = 0;

    if ((collisionFlags & OBJ_COL_FLAG_GROUNDED) == 1) {
        o->oAction = MIPS_ACT_WAIT_FOR_ANIMATION_DONE;

        o->oFlags |= OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW;
        o->oMoveAngleYaw = o->oFaceAngleYaw;

        if (collisionFlags & OBJ_COL_FLAG_UNDERWATER)
            spawn_object(o, MODEL_NONE, bhvSurfaceWaveShrinking);
    }
}

/**
 * Idle loop, after you catch MIPS and put him down.
 */
void bhv_mips_act_idle(void) {
    UNUSED s16 collisionFlags = 0;

    o->oForwardVel = 0;
    collisionFlags = object_step();

    // Spawn a star if he was just picked up for the first time.
    if (o->oMipsStarStatus == MIPS_STAR_STATUS_SHOULD_SPAWN_STAR) {
        bhv_spawn_star_no_level_exit(o->oBehParams2ndByte + 3);
        o->oMipsStarStatus = MIPS_STAR_STATUS_ALREADY_SPAWNED_STAR;
    }
}

/**
 * Handles all the actions MIPS does when he is not held.
 */
void bhv_mips_free(void) {
    switch (o->oAction) {
        case MIPS_ACT_WAIT_FOR_NEARBY_MARIO:
            bhv_mips_act_wait_for_nearby_mario();
            break;

        case MIPS_ACT_FOLLOW_PATH:
            bhv_mips_act_follow_path();
            break;

        case MIPS_ACT_WAIT_FOR_ANIMATION_DONE:
            bhv_mips_act_wait_for_animation_done();
            break;

        case MIPS_ACT_FALL_DOWN:
            bhv_mips_act_fall_down();
            break;

        case MIPS_ACT_IDLE:
            bhv_mips_act_idle();
            break;
    }
}

/**
 * Handles MIPS being held by Mario.
 */
void bhv_mips_held(void) {
    s16 dialogID;

    o->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
    SetObjAnimation(4); // Held animation.
    obj_set_pos_relative(gMarioObject, 0, 60.0f, 100.0f);
    obj_become_intangible();

    // If MIPS hasn't spawned his star yet...
    if (o->oMipsStarStatus == MIPS_STAR_STATUS_HAVENT_SPAWNED_STAR) {
        // Choose dialog based on which MIPS encounter this is.
        if (o->oBehParams2ndByte == 0)
            dialogID = DIALOG_084;
        else
            dialogID = DIALOG_162;

        if (set_mario_npc_dialog(1) == 2) {
            o->activeFlags |= ACTIVE_FLAG_INITIATED_TIME_STOP;
            if (cutscene_object_with_dialog(CUTSCENE_DIALOG, o, dialogID)) {
                o->oInteractionSubtype |= INT_SUBTYPE_DROP_IMMEDIATELY;
                o->activeFlags &= ~ACTIVE_FLAG_INITIATED_TIME_STOP;
                o->oMipsStarStatus = MIPS_STAR_STATUS_SHOULD_SPAWN_STAR;
                set_mario_npc_dialog(0);
            }
        }
    }
}

/**
 * Handles MIPS being dropped by Mario.
 */
void bhv_mips_dropped(void) {
    obj_get_dropped();
    o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
    SetObjAnimation(0);
    o->oHeldState = HELD_FREE;
    obj_become_tangible();
    o->oForwardVel = 3.0f;
    o->oAction = MIPS_ACT_IDLE;
}

/**
 * Handles MIPS being thrown by Mario.
 */
void bhv_mips_thrown(void) {
    obj_enable_rendering_2();
    o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
    o->oHeldState = HELD_FREE;
    o->oFlags &= ~OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW;
    SetObjAnimation(2);
    obj_become_tangible();
    o->oForwardVel = 25.0f;
    o->oVelY = 20.0f;
    o->oAction = MIPS_ACT_FALL_DOWN;
}

/**
 * MIPS' main loop.
 */
void bhv_mips_loop(void) {
    // Determine what to do based on MIPS' held status.
    switch (o->oHeldState) {
        case HELD_FREE:
            bhv_mips_free();
            break;

        case HELD_HELD:
            bhv_mips_held();
            break;

        case HELD_THROWN:
            bhv_mips_thrown();
            break;

        case HELD_DROPPED:
            bhv_mips_dropped();
            break;
    }
}
