#include "d/t/d_t_insect.h"

#include "c/c_lib.h"
#include "c/c_math.h"
#include "common.h"
#include "d/a/d_a_base.h"
#include "d/a/d_a_insect_butterfly.h"
#include "d/a/d_a_item.h"
#include "d/a/d_a_itembase.h"
#include "d/a/obj/d_a_obj_base.h"
#include "d/a/obj/d_a_obj_soil.h"
#include "d/a/obj/d_a_obj_warp.h"
#include "d/col/bg/d_bg_s.h"
#include "d/col/bg/d_bg_s_gnd_chk.h"
#include "d/col/bg/d_bg_s_lin_chk.h"
#include "d/flag/itemflag_manager.h"
#include "d/flag/sceneflag_manager.h"
#include "d/flag/storyflag_manager.h"
#include "f/f_base.h"
#include "f/f_manager.h"
#include "f/f_profile.h"
#include "f/f_profile_name.h"
#include "m/m_angle.h"
#include "m/m_vec.h"
#include "nw4r/math/math_geometry.h"
#include "nw4r/math/math_types.h"
#include "rvl/MTX/mtx.h"

SPECIAL_ACTOR_PROFILE(TAG_INSECT, dTgInsect_c, fProfile::TAG_INSECT, 0x167, 0, 0);

STATE_DEFINE(dTgInsect_c, Wait);
STATE_DEFINE(dTgInsect_c, WaitCreate);
STATE_DEFINE(dTgInsect_c, WaitForceEscape);
STATE_DEFINE(dTgInsect_c, End);

static const s32 unused[] = {
    0x001E0100, 0, 0, 0, 0, 0, 0, 0,
};
const f32 dTgInsect_c::SCALE_X = 100;
const f32 dTgInsect_c::SCALE_Y = 100;

static const f32 unused2[] = {150, 0.5f};

const s32 *useUnused() {
    return unused + (s32)unused2;
}

int dTgInsect_c::actorCreate() {
    if (getSubtype() == SUBTYPE_INVALID0) {
        return FAILED;
    }
    if (getSubtype() == SUBTYPE_INVALID1) {
        return FAILED;
    }
    if (getSubtype() == SUBTYPE_INVALID2) {
        return FAILED;
    }
    mInsectCount = getInsectCount();
    if (mInsectCount > 0xF) {
        mInsectCount = 0xF;
    } else {
        if (mInsectCount == 0xF) {
            mInsectCount = 1;
        }
    }
    mScale *= 0.01f;
    mRevealed = 0;
    mRevealedSpawnPos = mPosition;
    if (isTrialGateType() || isGossipStoneType() || isGoddessWallType() || isSpawnSubtype(SPAWN_BUG_MINIGAME)) {
        field_0x24e = 1;
    }
    return SUCCEEDED;
}

inline bool checkProfile(u16 prof, u32 target) {
    return prof == target;
}

int dTgInsect_c::actorPostCreate() {
    Subtype subtype = getSubtype();
    // ??? doesn't match without the double comparison
    if (subtype == SUBTYPE_WOODLAND_RHINO_BEETLE
        || subtype == SUBTYPE_WOODLAND_RHINO_BEETLE
        || subtype == SUBTYPE_SAND_CICADA
        || subtype == SUBTYPE_VOLCANIC_LADYBUG
        || subtype == SUBTYPE_SKY_STAG_BEETLE) {
        if (!checkValidSpawnLocation(mPosition, 1)) {
            return FAILED;
        }
    }
    dAcBase_c *obj = nullptr;
    do {
        obj = searchNextActor(obj);
        if (obj == nullptr) {
            break;
        }
        u16 prof = obj->mProfileName;
        if ((
                checkProfile(prof, fProfile::OBJ_TUBO) || checkProfile(prof, fProfile::OBJ_CARRY_STONE) ||
                checkProfile(prof, fProfile::OBJ_OCT_GRASS_LEAF) || checkProfile(prof, fProfile::OBJ_FRUIT) ||
                checkProfile(prof, fProfile::OBJ_BARREL) || checkProfile(prof, fProfile::OBJ_VSD) ||
                checkProfile(prof, fProfile::OBJ_SOIL)
            ) &&
            getSquareDistanceTo(obj->mPosition) < 25) {
            if (subtype == SUBTYPE_FARON_GRASSHOPPER
                || subtype == SUBTYPE_SKYLOFT_MANTIS
                || subtype == SUBTYPE_LANAYRU_ANT
                || subtype == SUBTYPE_EDLIN_ROLLER) {
                if (prof == fProfile::OBJ_SOIL && static_cast<dAcOsoil_c *>(obj)->isStateHole()) {
                    return FAILED;
                }
                setActorRef(obj);
            }
            break;
        }
    } while (true);
    if (mActorNode.isLinked()) {
        mStateMgr.changeState(StateID_WaitCreate);
    } else {
        if (isSpawnSubtype(SPAWN_BUG_MINIGAME)) {
            for (s32 i = 0; i < (s32)ARRAY_LENGTH(field_0x1f8); i++) {
                field_0x1f8[i] = 0;
            }
            mStateMgr.changeState(StateID_Wait);
        } else if (isTrialGateType()) {
            dAcOWarp_c *warp = static_cast<dAcOWarp_c *>(fManager_c::searchBaseByProfName(fProfile::OBJ_WARP));
            if (warp == nullptr) {
                return FAILED;
            }
            if (!warp->checkThisHasSongItem() || warp->isTrialComplete()) {
                return FAILED;
            }
            if ((warp->mPosition - mPosition).squareMagXZ() <  1000000.f) {
                mWarpRef.link(warp);
            } else {
                return FAILED;
            }
            mStateMgr.changeState(StateID_WaitForceEscape);
        } else if (isGossipStoneType()) {
            if (!ItemflagManager::sInstance->getFlagDirect(ITEM_BALLAD_OF_THE_GODDESS)) {
                return FAILED;
            }
            if (!StoryflagManager::sInstance->getFlag(STORYFLAG_IMPRISONED_1_DEFEATED)) {
                return FAILED;
            }
            if (SceneflagManager::sInstance->checkBoolFlag(mRoomID, mParams >> 0xC & 0xFF)) {
                return FAILED;
            }
        } else if (isGoddessWallType()) {
            if (!ItemflagManager::sInstance->getFlagDirect(ITEM_BALLAD_OF_THE_GODDESS)) {
                return FAILED;
            }
            if (!StoryflagManager::sInstance->getFlag(STORYFLAG_FARON_TRIAL_COMPLETE)) {
                return FAILED;
            }
            mStateMgr.changeState(StateID_WaitForceEscape);
        } else {
            mStateMgr.changeState(StateID_End);
        }
        spawnAll();
    }
    return SUCCEEDED;
}

int dTgInsect_c::doDelete() {
    return SUCCEEDED;
}

int dTgInsect_c::actorExecute() {
    mStateMgr.executeState();
    return SUCCEEDED;
}

int dTgInsect_c::draw() {
    return SUCCEEDED;
}

void dTgInsect_c::initializeState_Wait() {}

#pragma opt_strength_reduction off
void dTgInsect_c::executeState_Wait() {
    if (isSpawnSubtype(SPAWN_BUG_MINIGAME)) {
        s32 i = 0;
        for (; i < mInsectCount; i++) {
            if (!mLinks[i].isLinked()) {
                if (mInsectRespawnTimers[i] > 0) {
                    mInsectRespawnTimers[i]--;
                } else if (mInsectRespawnTimers[i] == 0) {
                    nw4r::math::MTX34 mtx;
                    MTXTrans(mtx, mPosition.x, mPosition.y, mPosition.z);
                    nw4r::math::MTX34 scale;
                    MTXScale(scale, mScale.x, mScale.y, mScale.x);
                    MTXConcat(mtx, scale, mtx);
                    nw4r::math::AABB aabb(-100, 0, -100, 100, 100, 100);
                    aabb.Set(&aabb, &mtx);
                    if (fn_801BB750(&aabb, 10000)) {
                        spawnInsect(i);
                    }
                } else {
                    mInsectRespawnTimers[i] = 900;
                }
            }
        }
    }
}
#pragma opt_strength_reduction on
void dTgInsect_c::finalizeState_Wait() {}
void dTgInsect_c::initializeState_WaitCreate() {
    if (!isSpawnSubtype(SPAWN_BUG_MINIGAME)) {
        field_0x24f = 1;
        for (s32 i = 0; i < mInsectCount; i++) {
            mShouldSpawn[i] = shouldSpawn();
        }
    }
}
void dTgInsect_c::executeState_WaitCreate() {
    if (mActorNode.isLinked()) {
        if (mActorNode.get()->mProfileName == fProfile::OBJ_VSD ||
            mActorNode.get()->mProfileName == fProfile::OBJ_SOIL) {
            if (mRevealed == 0) {
                return;
            }
        } else {
            if (mPosition.squareDistanceToXZ(mActorNode.get()->mPosition) < 25) {
                return;
            }
        }
    }
    mActorNode.unlink();
    for (s32 i = 0; i < mInsectCount; i++) {
        if (!isSpawnSubtype(SPAWN_BUG_MINIGAME)) {
            if (mShouldSpawn[i] != 0) {
                mShouldSpawn[i] = 0;
            } else {
                continue;
            }
        }
        dAcBase_c *insect = nullptr;
        mVec3_c tmp1 = mPosition;
        if (mRevealed) {
            tmp1 = mRevealedSpawnPos;
        }
        mAng3_c rot(0, cM::rndFX(65536), 0);
        switch (getSubtype()) {
            case SUBTYPE_LANAYRU_ANT:
                insect = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_ANT, 1, &tmp1, &rot, nullptr, 0, 0x3f);
                break;
            case SUBTYPE_FARON_GRASSHOPPER:
                insect = dAcObjBase_c::createInsectActor(
                    this, fProfile::INSECT_GRASSHOPPER, 1, &tmp1, &rot, nullptr, 0, 0x3f
                );
                break;
            case SUBTYPE_SKYLOFT_MANTIS:
                insect = dAcObjBase_c::createInsectActor(
                    this, fProfile::INSECT_GRASSHOPPER, 0x10000001, &tmp1, &rot, nullptr, 0, 0x3f
                );
                break;
            case SUBTYPE_EDLIN_ROLLER:
                insect =
                    dAcObjBase_c::createInsectActor(this, fProfile::INSECT_SCARAB, 1, &tmp1, &rot, nullptr, 0, 0x3f);
                break;
        }
        if (insect != nullptr) {
            mLinks[i].link(insect);
            mInsectRespawnTimers[i] = -1;
        }
    }
    if (isSpawnSubtype(SPAWN_BUG_MINIGAME)) {
        mStateMgr.changeState(StateID_Wait);
    } else {
        mStateMgr.changeState(StateID_End);
    }
}
void dTgInsect_c::finalizeState_WaitCreate() {}

void dTgInsect_c::initializeState_WaitForceEscape() {
    for (s32 i = 0; i < mInsectCount; i++) {
        dAcOInsect_c* tmp = static_cast<dAcOInsect_c*>(mLinks[i].get());
        if (tmp) {
            tmp->setActorProperty(AC_PROP_0x4);
        }
    }
}

void dTgInsect_c::executeState_WaitForceEscape() {
    if (isTrialGateType()) {
        if (mWarpRef.isLinked()) {
            dAcOWarp_c *warp = static_cast<dAcOWarp_c *>(mWarpRef.get());
            if (warp->fn_0x90()) {
                for (s32 i = 0; i < mInsectCount; i++) {
                    if (mLinks[i].isLinked()) {
                        static_cast<dAcInsectButterfly_c *>(mLinks[i].get())->setKillSignal();
                        mStateMgr.changeState(StateID_End);
                    }
                }
            }
        }
    } else if (isGoddessWallType() && mKillSignal) {
        for (s32 i = 0; i < mInsectCount; i++) {
            if (mLinks[i].isLinked()) {
                static_cast<dAcInsectButterfly_c *>(mLinks[i].get())->setKillSignal();
                mStateMgr.changeState(StateID_End);
            }
        }
    }
}
void dTgInsect_c::finalizeState_WaitForceEscape() {}
void dTgInsect_c::initializeState_End() {}
void dTgInsect_c::executeState_End() {}
void dTgInsect_c::finalizeState_End() {}

static const f32 FLOAT_ARRAY[] = {
    0.1f, -0.4f, 0.3f, 0.7f, -0.7f,
};

void dTgInsect_c::spawnAll() {
    for (s32 i = 0; i < mInsectCount; i++) {
        if (shouldSpawn()) {
            spawnInsect(i);
        }
    }
}

void dTgInsect_c::spawnInsect(s32 index) {
    f32 scaleX;
    f32 scaleY;
    f32 scaleXExt;
    f32 scaleYExt;
    f32 scaledScaleY;
    f32 scaledScaleX;
    f32 mult;
    f32 scaledTemp;
    scaleY = mScale.y;
    scaleYExt = SCALE_Y;
    scaledScaleY = scaleY * scaleYExt;
    mult = 0.85f;
    scaleXExt = SCALE_X;
    scaleX = mScale.x;
    scaledTemp = scaleXExt * scaleX;
    scaledScaleX = mult * scaledTemp;
    s16 angle1;
    mVec3_c pos;
    mVec3_c tmp;
    mAng3_c rot(0, 0, 0);
    bool spawnFound = false;
    s32 tries = 5;
    do {
        f32 scale = cM::rndF(scaledScaleX);
        angle1 = cM::rndFX(65536.0f);
        rot.y = cM::rndFX(65536.0f);
        mVec3_c v2 = mVec3_c::Ez * scale;
        cLib::offsetPos(pos, mPosition, angle1, v2);
        switch (getSubtype()) {
            case SUBTYPE_VOLCANIC_LADYBUG:
            case SUBTYPE_WOODLAND_RHINO_BEETLE:
            case SUBTYPE_SAND_CICADA:
            case SUBTYPE_SKY_STAG_BEETLE:
                pos = mPosition;
                if (getSubtype() == SUBTYPE_SAND_CICADA) {
                    tmp = mVec3_c::Ez;
                    tmp.rotY(mAng(mRotation.y + 0x4000));
                    f32 arrayResult = FLOAT_ARRAY[index % (s32)ARRAY_LENGTH(FLOAT_ARRAY)];
                    f32 scaledX = SCALE_X * mScale.x;
                    tmp.multScalar(arrayResult * scaledX);
                    pos = mPosition + tmp;
                    f32 scaledScale = SCALE_Y * mScale.y;
                    pos.y = mPosition.y + scaledScale * 0.3f;
                    pos.y += cM::rndF(scaledScale * 0.5f);
                    if (!checkValidSpawnLocation(pos, 0)) {
                        return;
                    }
                } else {
                    pos.y += cM::rndF(scaledScaleY);
                }
                spawnFound = true;
                break;
            case SUBTYPE_FARON_GRASSHOPPER:
            case SUBTYPE_LANAYRU_ANT:
            case SUBTYPE_SKYLOFT_MANTIS:
            case SUBTYPE_EDLIN_ROLLER:
                pos.y = 100 + mPosition.y;
                if (dBgS_ObjGndChk::CheckPos(pos)) {
                    spawnFound = true;
                }
                break;
            case SUBTYPE_GERUDO_DRAGONFLY:
            case SUBTYPE_BUTTERFLY:
            case SUBTYPE_STARRY_FIREFLY:
                spawnFound = true;
                break;
        }
    } while (!spawnFound && --tries);
    dAcBase_c *ref = nullptr;
    switch (getSubtype()) {
        case SUBTYPE_VOLCANIC_LADYBUG:
            ref = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_LADYBUG, 0, &pos, &rot, NULL, 0, 0x3f);
            break;
        case SUBTYPE_GERUDO_DRAGONFLY:
            ref = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_DRAGONFLY, 0, &pos, &rot, NULL, 0, 0x3f);
            break;
        case SUBTYPE_WOODLAND_RHINO_BEETLE:
            ref = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_BEETLE, 0, &pos, &rot, NULL, 0, 0x3f);
            break;
        case SUBTYPE_FARON_GRASSHOPPER:
            ref = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_GRASSHOPPER, 0, &pos, &rot, NULL, 0, 0x3f);
            break;
        case SUBTYPE_SAND_CICADA:
            ref = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_CICADA, 0, &pos, &rot, NULL, 0, 0x3f);
            break;
        case SUBTYPE_LANAYRU_ANT:
            ref = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_ANT, 0, &pos, &rot, NULL, 0, 0x3f);
            break;
        case SUBTYPE_SKY_STAG_BEETLE:
            ref = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_BEETLE, 0x10000000, &pos, &rot, NULL, 0, 0x3f);
            break;
        case SUBTYPE_BUTTERFLY:
            ref = dAcObjBase_c::createInsectActor(
                this, fProfile::INSECT_BUTTERFLY, (s16)mViewClipIdx, &pos, &rot, NULL, 0, 0x3f
            );
            break;
        case SUBTYPE_SKYLOFT_MANTIS:
            ref = dAcObjBase_c::createInsectActor(
                this, fProfile::INSECT_GRASSHOPPER, 0x10000000, &pos, &rot, NULL, 0, 0x3f
            );
            break;
        case SUBTYPE_EDLIN_ROLLER:
            ref = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_SCARAB, 0, &pos, &rot, NULL, 0, 0x3f);
            break;
        case SUBTYPE_STARRY_FIREFLY:
            ref = dAcObjBase_c::createInsectActor(this, fProfile::INSECT_FIREFLY, 0, &pos, &rot, NULL, 0, 0x3f);
            break;
    }
    if (ref != nullptr) {
        mLinks[index].link(ref);
        mInsectRespawnTimers[index] = -1;
    }
    return;
}

bool dTgInsect_c::shouldSpawn() {
    if (isSpawnSubtype(SPAWN_BUG_MINIGAME)) {
        return true;
    } else if (isSpawnSubtype(SPAWN_SKYLOFT_BUGKID_TREE)) {
        return !SceneflagManager::sInstance->checkBoolFlag(mRoomID, 0x3a);
    } else {
        if (isTrialGateType()) {
            return true;
        } else if (isGossipStoneType() || isGoddessWallType()) {
            return true;
        } else if ((isSubtype(SUBTYPE_STARRY_FIREFLY)) && !dAcItem_c::checkFlag(ITEM_STARRY_FIREFLY)) {
            return true;
        } else {
            if (cM::rndF(1.0f) >= 0.5f) {
                return false;
            } else {
                return true;
            }
        }
    }
}

bool dTgInsect_c::checkValidSpawnLocation(const mVec3_c &pos, bool updateRotation) {
    mVec3_c tmp = mVec3_c::Ez * (SCALE_X * mScale.x);
    tmp.rotY(mRotation.y);
    tmp += pos;
    dBgS_LinChk linChk;
    linChk.mBackFlag = true;
    linChk.mFrontFlag = false;
    linChk.Set(&pos, &tmp, nullptr);
    if (!dBgS::GetInstance()->LineCross(&linChk)) {
        mAng yRot = mRotation.y + 0x7FFF;
        if (updateRotation) {
            mRotation.y = yRot;
        }
        tmp = mVec3_c::Ez * (SCALE_X * mScale.x);
        tmp.rotY(yRot);
        tmp += pos;
        linChk.Set(&pos, &tmp, nullptr);
        if (!dBgS::GetInstance()->LineCross(&linChk)) {
            return false;
        }
    }
    return true;
}

void moreUnused() {
    static const s32 a = 0x3a;
}
