#include <libultraship.h>
#include <libultra/gbi.h>
#include "Penguin.h"
#include <vector>

#include "port/Game.h"

extern "C" {
#include "macros.h"
#include "main.h"
#include "actors.h"
#include "math_util.h"
#include "sounds.h"
#include "update_objects.h"
#include "render_player.h"
#include "external.h"
#include "bomb_kart.h"
#include "collision.h"
#include "code_80086E70.h"
#include "render_objects.h"
#include "code_80057C60.h"
#include "defines.h"
#include "code_80005FD0.h"
#include "math_util_2.h"
#include "collision.h"
#include "assets/bowsers_castle_data.h"
#include "ceremony_and_credits.h"
#include "objects.h"
#include "update_objects.h"
#include "render_objects.h"
#include "course_offsets.h"
#include "data/some_data.h"
extern s8 gPlayerCount;
}


OPenguin::OPenguin(FVector pos, u16 direction, PenguinType type, Behaviour behaviour) {
    Name = "Penguin";
    _type = type;
    _bhv = behaviour;

    find_unused_obj_index(&_objectIndex);

    init_object(_objectIndex, 0);

    Object *object = &gObjectList[_objectIndex];
    object->origin_pos[0] = pos.x * xOrientation;
    object->origin_pos[1] = pos.y;
    object->origin_pos[2] = pos.z;
    object->unk_0C6 = direction;

    switch(type) {
        case PenguinType::CHICK:
            object->surfaceHeight = 5.0f;
            object->sizeScaling = 0.04f;
            object->boundingBoxSize = 4;
            break;
        case PenguinType::ADULT:
            object->surfaceHeight = -80.0f;
            object->sizeScaling = 0.08f;
            object->boundingBoxSize = 4;
            break;
        case PenguinType::CREDITS:
            object->surfaceHeight = -80.0f;
            object->sizeScaling = 0.08f;
            object->sizeScaling = 0.15f;
            break;
        case PenguinType::EMPEROR:
            object->sizeScaling = 0.2f;
            object->boundingBoxSize = 0x000C;
            break;
    }
}

void OPenguin::Tick(void) {
    s32 objectIndex = _objectIndex;

    if (gObjectList[objectIndex].state != 0) {
        if (_type == PenguinType::EMPEROR) {
            OPenguin::EmperorPenguin(objectIndex);
        } else {
            OPenguin::OtherPenguin(objectIndex);
        }

        OPenguin::Behaviours(objectIndex);
    }
    if (func_80072320(objectIndex, 1) != 0) {
        func_80089820(objectIndex, 1.75f, 1.5f, 0x1900A046U);
    } else if (func_80072320(objectIndex, 8) != 0) {
        func_80089820(objectIndex, 1.3f, 1.0f, 0x1900A046U);
    } else {
        func_80089820(objectIndex, 1.5f, 1.25f, 0x1900A046U);
    }
    if ((is_obj_flag_status_active(objectIndex, 0x02000000) != 0) &&
        (func_80072354(objectIndex, 0x00000020) != 0)) {
        func_800722A4(objectIndex, 0x00000060);
        clear_object_flag(objectIndex, 0x02000000);
    }
}

void OPenguin::Draw(s32 cameraId) {
    s32 objectIndex = _objectIndex;
    s32 temp_s1;
    s32 drawDistance;
    u16 var_s1;
    u32 var_s3;

    if (gPlayerCountSelection1 == 1) {
        var_s3 = 0x0003D090;
    } else if (gPlayerCountSelection1 == 2) {
        var_s3 = 0x00027100;
    } else {
        var_s3 = 0x00015F90;
    }


    if (gObjectList[objectIndex].state >= 2) {

        // This code decreased draw dist if there's more players.
        // The var_s1 = 0x4000 for everything is just a guess.
        // It seems to check the angle of the camera.
        // These changes were so instance indices are not required.

        //if (gPlayerCountSelection1 == 1) {
            var_s1 = 0x4000;
            drawDistance = 1500;
        //} else {
        //    if (func_80072320(objectIndex, 8) != 0) {
        //        drawDistance = 500;
        //        var_s1 = 0x4000;
        //    } else {
        //        drawDistance = 600;
        //        var_s1 = 0x5555;
        //    }
        //}
        temp_s1 = func_8008A364(objectIndex, cameraId, var_s1, drawDistance);
        if (is_obj_flag_status_active(objectIndex, VISIBLE) != 0) {
            func_800557B4(objectIndex, (u32) temp_s1, var_s3);
        }
    }
}

void OPenguin::Behaviours(s32 objectIndex) { // func_800850B0
    Object* object;

    object = &gObjectList[objectIndex];
    switch (_bhv) {
        case 1: // emperor
            OPenguin::func_80085080(objectIndex);
            break;
        case 2:
            OPenguin::func_8008502C(objectIndex);
            break;
        case 3:
            OPenguin::func_80084D2C(objectIndex, 0);
            break;
        case 4:
            OPenguin::func_80084D2C(objectIndex, 1);
            break;
        case 5:
            OPenguin::func_80084D2C(objectIndex, 2);
            break;
        case 6:
            OPenguin::func_80084D2C(objectIndex, 3);
            break;
    }
    if (func_80072320(objectIndex, 0x00000020) != 0) {
        if (func_80072320(objectIndex, 0x00000040) != 0) {
            func_800722CC(objectIndex, 0x00000040);
            object->unk_084[6] = 0;
            object->unk_084[7] = 0x0096;
        }
        if (object->unk_084[7] == 0) {
            func_800722CC(objectIndex, 0x00000020);
        } else {
            object->unk_084[7]--;
            object->orientation[0] = object->direction_angle[0];
            object->orientation[1] += 0x2000;
            object->orientation[2] = object->direction_angle[2];
        }
    } else {
        object->orientation[0] = object->direction_angle[0];
        object->orientation[1] = object->direction_angle[1];
        object->orientation[2] = object->direction_angle[2];
    }
}

void OPenguin::func_80084D2C(s32 objectIndex, s32 arg1) {
    f32 sp24;

    switch (gObjectList[objectIndex].unk_0AE) {
        case 0:
            break;
        case 1:
            gObjectList[objectIndex].direction_angle[1] =
                func_800417B4(gObjectList[objectIndex].direction_angle[1], gObjectList[objectIndex].unk_0C6);
            if (gObjectList[objectIndex].direction_angle[1] == gObjectList[objectIndex].unk_0C6) {
                gObjectList[objectIndex].unk_09C = 4;
                gObjectList[objectIndex].unk_034 = 0.4f;
                func_80086FD4(objectIndex);
            }
            break;
        case 2:
            f32_step_towards(&gObjectList[objectIndex].unk_034, 0.8f, 0.02f);
            if (func_80087060(objectIndex, 0x0000000F) != 0) {
                func_800722A4(objectIndex, 1);
                func_800722A4(objectIndex, 2);
                gObjectList[objectIndex].unk_09C = 1;
                gObjectList[objectIndex].unk_0D8 = 1;
                gObjectList[objectIndex].textureListIndex = 0;
                gObjectList[objectIndex].type =
                    get_animation_length(d_course_sherbet_land_unk_data11, gObjectList[objectIndex].unk_0D8);
                func_800726CC(objectIndex, 3);
                func_80086FD4(objectIndex);
                if (func_80072354(objectIndex, 0x00000020) != 0) {
                    func_800722A4(objectIndex, 0x00000080);
                }
            }
            break;
        case 3:
            switch (arg1) {
                case 0:
                    sp24 = 1.0f;
                    break;
                case 1:
                    sp24 = 1.5f;
                    break;
                case 2:
                    sp24 = 2.0f;
                    break;
                case 3:
                    sp24 = 2.5f;
                    break;
            }
            f32_step_towards(&gObjectList[objectIndex].unk_034, sp24, 0.15f);
            if ((func_80072354(objectIndex, 2) != 0) && (sp24 == gObjectList[objectIndex].unk_034)) {
                func_80086FD4(objectIndex);
            }
            break;
        case 4:
            if (func_80087060(objectIndex, 0x0000001E) != 0) {
                func_800722CC(objectIndex, 1);
                func_80086FD4(objectIndex);
            }
            break;
        case 5:
            f32_step_towards(&gObjectList[objectIndex].unk_034, 0.4f, 0.2f);
            if (func_80087060(objectIndex, 0x0000000A) != 0) {
                func_800722A4(objectIndex, 2);
                gObjectList[objectIndex].unk_0D8 = 2;
                gObjectList[objectIndex].textureListIndex = 0;
                gObjectList[objectIndex].type =
                    get_animation_length(d_course_sherbet_land_unk_data11, gObjectList[objectIndex].unk_0D8);
                func_800726CC(objectIndex, 3);
                func_80086FD4(objectIndex);
            }
            break;
        case 6:
            if (func_80072354(objectIndex, 2) != 0) {
                gObjectList[objectIndex].unk_0D8 = 0;
                gObjectList[objectIndex].textureListIndex = 0;
                gObjectList[objectIndex].type =
                    get_animation_length(d_course_sherbet_land_unk_data11, gObjectList[objectIndex].unk_0D8);
                gObjectList[objectIndex].unk_0C6 += 0x8000;
                func_800726CC(objectIndex, 2);
                func_8008701C(objectIndex, 1);
            }
            break;
    }
    func_8008781C(objectIndex);
    object_calculate_new_pos_offset(objectIndex);
}

void OPenguin::func_80085080(s32 objectIndex) {
    func_8008B78C(objectIndex);
    object_calculate_new_pos_offset(objectIndex);
    func_800873F4(objectIndex);
}

void OPenguin::func_8008502C(s32 objectIndex) {
    func_80088038(objectIndex, gObjectList[objectIndex].unk_01C[1], gObjectList[objectIndex].unk_0C6);
    object_calculate_new_pos_offset(objectIndex);
    func_800873F4(objectIndex);
}


void OPenguin::EmperorPenguin(s32 objectIndex) {
    switch (gObjectList[objectIndex].state) {
        case 0:
            break;
        case 1:
            OPenguin::InitEmperorPenguin(objectIndex);
            break;
        case 2:
            func_80072E54(objectIndex, 0, gObjectList[objectIndex].type, 1, 0, -1);
            break;
    }
}

void OPenguin::InitEmperorPenguin(s32 objectIndex) {
    Object* object;

    object = &gObjectList[objectIndex];
    object->unk_0D8 = 0;
    object->model = (Gfx*) d_course_sherbet_land_unk_data1;
    object->vertex = (Vtx*) d_course_sherbet_land_unk_data11;
    //object->sizeScaling = 0.2f;
    //object->boundingBoxSize = 0x000C;
    object->unk_09C = 1;
    set_obj_direction_angle(objectIndex, 0U, 0U, 0U);
    object->unk_0DD = 1;
    func_80086EF0(objectIndex);
    object->spline = D_800E672C[0];
    set_object_flag(objectIndex, 0x04000800);
    object->type = get_animation_length(d_course_sherbet_land_unk_data11, 0);
    object_next_state(objectIndex);
}

void OPenguin::OtherPenguin(s32 objectIndex) {
    Object* object;

    object = &gObjectList[objectIndex];
    switch (object->state) {
        case 0:
            break;
        case 1:
            OPenguin::InitOtherPenguin(objectIndex);
            break;
        case 2:
            func_80072E54(objectIndex, 0, (s32) object->type, (s32) object->unk_09C, 0, -1);
            if (func_80072354(objectIndex, 0x00000020) != 0) {
                if (object->unk_084[6] == 0) {
                    object->unk_084[6] = random_int(0x005AU) + 0x5A;
                    func_800722A4(objectIndex, 0x00000080);
                } else {
                    object->unk_084[6]--;
                }
            }
            break;
        case 3:
            func_80072E54(objectIndex, 0, object->type, 1, 0, 0);
            break;
        case 4:
            func_800722CC(objectIndex, 2);
            object_next_state(objectIndex);
            break;
    }
    if (func_80072320(objectIndex, 0x00000020) != 0) {
        if (object->unk_084[6] == 0) {
            func_800722A4(objectIndex, 0x00000080);
            object->unk_084[6] = 0x0010;
        } else {
            object->unk_084[6]--;
        }
    }
    if (func_80072320(objectIndex, 0x00000080) != 0) {
        func_800722CC(objectIndex, 0x00000080);
        if (func_80072320(objectIndex, 0x00000010) != 0) {
            func_800C98B8(object->pos, object->velocity, SOUND_ARG_LOAD(0x19, 0x00, 0x70, 0x49));
        } else {
            func_800C98B8(object->pos, object->velocity, SOUND_ARG_LOAD(0x19, 0x00, 0x70, 0x17));
        }
    }
}

bool OPenguin::_toggle = false;

void OPenguin::InitOtherPenguin(s32 objectIndex) {
    Object* object;

    object = &gObjectList[objectIndex];
    object->unk_0D8 = 0;
    object->model = (Gfx*) d_course_sherbet_land_unk_data1;
    object->vertex = (Vtx*) d_course_sherbet_land_unk_data11;
    //object->boundingBoxSize = 4;
    object->unk_09C = 2;
    object->unk_04C = random_int(300);
    set_object_flag(objectIndex, 0x04000220);

    // This code has been significantly refactored from the original func_800845C8
    // Into a switch statement instead of checking for the index of the penguin
    switch(_bhv) {
        case Behaviour::CIRCLE:
            object->unk_01C[1] = Diameter;

            if (_toggle) {
                object->unk_0C4 = 0x8000;
            } else {
                object->unk_0C4 = 0;
            }

            _toggle = !_toggle;

            //object->unk_0DD = 2;
            func_800722A4(objectIndex, 8);
            break;
        case Behaviour::SLIDE3:
        case Behaviour::SLIDE4:
        case Behaviour::SLIDE6:
        case Behaviour::STRUT:

            if (gIsMirrorMode) {
                object->unk_0C6 = MirrorModeAngleOffset;
            }

            set_obj_direction_angle(objectIndex, 0U, object->unk_0C6 + 0x8000, 0U);
            func_800722A4(objectIndex, 20);
            break;
    }

    func_80086EF0(objectIndex);
    object->unk_034 = 0.0f;
    object->type = get_animation_length(d_course_sherbet_land_unk_data11, 0);
    object_next_state(objectIndex);
}

void OPenguin::Reset() {
    _toggle = false;
}
