#include "Library/LiveActor/ActorActionFunction.h"

#include "Library/Action/ActorActionKeeper.h"
#include "Library/Base/StringUtil.h"
#include "Library/LiveActor/ActorAnimFunction.h"
#include "Library/LiveActor/HitReactionKeeper.h"
#include "Library/LiveActor/LiveActor.h"
#include "Library/Math/MathUtil.h"
#include "Library/Nerve/NerveAction.h"
#include "Library/Nerve/NerveActionCtrl.h"
#include "Library/Nerve/NerveKeeper.h"
#include "Library/Nerve/NerveUtil.h"
#include "Project/Action/ActionAnimCtrl.h"
#include "Project/Action/ActionSeCtrl.h"

namespace al {

void startAction(LiveActor* actor, const char* actionName) {
    if (!actor->getActorActionKeeper() || !actor->getActorActionKeeper()->startAction(actionName)) {
        tryStartSklAnimIfExist(actor, actionName);
        tryStartMtpAnimIfExist(actor, actionName);
        tryStartMclAnimIfExist(actor, actionName);
        tryStartMtsAnimIfExist(actor, actionName);
        tryStartVisAnimIfExistForAction(actor, actionName);
    }
}

s32 startActionAtRandomFrame(LiveActor* actor, const char* actionName) {
    startAction(actor, actionName);
    s32 frame = getRandom(0.0f, getSklAnimFrameMax(actor, 0));
    setSklAnimFrame(actor, frame, 0);
    return frame;
}

bool tryStartAction(LiveActor* actor, const char* actionName) {
    if (actor->getActorActionKeeper() && actor->getActorActionKeeper()->getAnimCtrl() &&
        actor->getActorActionKeeper()->getAnimCtrl()->isExistAction(actionName)) {
        actor->getActorActionKeeper()->startAction(actionName);
        return true;
    }

    bool sklStarted = tryStartSklAnimIfExist(actor, actionName);
    bool mtpStarted = tryStartMtpAnimIfExist(actor, actionName);
    bool mclStarted = tryStartMclAnimIfExist(actor, actionName);
    bool mtsStarted = tryStartMtsAnimIfExist(actor, actionName);
    bool visStarted = tryStartVisAnimIfExistForAction(actor, actionName);

    if (!sklStarted && !mtpStarted && !mclStarted && !mtsStarted && !visStarted)
        return false;
    if (actor->getActorActionKeeper())
        actor->getActorActionKeeper()->startAction(actionName);
    return true;
}

bool tryStartActionIfNotPlaying(LiveActor* actor, const char* actionName) {
    if (isActionPlaying(actor, actionName))
        return false;

    startAction(actor, actionName);
    return true;
}

bool isActionPlaying(const LiveActor* actor, const char* actionName) {
    const char* playingActionName = nullptr;
    ActorActionKeeper* keeper = actor->getActorActionKeeper();
    if (keeper && keeper->getAnimCtrl())
        playingActionName = keeper->getAnimCtrl()->getPlayingActionName();
    if (!playingActionName)
        playingActionName = alAnimFunction::getAllAnimName(actor);
    return playingActionName && isEqualString(playingActionName, actionName);
}

bool tryStartActionIfActionEnd(LiveActor* actor, const char* actionName) {
    if (isActionPlaying(actor, actionName) && !isActionEnd(actor))
        return false;

    startAction(actor, actionName);
    return true;
}

bool isActionEnd(const LiveActor* actor) {
    const ActorActionKeeper* keeper = actor->getActorActionKeeper();
    if (keeper && keeper->getAnimCtrl())
        return keeper->getAnimCtrl()->isActionEnd();
    return alAnimFunction::isAllAnimEnd(actor, -1);
}

bool isExistAction(const LiveActor* actor) {
    return isSklAnimExist(actor) || isMtpAnimExist(actor) || isMclAnimExist(actor) ||
           isMtsAnimExist(actor) || isVisAnimExist(actor);
}

bool isExistAction(const LiveActor* actor, const char* actionName) {
    const ActorActionKeeper* keeper = actor->getActorActionKeeper();
    if (keeper && keeper->getAnimCtrl() && keeper->getAnimCtrl()->isExistAction(actionName))
        return true;
    return isSklAnimExist(actor, actionName) || isMtpAnimExist(actor, actionName) ||
           isMclAnimExist(actor, actionName) || isMtsAnimExist(actor, actionName) ||
           isVisAnimExist(actor, actionName);
}

bool isActionOneTime(const LiveActor* actor) {
    return isActionOneTime(actor, getActionName(actor));
}

bool isActionOneTime(const LiveActor* actor, const char* actionName) {
    if (actor->getActorActionKeeper() && actor->getActorActionKeeper()->getAnimCtrl() &&
        actor->getActorActionKeeper()->getAnimCtrl()->isExistAction(actionName))
        return actor->getActorActionKeeper()->getAnimCtrl()->isActionOneTime(actionName);

    if (isSklAnimExist(actor, actionName))
        return isSklAnimOneTime(actor, actionName);
    if (isMtpAnimExist(actor, actionName))
        return isMtpAnimOneTime(actor, actionName);
    if (isMclAnimExist(actor, actionName))
        return isMclAnimOneTime(actor, actionName);
    if (isMtsAnimExist(actor, actionName))
        return isMtsAnimOneTime(actor, actionName);
    if (isVisAnimExist(actor, actionName))
        return isVisAnimOneTime(actor, actionName);
    return false;
}

const char* getActionName(const LiveActor* actor) {
    const ActorActionKeeper* keeper = actor->getActorActionKeeper();
    if (keeper && keeper->getAnimCtrl()) {
        const char* actionName = keeper->getAnimCtrl()->getPlayingActionName();
        if (actionName)
            return actionName;
    }
    return alAnimFunction::getAllAnimName(actor);
}

f32 getActionFrame(const LiveActor* actor) {
    const ActorActionKeeper* keeper = actor->getActorActionKeeper();
    if (keeper && keeper->getAnimCtrl())
        return keeper->getAnimCtrl()->getFrame();
    return alAnimFunction::getAllAnimFrame(actor, -1);
}

f32 getActionFrameMax(const LiveActor* actor) {
    return getActionFrameMax(actor, getActionName(actor));
}

f32 getActionFrameMax(const LiveActor* actor, const char* actionName) {
    const ActorActionKeeper* keeper = actor->getActorActionKeeper();
    if (keeper && keeper->getAnimCtrl())
        return keeper->getAnimCtrl()->getActionFrameMax(actionName);
    return alAnimFunction::getAllAnimFrameMax(actor, actionName, -1);
}

f32 getActionFrameRate(const LiveActor* actor) {
    const ActorActionKeeper* keeper = actor->getActorActionKeeper();
    if (keeper && keeper->getAnimCtrl())
        return keeper->getAnimCtrl()->getFrameRate();
    return alAnimFunction::getAllAnimFrameRate(actor, -1);
}

void setActionFrame(LiveActor* actor, f32 frame) {
    if (isSklAnimExist(actor))
        setSklAnimFrame(actor, frame, 0);
}

void setActionFrameRate(LiveActor* actor, f32 rate) {
    if (isSklAnimExist(actor))
        setSklAnimFrameRate(actor, rate, 0);
}

void stopAction(LiveActor* actor) {
    if (isSklAnimExist(actor))
        setSklAnimFrameRate(actor, 0.0f, 0);
    if (isMtpAnimExist(actor))
        setMtpAnimFrameRate(actor, 0.0f);
    if (isMclAnimExist(actor))
        setMclAnimFrameRate(actor, 0.0f);
    if (isMtsAnimExist(actor))
        setMtsAnimFrameRate(actor, 0.0f);
    if (isVisAnimExist(actor) && isVisAnimPlayingForAction(actor))
        setVisAnimFrameRateForAction(actor, 0.0f);

    if (actor->getActorActionKeeper() && actor->getActorActionKeeper()->getSeCtrl())
        actor->getActorActionKeeper()->getSeCtrl()->resetAction(true);
}

void restartAction(LiveActor* actor) {
    if (isSklAnimExist(actor))
        setSklAnimFrameRate(actor, 1.0f, 0);
    if (isMtpAnimExist(actor))
        setMtpAnimFrameRate(actor, 1.0f);
    if (isMclAnimExist(actor))
        setMclAnimFrameRate(actor, 1.0f);
    if (isMtsAnimExist(actor))
        setMtsAnimFrameRate(actor, 1.0f);
    if (isVisAnimExist(actor) && isVisAnimPlayingForAction(actor))
        setVisAnimFrameRateForAction(actor, 1.0f);

    if (actor->getActorActionKeeper() && actor->getActorActionKeeper()->getSeCtrl())
        actor->getActorActionKeeper()->getSeCtrl()->restartAction();
}

void copyAction(LiveActor* actor, const LiveActor* sourceActor) {
    startAction(actor, getActionName(sourceActor));
    if (isSklAnimExist(sourceActor) && isSklAnimExist(actor))
        copySklAnim(actor, sourceActor);
}

void startNerveAction(LiveActor* actor, const char* actionName) {
    if (actor->getActorActionKeeper())
        actor->getActorActionKeeper()->tryStartActionNoAnim(actionName);
    alNerveFunction::setNerveAction(actor, actionName);
}

void setNerveAtActionEnd(LiveActor* actor, const Nerve* nerve) {
    if (isActionEnd(actor))
        setNerve(actor, nerve);
}

void resetNerveActionForInit(LiveActor* actor) {
    NerveActionCtrl* actionCtrl = actor->getNerveKeeper()->getActionCtrl();
    const Nerve* currentNerve = actor->getNerveKeeper()->getCurrentNerve();
    if (actionCtrl->getNumActions() <= 0)
        return;

    s32 index = 0;
    ;
    for (index = 0; index < actionCtrl->getNumActions(); index++) {
        if (actionCtrl->getAction(index) == currentNerve) {
            startNerveAction(actor, actionCtrl->getAction(index)->getActionName());
            break;
        }
    }
}

void startHitReaction(const LiveActor* actor, const char* name) {
    if (actor->getHitReactionKeeper())
        actor->getHitReactionKeeper()->start(name, nullptr, nullptr, nullptr);
}

void startHitReactionHitEffect(const LiveActor* actor, const char* name, const HitSensor* other,
                               const HitSensor* self) {
    if (actor->getHitReactionKeeper())
        actor->getHitReactionKeeper()->start(name, nullptr, other, self);
}

void startHitReactionHitEffect(const LiveActor* actor, const char* name,
                               const sead::Vector3f& pos) {
    if (actor->getHitReactionKeeper())
        actor->getHitReactionKeeper()->start(name, &pos, nullptr, nullptr);
}

void startHitReactionHitEffect(const LiveActor* actor, const char* name,
                               const sead::Matrix34f* mtx) {
    if (actor->getHitReactionKeeper()) {
        sead::Vector3f pos = mtx->getBase(3);

        // copying around again to force more compact stp codegen
        sead::Vector3f x;
        x.set(pos);
        actor->getHitReactionKeeper()->start(name, &x, nullptr, nullptr);
    }
}

void startHitReactionBlowHit(const LiveActor* actor, const HitSensor* other,
                             const HitSensor* self) {
    startHitReactionHitEffect(actor, "吹き飛びヒット", other, self);
}

void startHitReactionBlowHit(const LiveActor* actor, const sead::Vector3f& pos) {
    startHitReactionHitEffect(actor, "吹き飛びヒット", pos);
}

void startHitReactionBlowHit(const LiveActor* actor) {
    startHitReaction(actor, "吹き飛びヒット");
}

void startHitReactionBlowHitDirect(const LiveActor* actor, const HitSensor* other,
                                   const HitSensor* self) {
    startHitReactionHitEffect(actor, "吹き飛びヒット[直接ヒット]", other, self);
}

void startHitReactionBlowHitDirect(const LiveActor* actor, const sead::Vector3f& pos) {
    startHitReactionHitEffect(actor, "吹き飛びヒット[直接ヒット]", pos);
}

void startHitReactionBlowHitDirect(const LiveActor* actor) {
    startHitReaction(actor, "吹き飛びヒット[直接ヒット]");
}

}  // namespace al
