#include "Library/MapObj/RollingCubeMapParts.h"

#include "Library/Base/StringUtil.h"
#include "Library/Effect/EffectSystemInfo.h"
#include "Library/LiveActor/ActorActionFunction.h"
#include "Library/LiveActor/ActorClippingFunction.h"
#include "Library/LiveActor/ActorInitFunction.h"
#include "Library/LiveActor/ActorInitUtil.h"
#include "Library/LiveActor/ActorMovementFunction.h"
#include "Library/LiveActor/ActorPoseUtil.h"
#include "Library/LiveActor/ActorResourceFunction.h"
#include "Library/LiveActor/ActorSensorUtil.h"
#include "Library/Math/MathUtil.h"
#include "Library/Nerve/NerveSetupUtil.h"
#include "Library/Nerve/NerveUtil.h"
#include "Library/Obj/PartsModel.h"
#include "Library/Placement/PlacementFunction.h"
#include "Library/Yaml/ByamlIter.h"
#include "Library/Yaml/ByamlUtil.h"
#include "Project/Joint/RollingCubePoseKeeper.h"
#include "Project/Joint/RollingCubePoseKeeperUtil.h"

namespace {
using namespace al;

NERVE_ACTION_IMPL(RollingCubeMapParts, Wait)
NERVE_ACTION_IMPL(RollingCubeMapParts, Start)
NERVE_ACTION_IMPL(RollingCubeMapParts, Rotate)
NERVE_ACTION_IMPL(RollingCubeMapParts, Fall)
NERVE_ACTION_IMPL_(RollingCubeMapParts, SlideX, Slide)
NERVE_ACTION_IMPL_(RollingCubeMapParts, SlideY, Slide)
NERVE_ACTION_IMPL_(RollingCubeMapParts, SlideZ, Slide)
NERVE_ACTION_IMPL_(RollingCubeMapParts, LandX, Land)
NERVE_ACTION_IMPL_(RollingCubeMapParts, LandY, Land)
NERVE_ACTION_IMPL_(RollingCubeMapParts, LandZ, Land)
NERVE_ACTION_IMPL_(RollingCubeMapParts, FallLandX, FallLand)
NERVE_ACTION_IMPL_(RollingCubeMapParts, FallLandY, FallLand)
NERVE_ACTION_IMPL_(RollingCubeMapParts, FallLandZ, FallLand)
NERVE_ACTION_IMPL(RollingCubeMapParts, Stop)

NERVE_ACTIONS_MAKE_STRUCT(RollingCubeMapParts, Wait, Start, Rotate, Fall, SlideX, SlideY, SlideZ,
                          LandX, LandY, LandZ, FallLandX, FallLandY, FallLandZ, Stop)
}  // namespace

namespace al {
RollingCubeMapParts::RollingCubeMapParts(const char* name) : LiveActor(name) {}

void RollingCubeMapParts::init(const ActorInitInfo& info) {
    initNerveAction(this, "Wait", &NrvRollingCubeMapParts.collector, 0);
    initMapPartsActor(this, info, nullptr);
    tryGetQuatPtr(this);

    mInitialPoseQuat = getQuat(this);
    mInitialPoseTrans = getTrans(this);

    bool isUseMoveLimit = false;
    tryGetArg(&isUseMoveLimit, info, "IsUseMoveLimit");
    if (isUseMoveLimit) {
        mMoveLimitMtx = new sead::Matrix34f();
        mMoveLimitMtx->makeQT(mInitialPoseQuat, mInitialPoseTrans);

        mMoveLimitPartsModel = new PartsModel("");
        StringTmp<256> model;
        StringTmp<256> archive;
        makeMapPartsModelName(&model, &archive, info);
        mMoveLimitPartsModel->initPartsSuffix(this, info, model.cstr(), "MoveLimit", mMoveLimitMtx,
                                              false);
    }

    sead::BoundBox3f boundBox;
    if (isExistModelResourceYaml(this, "BoxInfo", nullptr) &&
        tryGetByamlBox3f(&boundBox, ByamlIter(getModelResourceYaml(this, "BoxInfo", nullptr))))
        mRollingCubePoseKeeper = createRollingCubePoseKeeper(boundBox, info);
    else
        mRollingCubePoseKeeper = createRollingCubePoseKeeper(this, info);

    bool isFloorTouchStart = true;
    tryGetArg(&isFloorTouchStart, info, "IsFloorTouchStart");
    if (!isFloorTouchStart)
        startNerveAction(this, "Start");

    trySetEffectNamedMtxPtr(this, "Land", &mLandEffectMtx);
    initMaterialCode(this, info);

    f32 clippingRadius = 0.0f;
    calcRollingCubeClippingInfo(&mClippingTrans, &clippingRadius, mRollingCubePoseKeeper, 0.0f);
    setClippingInfo(this, clippingRadius, &mClippingTrans);

    tryListenStageSwitchKill(this);
    makeActorAlive();
}

void RollingCubeMapParts::kill() {
    startHitReaction(this, "消滅");

    LiveActor::kill();
}

bool RollingCubeMapParts::receiveMsg(const SensorMsg* message, HitSensor* other, HitSensor* self) {
    if (isMsgPlayerFloorTouch(message) && isNerve(this, NrvRollingCubeMapParts.Wait.data())) {
        startNerveAction(this, "Start");

        return true;
    }

    return false;
}

void RollingCubeMapParts::control() {
    if (mMoveLimitMtx)
        mMoveLimitMtx->makeQT(mInitialPoseQuat, getTrans(this));

    calcMtxLandEffect(&mLandEffectMtx, mRollingCubePoseKeeper, getQuat(this), getTrans(this));
}

void RollingCubeMapParts::appearAndSetStart() {
    mRollingCubePoseKeeper->setStart();
    setQuat(this, mInitialPoseQuat);
    setTrans(this, mInitialPoseTrans);
    resetPosition(this);
    setNerveNextMovement(isNextFallKey());

    appear();
}

void RollingCubeMapParts::setNerveNextMovement(bool isNextFallKey) {
    if (isMovementCurrentKeyRotate(mRollingCubePoseKeeper)) {
        startNerveAction(this, "Rotate");

        return;
    }

    if (isNextFallKey) {
        startNerveAction(this, "Fall");

        return;
    }

    Axis axis = (Axis)sead::Mathi::abs(
        (s32)calcNearVecFromAxis3(nullptr, sead::Vector3f::ey, getQuat(this)));
    switch (axis) {
    default:
        return;
    case Axis::X:
        startNerveAction(this, "SlideX");

        return;
    case Axis::Y:
        startNerveAction(this, "SlideY");

        return;
    case Axis::Z:
        startNerveAction(this, "SlideZ");

        return;
    }
}

bool RollingCubeMapParts::isNextFallKey() const {
    bool isNextFall = false;
    tryGetArg(&isNextFall, getCurrentKeyPlacementInfo(mRollingCubePoseKeeper), "isNextFall");

    return isNextFall;
}

void RollingCubeMapParts::exeWait() {}

void RollingCubeMapParts::exeStart() {
    if (isGreaterEqualStep(this, 40))
        setNerveNextMovement(isNextFallKey());
}

void RollingCubeMapParts::exeRotate() {
    if (isFirstStep(this)) {
        fittingToCurrentKeyBoundingBox(getQuatPtr(this), getTransPtr(this), mRollingCubePoseKeeper);
        mCurrentPoseQuat = getQuat(this);
        mCurrentPoseTrans = getTrans(this);
        mMovementTime = getMovementTime() - 1;
    }

    calcCurrentKeyQT(getQuatPtr(this), getTransPtr(this), mRollingCubePoseKeeper, mCurrentPoseQuat,
                     mCurrentPoseTrans, calcNerveSquareInRate(this, mMovementTime));

    if (isGreaterEqualStep(this, mMovementTime)) {
        if (nextRollingCubeKey(mRollingCubePoseKeeper)) {
            if (!isNextFallKey())
                setNerveNextLand();
            else if (isMovementCurrentKeyRotate(mRollingCubePoseKeeper))
                startNerveAction(this, "Rotate");
            else
                startNerveAction(this, "Fall");
        } else {
            startNerveAction(this, "Stop");
        }
    }
}

s32 RollingCubeMapParts::getMovementTime() const {
    s32 movementTime = 40;
    tryGetArg(&movementTime, getCurrentKeyPlacementInfo(mRollingCubePoseKeeper), "MovementTime");

    return movementTime;
}

void RollingCubeMapParts::setNerveNextLand() {
    Axis axis = (Axis)sead::Mathi::abs(
        (s32)calcNearVecFromAxis3(nullptr, sead::Vector3f::ey, getQuat(this)));
    switch (axis) {
    default:
        return;
    case Axis::X:
        startNerveAction(this, "LandX");

        return;
    case Axis::Y:
        startNerveAction(this, "LandY");

        return;
    case Axis::Z:
        startNerveAction(this, "LandZ");

        return;
    }
}

void RollingCubeMapParts::exeSlide() {
    if (updateSlide()) {
        if (nextRollingCubeKey(mRollingCubePoseKeeper)) {
            if (!isNextFallKey())
                setNerveNextLand();
            else if (isMovementCurrentKeyRotate(mRollingCubePoseKeeper))
                startNerveAction(this, "Rotate");
            else
                startNerveAction(this, "Fall");
        } else {
            startNerveAction(this, "Stop");
        }
    }
}

bool RollingCubeMapParts::updateSlide() {
    if (isFirstStep(this)) {
        fittingToCurrentKeyBoundingBox(getQuatPtr(this), getTransPtr(this), mRollingCubePoseKeeper);
        mCurrentPoseQuat = getQuat(this);
        mCurrentPoseTrans = getTrans(this);
        mMovementTime = getMovementTime() - 1;
    }

    calcCurrentKeyQT(getQuatPtr(this), getTransPtr(this), mRollingCubePoseKeeper, mCurrentPoseQuat,
                     mCurrentPoseTrans, calcNerveSquareInRate(this, mMovementTime));

    return isGreaterEqualStep(this, mMovementTime);
}

void RollingCubeMapParts::exeFall() {
    if (updateSlide()) {
        if (nextRollingCubeKey(mRollingCubePoseKeeper)) {
            if (!isNextFallKey())
                setNerveNextFallLand();
            else if (isMovementCurrentKeyRotate(mRollingCubePoseKeeper))
                startNerveAction(this, "Rotate");
            else
                startNerveAction(this, "Fall");
        } else {
            startNerveAction(this, "Stop");
        }
    }
}

void RollingCubeMapParts::setNerveNextFallLand() {
    Axis axis = (Axis)sead::Mathi::abs(
        (s32)calcNearVecFromAxis3(nullptr, sead::Vector3f::ey, getQuat(this)));
    switch (axis) {
    default:
        return;
    case Axis::X:
        startNerveAction(this, "FallLandX");

        return;
    case Axis::Y:
        startNerveAction(this, "FallLandY");

        return;
    case Axis::Z:
        startNerveAction(this, "FallLandZ");

        return;
    }
}

void RollingCubeMapParts::exeLand() {
    if (isGreaterEqualStep(this, getLandTime() - 1))
        setNerveNextMovement(false);
}

s32 RollingCubeMapParts::getLandTime() const {
    s32 landTime = 40;
    tryGetArg(&landTime, getCurrentKeyPlacementInfo(mRollingCubePoseKeeper), "LandTime");

    return landTime;
}

void RollingCubeMapParts::exeFallLand() {
    if (isGreaterEqualStep(this, getLandTime()))
        setNerveNextMovement(false);
}

void RollingCubeMapParts::exeStop() {
    if (mIsStoppable) {
        if (isFirstStep(this))
            startHitReaction(this, "消滅");

        if (isStep(this, 1)) {
            setQuat(this, mInitialPoseQuat);
            setTrans(this, mInitialPoseTrans);
            resetPosition(this);

            makeActorDead();
        }
    }
}

bool RollingCubeMapParts::isStop() const {
    return isNerve(this, NrvRollingCubeMapParts.Stop.data());
}
}  // namespace al
