#include <ultra64.h>
#include "functions.h"
#include "variables.h"
#include <math.h>

extern f32 mapModel_getFloorY(f32[3]);

typedef enum {
    CH_BAT_STATE_ROOSTING = 1,
    CH_BAT_STATE_EXIT_ROOST = 2,
    CH_BAT_STATE_CHASE = 3,
    CH_BAT_STATE_ROAM = 4,
    CH_BAT_STATE_FLY_HOME = 5,
    CH_BAT_STATE_ENTER_ROOST = 6,
    CH_BAT_STATE_FALL = 7,
    CH_BAT_STATE_DIE = 8
} ChBatState;

typedef struct {
    f32 cooldown; //cooldown timer after attacking the player
    f32 roost_yaw;
} ChBatLocal;

void chbat_update(Actor *this);

/* .data */
ActorAnimationInfo sChBatAnimations[] = {
    {0, 0.0f},
    { ASSET_AE_ANIM_BAT_ROOST, 12.0f},
    { ASSET_AD_ANIM_BAT_TAKE_FLIGHT,  0.3f},
    { ASSET_9D_ANIM_BAT_FLY,  2.0f},
    { ASSET_9D_ANIM_BAT_FLY,  0.4f},
    { ASSET_9D_ANIM_BAT_FLY,  0.35f},
    { ASSET_AD_ANIM_BAT_TAKE_FLIGHT,  0.2f},
    { ASSET_2A9_ANIM_BAT_FALL,  0.4f},
    { ASSET_2AA_ANIM_BAT_DIE,  0.85f},
};

ActorInfo gChBat = { 
    MARKER_127_BAT, ACTOR_163_BAT, ASSET_3CA_MODEL_BAT, 
    CH_BAT_STATE_ROOSTING, sChBatAnimations, 
    chbat_update, actor_update_func_80326224, actor_draw, 
    2500, 0, 0.9f, 0
};

/* .code */
void chbat_roost(Actor *this){
    subaddie_set_state(this, CH_BAT_STATE_ROOSTING);
    actor_loopAnimation(this);
}

void chbat_exitRoost(Actor *this){
    if(!volatileFlag_get(VOLATILE_FLAG_C1_IN_FINAL_CHARACTER_PARADE)){
        subaddie_set_state_with_direction(this, CH_BAT_STATE_EXIT_ROOST, 0.01f, 1);
        actor_playAnimationOnce(this);
        this->actor_specific_1_f = 5.0f;
        FUNC_8030E8B4(SFX_419_UNKNOWN, 1.0f, 28000, this->position, 0x4e2, 0x9c4);
    }
}

void chbat_chase(Actor *this){
    subaddie_set_state(this, CH_BAT_STATE_CHASE);
    actor_loopAnimation(this);
    this->actor_specific_1_f = 5.0f;
}

void chbat_roam(Actor *this){
    subaddie_set_state(this, CH_BAT_STATE_ROAM);
    actor_loopAnimation(this);
}

void chbat_flyHome(Actor *this){
    ChBatLocal *local = (ChBatLocal *)&this->local;
    this->yaw_ideal = local->roost_yaw;
    subaddie_set_state(this, CH_BAT_STATE_FLY_HOME);
    actor_loopAnimation(this);
}

void chbat_enterRoost(Actor *this){
    subaddie_set_state_with_direction(this, CH_BAT_STATE_ENTER_ROOST, 0.99f, 0);
    actor_playAnimationOnce(this);
}

void chBat_fall(Actor *this){
    subaddie_set_state_with_direction(this, CH_BAT_STATE_FALL, 0.01f, 1);
    actor_loopAnimation(this);
    this->yaw += 180.0f;
    this->actor_specific_1_f = 20.0f;
    this->velocity_x = 300.0f;

}

int chbat_isWithinHorzontalRadiusOfHome(Actor * this, s32 dist){
    f32 f0 = this->position_x - this->unk1C_x;  
    f32 f2 = this->position_z - this->unk1C_z;  
    if(f0*f0 + f2*f2 < dist*dist)
        return 1;
    
    return 0;
}

bool chbat_nearHome(Actor *arg0, s32 arg1) {
    if( (arg0->position[1] < ( arg0->unk1C[1] + 0.5)) 
        && (( arg0->unk1C[1] - 0.5) < arg0->position[1])
    ) {
        return  func_80329480(arg0) != 0;
    }
    return FALSE;
}

void chBat_updateHeight(Actor *this, f32 target_height, f32 velocity){
    if(this->position[1] < target_height){
        this->position[1] += velocity;
        if(target_height < this->position[1]){
            this->position[1] = target_height;
        }
    }
    else if(target_height < this->position[1]){
        this->position[1] -= velocity;
        if(this->position[1] < target_height){
            this->position[1] = target_height;
        }
    }
}

bool func_8035FC98(Actor *this, f32 arg1){
    f32 sp34[3];
    f32 sp28[3];
    f32 sp1C[3];

    if(this->unk38_0)
        return FALSE;

    TUPLE_COPY(sp28, this->position);
    TUPLE_COPY(sp1C, sp28);
    sp1C[1] += arg1;

    if (func_80309B48(sp28, sp1C, sp34, 0x5E0000) != NULL)
        return TRUE;
    return FALSE;

}

bool func_8035FD28(Actor *this){
    this->velocity[0] -= 2.5;
    if(func_8035FC98(this, this->velocity[0]*2)){
        return FALSE;
    }
    this->position[1] += this->velocity[0];
    return TRUE;
}

bool chbat_nearPlayer(Actor *this) {
    f32 sp24[3];

    player_getPosition(sp24);
    sp24[1] += 50.0f;
    if (this->unk38_0){
        if(!func_80329260(this, sp24)){
            return FALSE;
        }
    } else{
        if(!func_80329210(this, sp24)){
            return FALSE;
        }
    }

    if (this->unk38_31 != 0) {
        if (func_80329530(this, 0) && (sp24[1] <= (this->unk1C[1] - 40.0f))) {
            return TRUE;
        }
        this->unk38_31--;
        return FALSE;
    }
    if (func_80329530(this, 800) && (sp24[1] <= (this->unk1C[1] - 40.0f))) {
        return TRUE;
    }

    return FALSE;
}

bool func_8035FEDC(Actor *this){
    if(func_8032CA80(this, this->unk38_0?0x13:4)){
        if(this->unk38_0){
            return FALSE;
        }
        
        if(func_80329480(this)){
            this->actor_specific_1_f = 0.0f;
            return FALSE;
        }
    }
    return TRUE;
}

bool func_8035FF5C(Actor *this){
    if(func_8032CA80(this, 5) && func_80329480(this)){
        this->actor_specific_1_f = 0.0f;
        return FALSE;
    }
    return TRUE;
}

void func_8035FFAC(Actor *this, f32 arg1){
    if(arg1 < 0.0f && randf() < 0.82){
        this->velocity_y = 0.0f;
    }
    else if(randf() < 0.92){
        this->velocity_y = 1.0f;
    }
}

void chbat_updateFlyingRoll(Actor *this) {
    f32 d_yaw;

    d_yaw = this->yaw_ideal - this->yaw;
    if (d_yaw >= 180.0f) {
        d_yaw -= 360.0f;
    }
    if (d_yaw < -180.0f) {
        d_yaw += 360.0f;
    }
    this->velocity[2] = -d_yaw;
    if (( this->roll <  this->velocity[2]) && ( this->roll < 55.0f)) {
        this->roll += 2.0f;
    }
    if ((this->velocity[2] < this->roll) && (this->roll > -55.0f)) {
        this->roll -= 2.0f;
    }
}

void chbat_updateRollTowardsZero(Actor *this){
    if(0.0f < this->roll){
        this->roll -= 2.0;
    }
    else if(this->roll < 0.0f){
        this->roll += 2.0;
    }
}

bool func_80360198(Actor *this) {
    f32 var_f16;
    f64 temp_f0;
    f64 d_yaw;
    f64 d_yaw_2;

    func_80328FB0(this, 5.0f);
    chbat_updateFlyingRoll(this);
    this->actor_specific_1_f += (this->velocity[1] * 0.45) - (0.001 * this->actor_specific_1_f);
    if (this->actor_specific_1_f > 13.0) {
        this->actor_specific_1_f = 13.0f;
    }
    if (this->actor_specific_1_f < -13.0) {
        this->actor_specific_1_f = -13.0f;
    }

    this->velocity[0] += ((this->velocity[1] * 0.9) - 0.6);
    if (this->velocity[0] > 6.0) {
        this->velocity[0] = 6.0f;
    }
    if (this->velocity[0] < -6.0) {
        this->velocity[0] = -6.0f;
    }

    if (func_8035FC98(this, 2 * this->velocity[0]) == 0) {
        this->position[1] += this->velocity[0];
    } else {
        this->velocity[0] *= -3.0f;
        this->position[1] += this->velocity[0];
    }

    if (this->state == CH_BAT_STATE_ROAM) {
        if (func_8035FF5C(this) == 0) {
            return FALSE;
        }
    } else if (func_8035FEDC(this) == 0) {
        return FALSE;
    }
    return TRUE;
}

f32 func_803603AC(Actor *this, s32 arg1, u8 arg2){
    f32 phi_f2;
    f32 dy;
    f32 D1, D2;
    f32 unused;
    f32 player_position[3];

    switch (arg2) {
    case 1:
        player_getPosition(player_position);
        break;

    case 2:
        TUPLE_COPY(player_position, this->unk1C);
        break;
    }

    D1 = SQ(this->position[0] - player_position[0]);
    D2 = SQ(this->position[2] - player_position[2]);
    
    dy = this->position[1] - player_position[1] - arg1;
    
    if(dy == 0.0 || D1 + D2 == 0.0)
        return 0.0f;
    
        
    phi_f2 = -(this->actor_specific_1_f*(dy))/(D1 + D2);
    if (phi_f2 >= 4.0f) {
        phi_f2 = 4.0f;
    } else  if (phi_f2 <= -4.0f) {
        phi_f2 = -4.0f;
    }
    return phi_f2;
}

int func_803604E8(Actor *this){
    f32 tmp_f0;
    this->yaw_ideal = (f32) func_80329784(this);
    tmp_f0 = func_803603AC(this, 170, 1);
    func_8035FFAC(this, tmp_f0);
    if(!func_80360198(this)){
        return 0;
    }
    return 1;
}

bool chbat_updateRoam(Actor *this) {
    s32 phi_v0;
    s32 phi_s1;
    s32 phi_s2;

    if (this->lifetime_value == 0.0f) {
        // fly towards home if lifetime is done 
        this->yaw_ideal = func_80257204(this->position[0], this->position[2], this->unk1C[0], this->unk1C[2]);
        func_8035FFAC(this, func_803603AC(this, -110, 2));
    }
    else{
        func_80328FB0(this, 5.0f); //update yaw
        chbat_updateFlyingRoll(this);
        if (func_80329480(this) != 0) {
            this->lifetime_value = 0.0f;
        } else {
            return TRUE;
        }
    }
    for(phi_s2 = 0; !func_80360198(this) && phi_s2 < 1; phi_s2++){
        this->lifetime_value = 45.0f;
        func_80328CEC(this, (s32) this->yaw, 90, 180);
        phi_s1 = 0;
        do{
            if (this->unk38_0) {
                phi_v0 = func_80329140(this, (s32) this->yaw_ideal, 200);
            } else {
                phi_v0 = func_80329078(this, (s32) this->yaw_ideal, 200);
            }

            if(phi_v0 == 0){
                phi_s1++;
                this->yaw_ideal += 30.0f;
                if (this->yaw_ideal >= 360.0f) {
                    this->yaw_ideal -= 360.0f;
                }
            }
        } while ( phi_v0 == 0 && phi_s1 < 0xC);
    }
    if (this->position[1] < (this->unk1C[1] + -110.0f)) {
        this->velocity[1] = 1.0f;
    } else {
        this->velocity[1] = (f32) randi2(0, 0);
    }
    if (this->position[1] <= mapModel_getFloorY(this->position)) {
        this->velocity[0] = 3.0f;
    }
    return TRUE;
}

void chBat_dieCollision(ActorMarker *this_marker, ActorMarker *other_marker){
    Actor *this = marker_getActor(this_marker);
    ChBatLocal *local = (ChBatLocal *)&this->local;
    chBat_fall(this);
    local->cooldown = 0.0f;
    this->marker->collidable = FALSE;
    FUNC_8030E8B4(SFX_115_BUZZBOMB_DEATH, 1.3f, 26000, this->position, 1250, 2500);
}

void chBat_attackCollision(ActorMarker *this_marker, ActorMarker *other_marker){
    Actor *this = marker_getActor(this_marker);
    ChBatLocal *local = (ChBatLocal *)&this->local;
    local->cooldown = 0.8f;
}

void chbat_update(Actor *this){
    f32 dt = time_getDelta();
    ChBatLocal *local = (ChBatLocal *)&this->local;
    f32 sp34;

    if(!this->initialized){
        this->initialized = TRUE;
        marker_setCollisionScripts(this->marker, NULL, chBat_attackCollision, chBat_dieCollision);
        this->unk38_0 = FALSE;
        this->actor_specific_1_f = 0.0f;
        this->velocity_x = 0.0f;
        TUPLE_COPY(this->unk1C, this->position); // set roost position
        local->roost_yaw = this->yaw;
    }
    if(local->cooldown <= 0.0){
        local->cooldown = 0.0f;
    }else{//L80360910
        local->cooldown -= dt;
        return;
    }

    switch(this->state){
        case CH_BAT_STATE_ROOSTING: //L80360918
            if(chbat_nearPlayer(this)){
                chbat_exitRoost(this);
            }
            break;
        case CH_BAT_STATE_EXIT_ROOST: //L80360938
            if( 0.98 <  anctrl_getAnimTimer(this->anctrl)
                || !func_8035FD28(this)
            ){
                chbat_chase(this);
            }
            break;
        case CH_BAT_STATE_CHASE: //L8036097C
            anctrl_setDuration(this->anctrl, 1.2 - this->velocity_y);
            if(!chbat_nearPlayer(this)){
                chbat_roam(this);
            }
            else if(!func_803604E8(this)){
                chbat_roam(this);
                this->unk38_31 = 0x3C;
            }
            else{
                if(!(globalTimer_getTime() & 0xf)){
                    if(randf() < 0.35){
                        FUNC_8030E8B4(SFX_419_UNKNOWN, 1.0f, 28000, this->position, 1250, 2500);
                    }
                }
            }//L80360A40
            if(actor_animationIsAt(this, 0.5f)){
                func_8030E878(SFX_2_CLAW_SWIPE, randf2(1.0f, 1.2f), 10000, this->position, 833.0f, 2500.0f);
            }
            break;
        case CH_BAT_STATE_ROAM: //L80360A9C
            if(chbat_nearPlayer(this)){
                chbat_chase(this);
            } else if(chbat_isWithinHorzontalRadiusOfHome(this, 20)){
                chbat_flyHome(this);
            } else{
                chbat_updateRoam(this);
            }//L80360AE8

            if(actor_animationIsAt(this, 0.5f)){
                func_8030E878(SFX_2_CLAW_SWIPE, randf2(1.0f, 1.2f), 10000, this->position, 833.0f, 2500.0f);
            }
            break;
        case CH_BAT_STATE_FLY_HOME: //L80360B3C
            if(chbat_nearPlayer(this)){
                chbat_chase(this);
            } else if(chbat_nearHome(this, 1)){
                chbat_enterRoost(this);
            } else{
                func_80328FB0(this, 5.0f);
                chBat_updateHeight(this, this->unk1C_y, 2.0f);
                chbat_updateRollTowardsZero(this);
            }

            if(actor_animationIsAt(this, 0.5f)){
                func_8030E878(SFX_2_CLAW_SWIPE, randf2(1.0f, 1.2f), 10000, this->position, 833.0f, 2500.0f);
            }
            break;
        case CH_BAT_STATE_ENTER_ROOST:
            if(anctrl_getAnimTimer(this->anctrl) < 0.01){
                chbat_roost(this);
            }
            break;
        case CH_BAT_STATE_FALL: //L80360C28
            sp34 = time_getDelta();
            func_8032CA80(this, this->unk38_0 ? 0x13 : 0x4);
            if(func_8035FC98(this, this->velocity_x * sp34)){
                this->position_y =  mapModel_getFloorY(this->position);
                subaddie_set_state_with_direction(this, CH_BAT_STATE_DIE, 0.01f, 1);
                actor_playAnimationOnce(this);
                func_8030E6A4(SFX_1F_HITTING_AN_ENEMY_3, 1.2f, 32200);
            }
            else{//L80360CC4
                this->position_y += this->velocity_x * sp34;
                this->velocity_x -=  1600.0f * sp34;
            }
            break;
        case CH_BAT_STATE_DIE: //L80360CF0
            if(actor_animationIsAt(this, 0.3f)){
                func_80326310(this);
            }
            break;
    }
}
