#include "Library/Movement/EnemyStateBlowDown.h"

#include "Library/LiveActor/ActorActionFunction.h"
#include "Library/LiveActor/ActorClippingFunction.h"
#include "Library/LiveActor/ActorCollisionFunction.h"
#include "Library/LiveActor/ActorFlagFunction.h"
#include "Library/LiveActor/ActorMovementFunction.h"
#include "Library/LiveActor/ActorPoseUtil.h"
#include "Library/LiveActor/ActorSensorUtil.h"
#include "Library/Math/MathUtil.h"

const al::EnemyStateBlowDownParam sEnemyStateBlowDownParam = al::EnemyStateBlowDownParam();

namespace al {
EnemyStateBlowDown::EnemyStateBlowDown(LiveActor* actor, const EnemyStateBlowDownParam* param,
                                       const char* name)
    : ActorStateBase(name, actor), mParam(param) {
    if (!mParam)
        mParam = &sEnemyStateBlowDownParam;
}

void EnemyStateBlowDown::start(const HitSensor* other) {
    sead::Vector3f dir = getSensorPos(other) - getTrans(mActor);

    verticalizeVec(&dir, getGravity(mActor), dir);

    if (!tryNormalizeOrZero(&dir))
        calcFrontDir(&dir, mActor);

    start(-dir);
}

void EnemyStateBlowDown::start(const sead::Vector3f& dir) {
    if (mParam->isFaceAwayFromActor)
        faceToDirection(mActor, -dir);

    auto* actor = mActor;
    sead::Vector3f direction = dir * mParam->velocityStrength;
    sead::Vector3f gravity = getGravity(actor);
    sead::Vector3f velocity = gravity * mParam->gravityStrength;
    setVelocity(actor, direction - velocity);
}

void EnemyStateBlowDown::start(const HitSensor* other, const HitSensor* self) {
    sead::Vector3f dir = getSensorPos(other) - getSensorPos(self);

    verticalizeVec(&dir, getGravity(mActor), dir);

    if (!tryNormalizeOrZero(&dir))
        calcFrontDir(&dir, mActor);

    start(-dir);
}

void EnemyStateBlowDown::start(const LiveActor* attacker) {
    sead::Vector3f dir;
    calcFrontDir(&dir, attacker);

    start(-dir);
}

void EnemyStateBlowDown::appear() {
    NerveStateBase::appear();
    if (isInvalidClipping(mActor))
        mIsInvalidClipping = true;
    else {
        invalidateClipping(mActor);
        mIsInvalidClipping = false;
    }
    mBlowDownTimer = 0;
}

void EnemyStateBlowDown::kill() {
    NerveStateBase::kill();
    if (!mIsInvalidClipping)
        validateClipping(mActor);
}

void EnemyStateBlowDown::control() {
    if (mBlowDownTimer == 0) {
        if (mParam->actionName)
            startAction(mActor, mParam->actionName);
        if (isExistActorCollider(mActor))
            onCollide(mActor);
    }
    if ((mParam->blowDownLength <= mBlowDownTimer) ||
        (mParam->actionName && isActionOneTime(mActor, mParam->actionName) &&
         isActionEnd(mActor)) ||
        (isExistActorCollider(mActor) && isOnGround(mActor, 0))) {
        kill();
        return;
    }
    addVelocityToGravity(mActor, mParam->velocityMultiplier);
    scaleVelocity(mActor, mParam->velocityScale);
    mBlowDownTimer++;
}

EnemyStateBlowDownParam::EnemyStateBlowDownParam() = default;

EnemyStateBlowDownParam::EnemyStateBlowDownParam(const char* actionName) : actionName(actionName) {}

EnemyStateBlowDownParam::EnemyStateBlowDownParam(const char* actionName, f32 velocityStrength,
                                                 f32 gravityStrength, f32 velocityMultiplier,
                                                 f32 velocityScale, s32 blowDownLength,
                                                 bool isFaceAwayFromActor)
    : actionName(actionName), velocityStrength(velocityStrength), gravityStrength(gravityStrength),
      velocityMultiplier(velocityMultiplier), velocityScale(velocityScale),
      blowDownLength(blowDownLength), isFaceAwayFromActor(isFaceAwayFromActor) {}
}  // namespace al
