#include "Library/Obj/PartsModel.h"

#include "Library/Base/StringUtil.h"
#include "Library/LiveActor/ActorClippingFunction.h"
#include "Library/LiveActor/ActorFlagFunction.h"
#include "Library/LiveActor/ActorInitFunction.h"
#include "Library/LiveActor/ActorInitUtil.h"
#include "Library/LiveActor/ActorModelFunction.h"
#include "Library/LiveActor/ActorPoseUtil.h"
#include "Library/LiveActor/ActorResourceFunction.h"
#include "Library/LiveActor/LiveActorFunction.h"
#include "Library/Math/MathUtil.h"
#include "Library/Matrix/MatrixUtil.h"
#include "Library/Yaml/ByamlUtil.h"

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

void PartsModel::endClipped() {
    LiveActor::endClipped();
    updatePose();
}

void PartsModel::calcAnim() {
    updatePose();
    LiveActor::calcAnim();
}

void PartsModel::attackSensor(HitSensor* self, HitSensor* other) {
    mParentModel->attackSensor(self, other);
}

bool PartsModel::receiveMsg(const SensorMsg* message, HitSensor* other, HitSensor* self) {
    return mParentModel->receiveMsg(message, other, self);
}

void PartsModel::initPartsDirect(LiveActor* parent, const ActorInitInfo& initInfo,
                                 const char* arcName, const sead::Matrix34f* jointMtx,
                                 const sead::Vector3f& localTrans,
                                 const sead::Vector3f& localRotate,
                                 const sead::Vector3f& localScale, bool useFollowMtxScale) {
    mParentModel = parent;
    mJointMtx = jointMtx;
    mIsUseFollowMtxScale = useFollowMtxScale;
    initChildActorWithArchiveNameNoPlacementInfo(this, initInfo, arcName, nullptr);
    invalidateClipping(this);
    registerSubActor(parent, this);
    makeActorAlive();
    mIsUseLocalPos = true;
    mLocalTrans = localTrans;
    mLocalRotate = localRotate;
    mLocalScale = localScale;
}

void PartsModel::initPartsSuffix(LiveActor* parent, const ActorInitInfo& initInfo,
                                 const char* arcName, const char* suffix,
                                 const sead::Matrix34f* jointMtx, bool useFollowMtxScale) {
    mParentModel = parent;
    mJointMtx = jointMtx;
    mIsUseFollowMtxScale = useFollowMtxScale;
    initChildActorWithArchiveNameNoPlacementInfo(this, initInfo, arcName, suffix);
    invalidateClipping(this);
    registerSubActor(parent, this);
    makeActorAlive();
}

void PartsModel::initPartsMtx(LiveActor* parent, const ActorInitInfo& initInfo, const char* arcName,
                              const sead::Matrix34f* jointMtx, bool useFollowMtxScale) {
    mParentModel = parent;
    mJointMtx = jointMtx;
    mIsUseFollowMtxScale = useFollowMtxScale;
    initChildActorWithArchiveNameNoPlacementInfo(this, initInfo, arcName, nullptr);
    invalidateClipping(this);
    registerSubActor(parent, this);
    makeActorAlive();
}

void PartsModel::initPartsFixFile(LiveActor* parent, const ActorInitInfo& initInfo,
                                  const char* arcName, const char* arcSuffix, const char* suffix) {
    initPartsFixFileNoRegister(parent, initInfo, arcName, arcSuffix, suffix);
    registerSubActor(parent, this);
}

void PartsModel::initPartsFixFileNoRegister(LiveActor* parent, const ActorInitInfo& initInfo,
                                            const char* arcName, const char* arcSuffix,
                                            const char* suffix) {
    mParentModel = parent;
    mJointMtx = parent->getBaseMtx();

    initChildActorWithArchiveNameNoPlacementInfo(this, initInfo, arcName, arcSuffix);
    invalidateClipping(this);

    StringTmp<128> initArcName;
    createFileNameBySuffix(&initArcName, "InitPartsFixInfo", suffix);

    if (!isExistModelResourceYaml(mParentModel, initArcName.cstr(), nullptr))
        return makeActorAlive();
    const u8* modelResByml = getModelResourceYaml(mParentModel, initArcName.cstr(), nullptr);
    ByamlIter modelResIter(modelResByml);

    const char* jointName = nullptr;
    modelResIter.tryGetStringByKey(&jointName, "JointName");

    if (jointName)
        mJointMtx = getJointMtxPtr(mParentModel, jointName);

    tryGetByamlV3f(&mLocalTrans, modelResIter, "LocalTrans");
    tryGetByamlV3f(&mLocalRotate, modelResIter, "LocalRotate");
    tryGetByamlV3f(&mLocalScale, modelResIter, "LocalScale");

    mIsUseLocalScale = tryGetByamlKeyBoolOrFalse(modelResIter, "UseLocalScale");

    if (!isNearZero(mLocalTrans) || !isNearZero(mLocalRotate) || mIsUseLocalScale)
        mIsUseLocalPos = true;

    mIsUseFollowMtxScale = tryGetByamlKeyBoolOrFalse(modelResIter, "UseFollowMtxScale");

    makeActorAlive();
}

void PartsModel::updatePose() {
    if (!mIsUpdate)
        return;

    if (!mIsUseLocalPos) {
        sead::Matrix34f baseMtx = *mJointMtx;
        if (mIsUseFollowMtxScale) {
            sead::Vector3f mtxScale;
            calcMtxScale(&mtxScale, baseMtx);
            const sead::Vector3f& scale = sead::Vector3f::ones;
            mtxScale.x *= scale.x;
            mtxScale.y *= scale.y;
            mtxScale.z *= scale.z;
            setScale(this, mtxScale);
        }
        normalize(&baseMtx);
        updatePoseMtx(this, &baseMtx);
        return;
    }

    sead::Matrix34f rotationMatrix;
    sead::Vector3f rotate(sead::Mathf::deg2rad(mLocalRotate.x),
                          sead::Mathf::deg2rad(mLocalRotate.y),
                          sead::Mathf::deg2rad(mLocalRotate.z));
    rotationMatrix.makeR(rotate);

    sead::Matrix34f translationMatrix;
    translationMatrix.makeRT({0.0f, 0.0f, 0.0f}, mLocalTrans);

    sead::Matrix34f poseMatrix = rotationMatrix * translationMatrix;

    sead::Matrix34f baseMtx = *mJointMtx;

    if (mIsUseFollowMtxScale) {
        sead::Matrix34f rotBaseMtx = baseMtx * rotationMatrix;

        sead::Vector3f mtxScale = {0.0f, 0.0f, 0.0f};
        calcMtxScale(&mtxScale, rotBaseMtx);

        poseMatrix.m[0][3] *= mtxScale.x;
        poseMatrix.m[1][3] *= mtxScale.y;
        poseMatrix.m[2][3] *= mtxScale.z;

        if (mIsUseLocalScale) {
            mtxScale.x *= mLocalScale.x;
            mtxScale.y *= mLocalScale.y;
            mtxScale.z *= mLocalScale.z;
        }

        setScale(this, mtxScale);
    } else if (mIsUseLocalScale) {
        setScale(this, mLocalScale);
    }

    normalize(&baseMtx);
    baseMtx = baseMtx * poseMatrix;
    updatePoseMtx(this, &baseMtx);
}

void PartsModel::offSyncAppearAndHide() {
    offSyncAppearSubActor(mParentModel, this);
    offSyncHideSubActor(mParentModel, this);
}

void PartsModel::onSyncAppearAndHide() {
    onSyncHideSubActor(mParentModel, this);

    if (isHideModel(mParentModel))
        hideModelIfShow(this);
    else
        showModelIfHide(this);

    onSyncAppearSubActor(mParentModel, this);

    if (isDead(mParentModel) && isAlive(this))
        makeActorDead();
    else if (isAlive(mParentModel) && isDead(this))
        makeActorAlive();
}
}  // namespace al
