
/**
 * Behavior for bhvHomingAmp and bhvCirclingAmp.
 * These are distinct objects; one chases (homes in on) Mario,
 * while the other circles around a fixed location with a radius
 * of 200, 300, 400, or 0 (stationary).
 */

static struct ObjectHitbox sAmpHitbox = {
    /* interactType:      */ INTERACT_SHOCK,
    /* downOffset:        */ 40,
    /* damageOrCoinValue: */ 1,
    /* health:            */ 0,
    /* numLootCoins:      */ 0,
    /* radius:            */ 40,
    /* height:            */ 50,
    /* hurtboxRadius:     */ 50,
    /* hurtboxHeight:     */ 60,
};

/**
 * Homing amp initialization function.
 */
void bhv_homing_amp_init(void) {
    o->oHomeX = o->oPosX;
    o->oHomeY = o->oPosY;
    o->oHomeZ = o->oPosZ;
    o->oGravity = 0.0f;
    o->oFriction = 1.0f;
    o->oBuoyancy = 1.0f;
    o->oHomingAmpAvgY = o->oHomeY;

    // Homing amps start at 1/10th their normal size.
    // They grow when they "appear" to Mario.
    cur_obj_scale(0.1f);

    // Hide the amp (until Mario gets near).
    o->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
}

/**
 * Amps' attack handler, shared by both types of amp.
 */
static void check_amp_attack(void) {
    // Strange placement for this call. The hitbox is never cleared.
    // For perspective, this code is run every frame of bhv_circling_amp_loop
    // and every frame of a homing amp's HOMING_AMP_ACT_CHASE action.
    obj_set_hitbox(o, &sAmpHitbox);

    if (o->oInteractStatus & INT_STATUS_INTERACTED) {
        // Unnecessary if statement, maybe caused by a macro for
        //     if (o->oInteractStatus & INT_STATUS_INTERACTED) {
        //         o->oAction = X;
        //     }
        // ?
        if (o->oInteractStatus & INT_STATUS_INTERACTED) {
            // This function is used for both normal amps and homing amps,
            // AMP_ACT_ATTACK_COOLDOWN == HOMING_AMP_ACT_ATTACK_COOLDOWN
            o->oAction = AMP_ACT_ATTACK_COOLDOWN;
        }

        // Clear interact status
        o->oInteractStatus = 0;
    }
}

/**
 * Unhide the amp and grow until normal size, then begin chasing Mario.
 */
static void homing_amp_appear_loop(void) {
    // gLakituState.goalPos is the position lakitu is moving towards.
    // In Lakitu and Mario cam, it is usually very close to the current camera position.
    // In Fixed cam, it is the point behind Mario the camera will go to when transitioning
    // to Lakitu cam. Homing amps will point themselves towards this point when appearing.
    f32 relativeTargetX = gLakituState.goalPos[0] - o->oPosX;
    f32 relativeTargetZ = gLakituState.goalPos[2] - o->oPosZ;
    s16 targetYaw = atan2s(relativeTargetZ, relativeTargetX);

    o->oMoveAngleYaw = approach_s16_symmetric(o->oMoveAngleYaw, targetYaw, 0x1000);

    // For 30 frames, make the amp "appear" by increasing its size by 0.03 each frame,
    // except for the first frame (when oTimer == 0) because the expression in cur_obj_scale
    // evaluates to 0.1, which is the same as it was before. After 30 frames, it ends at
    // a scale factor of 0.97. The amp remains at 97% of its real height for 60 more frames.
    if (o->oTimer < 30) {
        cur_obj_scale(0.1 + 0.9 * (f32)(o->oTimer / 30.0f));
    } else {
        o->oAnimState = 1;
    }

    // Once the timer becomes greater than 90, i.e. 91 frames have passed,
    // reset the amp's size and start chasing Mario.
    if (o->oTimer > 90) {
        cur_obj_scale(1.0f);
        o->oAction = HOMING_AMP_ACT_CHASE;
        o->oAmpYPhase = 0;
    }
}

/**
 * Chase Mario.
 */
static void homing_amp_chase_loop(void) {
    // Lock on to Mario if he ever goes within 11.25 degrees of the amp's line of sight
    if ((o->oAngleToMario - 0x400 < o->oMoveAngleYaw)
        && (o->oMoveAngleYaw < o->oAngleToMario + 0x400)) {
        o->oHomingAmpLockedOn = TRUE;
        o->oTimer = 0;
    }

    // If the amp is locked on to Mario, start "chasing" him by moving
    // in a straight line at 15 units/second for 32 frames.
    if (o->oHomingAmpLockedOn == TRUE) {
        o->oForwardVel = 15.0f;

        // Move the amp's average Y (the Y value it oscillates around) to align with
        // Mario's head. Mario's graphics' Y + 150 is around the top of his head.
        // Note that the average Y will slowly go down to approach his head if the amp
        // is above his head, but if the amp is below it will instantly snap up.
        if (o->oHomingAmpAvgY > gMarioObject->header.gfx.pos[1] + 150.0f) {
            o->oHomingAmpAvgY -= 10.0f;
        } else {
            o->oHomingAmpAvgY = gMarioObject->header.gfx.pos[1] + 150.0f;
        }

        if (o->oTimer > 30) {
            o->oHomingAmpLockedOn = FALSE;
        }
    } else {
        // If the amp is not locked on to Mario, move forward at 10 units/second
        // while curving towards him.
        o->oForwardVel = 10.0f;

        obj_turn_toward_object(o, gMarioObject, 16, 0x400);

        // The amp's average Y will approach Mario's graphical Y position + 250
        // at a rate of 10 units per frame. Interestingly, this is different from
        // the + 150 used while chasing him. Could this be a typo?
        if (o->oHomingAmpAvgY < gMarioObject->header.gfx.pos[1] + 250.0f) {
            o->oHomingAmpAvgY += 10.0f;
        }
    }

    // The amp's position will sinusoidally oscillate 40 units around its average Y.
    o->oPosY = o->oHomingAmpAvgY + sins(o->oAmpYPhase * 0x400) * 20.0f;

    // Handle attacks
    check_amp_attack();

    // Give up if Mario goes further than 1500 units from the amp's original position
    if (!is_point_within_radius_of_mario(o->oHomeX, o->oHomeY, o->oHomeZ, 1500)) {
        o->oAction = HOMING_AMP_ACT_GIVE_UP;
    }
}

/**
 * Give up on chasing Mario.
 */
static void homing_amp_give_up_loop(void) {
    UNUSED u8 filler[8];

    // Move forward for 152 frames
    o->oForwardVel = 15.0f;

    if (o->oTimer > 150) {
        // Hide the amp and reset it back to its inactive state
        o->oPosX = o->oHomeX;
        o->oPosY = o->oHomeY;
        o->oPosZ = o->oHomeZ;
        o->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
        o->oAction = HOMING_AMP_ACT_INACTIVE;
        o->oAnimState = 0;
        o->oForwardVel = 0.0f;
        o->oHomingAmpAvgY = o->oHomeY;
    }
}

/**
 * Cool down after a successful attack, shared by both types of amp.
 */
static void amp_attack_cooldown_loop(void) {
    // Turn intangible and wait for 90 frames before chasing Mario again after hitting him.
    o->header.gfx.animInfo.animFrame += 2;
    o->oForwardVel = 0.0f;

    cur_obj_become_intangible();

    if (o->oTimer > 30) {
        o->oAnimState = 0;
    }

    if (o->oTimer > 90) {
        o->oAnimState = 1;
        cur_obj_become_tangible();
        o->oAction = HOMING_AMP_ACT_CHASE;
    }
}

/**
 * Homing amp update function.
 */
void bhv_homing_amp_loop(void) {
    switch (o->oAction) {
        case HOMING_AMP_ACT_INACTIVE:
            if (is_point_within_radius_of_mario(o->oHomeX, o->oHomeY, o->oHomeZ, 800) == TRUE) {
                // Make the amp start to appear, and un-hide it.
                o->oAction = HOMING_AMP_ACT_APPEAR;
                o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
            }
            break;

        case HOMING_AMP_ACT_APPEAR:
            homing_amp_appear_loop();
            break;

        case HOMING_AMP_ACT_CHASE:
            homing_amp_chase_loop();
            cur_obj_play_sound_1(SOUND_AIR_AMP_BUZZ);
            break;

        case HOMING_AMP_ACT_GIVE_UP:
            homing_amp_give_up_loop();
            break;

        case HOMING_AMP_ACT_ATTACK_COOLDOWN:
            amp_attack_cooldown_loop();
            break;
    }

    object_step();

    // Oscillate
    o->oAmpYPhase++;
}

/**
 * Circling amp initialization function.
 */
void bhv_circling_amp_init(void) {
    o->oHomeX = o->oPosX;
    o->oHomeY = o->oPosY;
    o->oHomeZ = o->oPosZ;
    o->oAnimState = 1;

    // Determine the radius of the circling amp's circle
    switch (o->oBhvParams2ndByte) {
        case AMP_BP_ROT_RADIUS_200:
            o->oAmpRadiusOfRotation = 200.0f;
            break;

        case AMP_BP_ROT_RADIUS_300:
            o->oAmpRadiusOfRotation = 300.0f;
            break;

        case AMP_BP_ROT_RADIUS_400:
            o->oAmpRadiusOfRotation = 400.0f;
            break;

        case AMP_BP_ROT_RADIUS_0:
            break;
    }

    // Choose a random point along the amp's circle.
    // The amp's move angle represents its angle along the circle.
    o->oMoveAngleYaw = random_u16();

    o->oAction = AMP_ACT_IDLE;
}

/**
 * Main update function for fixed amps.
 * Fixed amps are a sub-species of circling amps, with circle radius 0.
 */
static void fixed_circling_amp_idle_loop(void) {
    // Turn towards Mario, in both yaw and pitch.
    f32 xToMario = gMarioObject->header.gfx.pos[0] - o->oPosX;
    f32 yToMario = gMarioObject->header.gfx.pos[1] + 120.0f - o->oPosY;
    f32 zToMario = gMarioObject->header.gfx.pos[2] - o->oPosZ;
    s16 vAngleToMario = atan2s(sqrtf(xToMario * xToMario + zToMario * zToMario), -yToMario);

    obj_turn_toward_object(o, gMarioObject, 19, 0x1000);
    o->oFaceAnglePitch = approach_s16_symmetric(o->oFaceAnglePitch, vAngleToMario, 0x1000);

    // Oscillate 40 units up and down.
    // Interestingly, 0x458 (1112 in decimal) is a magic number with no apparent significance.
    // It is slightly larger than the 0x400 figure used for homing amps, which makes
    // fixed amps oscillate slightly quicker.
    // Also, this uses the cosine, which starts at 1 instead of 0.
    o->oPosY = o->oHomeY + coss(o->oAmpYPhase * 0x458) * 20.0f;

    // Handle attacks
    check_amp_attack();

    // Oscillate
    o->oAmpYPhase++;

    // Where there is a cur_obj_play_sound_1 call in the main circling amp update function,
    // there is nothing here. Fixed amps are the only amps that never play
    // the "amp buzzing" sound.
}

/**
 * Main update function for regular circling amps.
 */
static void circling_amp_idle_loop(void) {
    // Move in a circle.
    // The Y oscillation uses the magic number 0x8B0 (2224), which is
    // twice that of the fixed amp. In other words, circling amps will
    // oscillate twice as fast. Also, unlike all other amps, circling
    // amps oscillate 60 units around their average Y instead of 40.
    o->oPosX = o->oHomeX + sins(o->oMoveAngleYaw) * o->oAmpRadiusOfRotation;
    o->oPosZ = o->oHomeZ + coss(o->oMoveAngleYaw) * o->oAmpRadiusOfRotation;
    o->oPosY = o->oHomeY + coss(o->oAmpYPhase * 0x8B0) * 30.0f;
    o->oMoveAngleYaw += 0x400;
    o->oFaceAngleYaw = o->oMoveAngleYaw + 0x4000;

    // Handle attacks
    check_amp_attack();

    // Oscillate
    o->oAmpYPhase++;

    cur_obj_play_sound_1(SOUND_AIR_AMP_BUZZ);
}

/**
 * Circling amp update function.
 * This calls the main update functions for both types of circling amps,
 * and calls the common amp cooldown function when the amp is cooling down.
 */
void bhv_circling_amp_loop(void) {
    switch (o->oAction) {
        case AMP_ACT_IDLE:
            if (o->oBhvParams2ndByte == AMP_BP_ROT_RADIUS_0) {
                fixed_circling_amp_idle_loop();
            } else {
                circling_amp_idle_loop();
            }
            break;

        case AMP_ACT_ATTACK_COOLDOWN:
            amp_attack_cooldown_loop();
            break;
    }
}
