#include "Library/Movement/WheelMovement.h"

#include "Library/LiveActor/ActorMovementFunction.h"
#include "Library/LiveActor/ActorPoseUtil.h"
#include "Library/LiveActor/ActorSensorUtil.h"
#include "Library/LiveActor/LiveActor.h"
#include "Library/Math/MathUtil.h"
#include "Library/Placement/PlacementFunction.h"
#include "Library/Rail/RailUtil.h"

namespace al {
WheelMovement::WheelMovement(LiveActor* actor, const ActorInitInfo& info)
    : NerveExecutor("車輪動作計算") {
    tryGetArg((s32*)&mRotateAxis, info, "RotateAxis");
    tryGetArg(&mRotateAccel, info, "RotateAccel");
    tryGetArg(&mMoveEndDegree, info, "MoveEndDegree");
    tryGetArg(&mNoRotateWidth, info, "NoRotateWidth");

    getQuat(&mInitialActorQuat, info);
    mActorQuat = mInitialActorQuat;

    sead::Vector3f localRotateAxis = sead::Vector3f::ex;
    calcQuatLocalAxis(&localRotateAxis, mInitialActorQuat, (s32)mRotateAxis);

    mMoveDir.setCross(localRotateAxis, sead::Vector3f::ey);
    if (isNearZero(mMoveDir))
        mMoveDir = sead::Vector3f::ez;

    if (isExistRail(actor)) {
        setSyncRailToNearestPos(actor);
        mIsOnRail = true;
        mIsRailPlusDir = isRailPlusDir(actor, mMoveDir);
        f32 railProgress = getRailCoord(actor) / getRailTotalLength(actor);
        mRailProgress = railProgress;
        mInitialRailProgress = railProgress;

        if (mIsRailPlusDir)
            mWheelAngle = railProgress * mMoveEndDegree;
        else
            mWheelAngle = -(railProgress * mMoveEndDegree);

        rotateQuatLocalDirDegree(&mActorQuat, mInitialActorQuat, (s32)mRotateAxis,
                                 wrapAngle(mWheelAngle));
    }
}

bool WheelMovement::receiveMsg(LiveActor* actor, const SensorMsg* message, HitSensor* other,
                               HitSensor* self) {
    if (isMsgFloorTouch(message)) {
        sead::Vector3f pos;
        if (isMySensor(self, actor))
            pos = getSensorPos(other);
        else
            pos = getActorTrans(self);

        f32 width = normalizeAbs(mMoveDir.dot(pos - getTrans(actor)), mNoRotateWidth,
                                 mNoRotateWidth + 50.0f);
        mRotateWidth += isMsgEnemyFloorTouch(message) ? width * 0.9f : width;
        return true;
    }

    return false;
}

void WheelMovement::update(LiveActor* actor) {
    updateRotate();
    updateActorPoseAndTrans(actor);
}

void WheelMovement::updateRotate() {
    mRotateWidth = sead::Mathf::clamp(mRotateWidth, -1.25f, 1.25f);
    mDeltaAngle = (mRotateWidth * mRotateAccel * 0.001f + mNextDeltaAngle) * 0.97f;
    mWheelAngle = mWheelAngle + mDeltaAngle;

    mNextDeltaAngle = mDeltaAngle;
    mIsInvertDirection = false;
    if (mIsOnRail) {
        if (mIsRailPlusDir) {
            if (mWheelAngle < 0.0f) {
                mWheelAngle = 0.0f;
                if (mNextDeltaAngle < 0.0f) {
                    if (mNextDeltaAngle < mRotateAccel * -0.001f)
                        mIsInvertDirection = true;
                    else
                        mNextDeltaAngle = 0.0f;
                }
            }
            if (mWheelAngle > mMoveEndDegree) {
                mWheelAngle = mMoveEndDegree;
                if (mNextDeltaAngle > 0.0f) {
                    if (mNextDeltaAngle > mRotateAccel * 0.001f)
                        mIsInvertDirection = true;
                    else
                        mNextDeltaAngle = 0.0f;
                }
            }
            mRailProgress = mWheelAngle / mMoveEndDegree;
        } else {
            if (mWheelAngle < -mMoveEndDegree) {
                mWheelAngle = -mMoveEndDegree;
                if (mNextDeltaAngle < 0.0f) {
                    if (mNextDeltaAngle < mRotateAccel * -0.001f)
                        mIsInvertDirection = true;
                    else
                        mNextDeltaAngle = 0.0f;
                }
            }
            if (mWheelAngle > 0.0f) {
                mWheelAngle = 0.0f;
                if (mNextDeltaAngle > 0.0f) {
                    if (mNextDeltaAngle > mRotateAccel * 0.001f)
                        mIsInvertDirection = true;
                    else
                        mNextDeltaAngle = 0.0f;
                }
            }
            mRailProgress = -mWheelAngle / mMoveEndDegree;
        }
        if (mIsInvertDirection)
            mNextDeltaAngle *= -0.2f;
    } else {
        mWheelAngle = wrapAngle(mWheelAngle);
    }
    mRotateWidth = 0.0f;
    rotateQuatLocalDirDegree(&mActorQuat, mInitialActorQuat, (s32)mRotateAxis,
                             wrapAngle(mWheelAngle));
}

void WheelMovement::updateActorPoseAndTrans(LiveActor* actor) {
    setQuat(actor, mActorQuat);
    if (isExistRail(actor))
        setSyncRailToCoord(actor, mRailProgress * getRailTotalLength(actor));
}

void WheelMovement::reset() {
    mWheelAngle = 0.0f;
    mNextDeltaAngle = 0.0f;
    mRotateWidth = 0.0f;
    mIsInvertDirection = false;
    mRailProgress = mInitialRailProgress;
    rotateQuatLocalDirDegree(&mActorQuat, mInitialActorQuat, (s32)mRotateAxis,
                             wrapAngle(mWheelAngle));
}

void WheelMovement::reset(LiveActor* actor) {
    reset();
    updateActorPoseAndTrans(actor);
    resetPosition(actor);
}

}  // namespace al
