#include "Library/MapObj/GateMapParts.h"

#include "Library/Audio/System/SimpleAudioUser.h"
#include "Library/Base/StringUtil.h"
#include "Library/LiveActor/ActorActionFunction.h"
#include "Library/LiveActor/ActorClippingFunction.h"
#include "Library/LiveActor/ActorInitFunction.h"
#include "Library/LiveActor/ActorInitUtil.h"
#include "Library/LiveActor/ActorPoseUtil.h"
#include "Library/Math/MathUtil.h"
#include "Library/Nerve/NerveSetupUtil.h"
#include "Library/Nerve/NerveUtil.h"
#include "Library/Placement/PlacementFunction.h"
#include "Library/Se/SeFunction.h"
#include "Library/Stage/StageSwitchUtil.h"
#include "Library/Thread/FunctorV0M.h"

namespace {
using namespace al;

NERVE_ACTION_IMPL(GateMapParts, Wait)
NERVE_ACTION_IMPL(GateMapParts, Open)
NERVE_ACTION_IMPL(GateMapParts, Bound)
NERVE_ACTION_IMPL(GateMapParts, End)

NERVE_ACTIONS_MAKE_STRUCT(GateMapParts, Wait, Open, Bound, End)
}  // namespace

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

void GateMapParts::init(const ActorInitInfo& info) {
    using GateMapPartsFunctor = FunctorV0M<GateMapParts*, void (GateMapParts::*)()>;

    initNerveAction(this, "Wait", &NrvGateMapParts.collector, 0);
    initMapPartsActor(this, info, nullptr);
    tryGetQuatPtr(this);

    mTrans = getTrans(this);
    mQuat = getQuat(this);

    tryGetLinksTrans(&mMoveNextTrans, info, "MoveNext");
    tryGetLinksQuat(&mMoveNextQuat, info, "MoveNext");

    tryGetArg(&mOpenTime, info, "OpenTime");
    tryGetArg(&mBoundRate, info, "BoundRate");
    tryGetArg(&mHitReactionCount, info, "HitReactionCount");

    listenStageSwitchOnStart(this, GateMapPartsFunctor(this, &GateMapParts::start));

    bool isPlaySuccessSe = false;
    tryGetArg(&isPlaySuccessSe, info, "IsPlaySuccessSe");

    if (isPlaySuccessSe)
        mSuccessSeObj = new SimpleAudioUser("SuccessSeObj", info);

    makeActorAlive();
}

void GateMapParts::start() {
    if (!isNerve(this, NrvGateMapParts.Wait.data()))
        return;

    appearAndSetStart();
}

void GateMapParts::appearAndSetStart() {
    invalidateClipping(this);
    startNerveAction(this, "Open");
}

void GateMapParts::exeWait() {
    if (isFirstStep(this))
        validateClipping(this);
}

void GateMapParts::exeOpen() {
    updatePose(calcNerveSquareInRate(this, mOpenTime - 1));

    if (isGreaterEqualStep(this, mOpenTime - 1)) {
        mCurrentBoundRate = mBoundRate;
        mCurrentBoundSteps = (s32)(mBoundRate * (f32)mOpenTime * 2);
        mHitReactionCurrent = 0;

        if (mMaxHitReactions > mHitReactionCurrent && mCurrentBoundSteps > 1) {
            startNerveAction(this, "Bound");

            return;
        }

        if (mSuccessSeObj)
            startSe(mSuccessSeObj, "Riddle");

        startNerveAction(this, "End");

        if (mHitReactionCount < 2)
            startHitReaction(this, "バウンド1回目");
    }
}

void GateMapParts::updatePose(f32 rate) {
    rate = sead::Mathf::clamp(rate, 0.0f, 1.0f);

    lerpVec(getTransPtr(this), mTrans, mMoveNextTrans, rate);
    slerpQuat(getQuatPtr(this), mQuat, mMoveNextQuat, rate);
}

void GateMapParts::exeBound() {
    if (isFirstStep(this)) {
        if (mHitReactionCurrent++ < mHitReactionCount)
            startHitReaction(this, StringTmp<32>("バウンド%d回目", mHitReactionCurrent).cstr());

        tryStartSeWithParam(this, "BoundStart", (f32)(mMaxHitReactions - mHitReactionCurrent), "");
    }

    f32 rate = calcNerveRate(this, mCurrentBoundSteps - 1);
    rate = sead::Mathf::pow(mCurrentBoundRate * (rate * 2 - 1.0f), 2);
    rate += (1.0f - sead::Mathf::pow(mCurrentBoundRate, 2));

    updatePose(rate);

    if (isGreaterEqualStep(this, mCurrentBoundSteps - 1)) {
        mCurrentBoundRate *= mBoundRate;
        mCurrentBoundSteps = (s32)(mBoundRate * (f32)mCurrentBoundSteps);

        if (mMaxHitReactions > mHitReactionCurrent && mCurrentBoundSteps > 1) {
            startNerveAction(this, "Bound");

            return;
        }

        if (mSuccessSeObj)
            startSe(mSuccessSeObj, "Riddle");

        startNerveAction(this, "End");
    }
}

void GateMapParts::exeEnd() {
    if (isFirstStep(this)) {
        validateClipping(this);
        updatePose(1.0f);
    }
}
}  // namespace al
