#include "Library/LiveActor/ActorPoseUtil.h"

#include "Library/LiveActor/ActorAnimFunction.h"
#include "Library/LiveActor/ActorPoseKeeper.h"
#include "Library/LiveActor/LiveActor.h"
#include "Library/Math/MathUtil.h"
#include "Library/Matrix/MatrixUtil.h"

namespace al {
class LiveActor;

void initActorPoseT(LiveActor* actor, const sead::Vector3f& trans) {
    actor->initPoseKeeper(new ActorPoseKeeperTRSV());
    setTrans(actor, trans);
}

void setTrans(LiveActor* actor, const sead::Vector3f& trans) {
    *getTransPtr(actor) = trans;
}

void initActorPoseTR(LiveActor* actor, const sead::Vector3f& trans, const sead::Vector3f& rotate) {
    actor->initPoseKeeper(new ActorPoseKeeperTRSV());
    setTrans(actor, trans);
    setRotate(actor, rotate);
}

void setRotate(LiveActor* actor, const sead::Vector3f& rotate) {
    getRotatePtr(actor)->set(rotate);
}

void makeMtxSRT(sead::Matrix34f* mtx, const LiveActor* actor) {
    ActorPoseKeeperBase* poseKeeper = actor->getPoseKeeper();
    poseKeeper->calcBaseMtx(mtx);
    preScaleMtx(mtx, poseKeeper->getScale());
}

void makeMtxRT(sead::Matrix34f* mtx, const LiveActor* actor) {
    actor->getPoseKeeper()->calcBaseMtx(mtx);
}

void makeMtxR(sead::Matrix34f* mtx, const LiveActor* actor) {
    makeMtxRT(mtx, actor);
    mtx->setBase(3, sead::Vector3f::zero);
}

void calcAnimFrontGravityPos(LiveActor* actor, const sead::Vector3f& front) {
    sead::Vector3f up = -getGravity(actor);
    if (actor->getBaseMtx()) {
        const sead::Matrix34f* mtx = actor->getBaseMtx();
        sead::Vector3f side;
        side.setCross(front, up);
        if (isNearZero(side))
            up.setCross(front, mtx->getBase(0));
    }

    sead::Matrix34f mtx;
    makeMtxFrontUpPos(&mtx, front, up, getTrans(actor));
    setBaseMtxAndCalcAnim(actor, mtx, getScale(actor));
}

const sead::Vector3f& getGravity(const LiveActor* actor) {
    return actor->getPoseKeeper()->getGravity();
}

const sead::Vector3f& getTrans(const LiveActor* actor) {
    return actor->getPoseKeeper()->getTrans();
}

const sead::Vector3f& getScale(const LiveActor* actor) {
    return actor->getPoseKeeper()->getScale();
}

void copyPose(LiveActor* actor, const LiveActor* target) {
    actor->getPoseKeeper()->copyPose(target->getPoseKeeper());
}

void updatePoseTrans(LiveActor* actor, const sead::Vector3f& trans) {
    actor->getPoseKeeper()->updatePoseTrans(trans);
}

void updatePoseRotate(LiveActor* actor, const sead::Vector3f& rotate) {
    actor->getPoseKeeper()->updatePoseRotate(rotate);
}

void updatePoseQuat(LiveActor* actor, const sead::Quatf& quat) {
    actor->getPoseKeeper()->updatePoseQuat(quat);
}

void updatePoseMtx(LiveActor* actor, const sead::Matrix34f* mtx) {
    actor->getPoseKeeper()->updatePoseMtx(mtx);
}

void calcSideDir(sead::Vector3f* side, const LiveActor* actor) {
    sead::Matrix34f mtx;
    makeMtxRT(&mtx, actor);
    mtx.getBase(*side, 0);
}

void calcLeftDir(sead::Vector3f* left, const LiveActor* actor) {
    return calcSideDir(left, actor);
}

void calcRightDir(sead::Vector3f* right, const LiveActor* actor) {
    sead::Vector3f left;
    calcLeftDir(&left, actor);
    *right = left;
    right->negate();
}

void calcUpDir(sead::Vector3f* up, const LiveActor* actor) {
    sead::Matrix34f mtx;
    makeMtxRT(&mtx, actor);
    mtx.getBase(*up, 1);
}

void calcDownDir(sead::Vector3f* down, const LiveActor* actor) {
    sead::Vector3f up;
    calcUpDir(&up, actor);
    *down = up;
    down->negate();
}

void calcFrontDir(sead::Vector3f* front, const LiveActor* actor) {
    sead::Matrix34f mtx;
    makeMtxRT(&mtx, actor);
    mtx.getBase(*front, 2);
}

void calcBackDir(sead::Vector3f* back, const LiveActor* actor) {
    sead::Vector3f front;
    calcFrontDir(&front, actor);
    *back = front;
    back->negate();
}

void calcPoseDir(sead::Vector3f* side, sead::Vector3f* up, sead::Vector3f* front,
                 const LiveActor* actor) {
    sead::Matrix34f mtx;
    makeMtxRT(&mtx, actor);
    if (side)
        mtx.getBase(*side, 0);
    if (up)
        mtx.getBase(*up, 1);
    if (front)
        mtx.getBase(*front, 2);
}

void calcQuat(sead::Quatf* quat, const LiveActor* actor) {
    sead::Matrix34f mtx;
    makeMtxRT(&mtx, actor);
    mtx.toQuat(*quat);
}

f32 calcDistanceSignLocalXDir(const LiveActor* actor, const sead::Vector3f& pos) {
    sead::Vector3f left;
    calcLeftDir(&left, actor);
    return left.dot(pos - getTrans(actor));
}

f32 calcDistanceSignLocalYDir(const LiveActor* actor, const sead::Vector3f& pos) {
    sead::Vector3f up;
    calcUpDir(&up, actor);
    return up.dot(pos - getTrans(actor));
}

f32 calcDistanceSignLocalZDir(const LiveActor* actor, const sead::Vector3f& pos) {
    sead::Vector3f front;
    calcFrontDir(&front, actor);
    return front.dot(pos - getTrans(actor));
}

f32 calcDistanceLocalXDir(const LiveActor* actor, const sead::Vector3f& pos) {
    return sead::Mathf::abs(calcDistanceSignLocalXDir(actor, pos));
}

f32 calcDistanceLocalYDir(const LiveActor* actor, const sead::Vector3f& pos) {
    return sead::Mathf::abs(calcDistanceSignLocalYDir(actor, pos));
}

f32 calcDistanceLocalZDir(const LiveActor* actor, const sead::Vector3f& pos) {
    return sead::Mathf::abs(calcDistanceSignLocalZDir(actor, pos));
}

sead::Vector3f* getTransPtr(LiveActor* actor) {
    return actor->getPoseKeeper()->getTransPtr();
}

void setTrans(LiveActor* actor, f32 x, f32 y, f32 z) {
    setTrans(actor, {x, y, z});
}

void setTransX(LiveActor* actor, f32 x) {
    getTransPtr(actor)->x = x;
}

void setTransY(LiveActor* actor, f32 y) {
    getTransPtr(actor)->y = y;
}

void setTransZ(LiveActor* actor, f32 z) {
    getTransPtr(actor)->z = z;
}

const sead::Vector3f& getRotate(const LiveActor* actor) {
    return actor->getPoseKeeper()->getRotate();
}

sead::Vector3f* getRotatePtr(LiveActor* actor) {
    return actor->getPoseKeeper()->getRotatePtr();
}

void setRotate(LiveActor* actor, f32 x, f32 y, f32 z) {
    setRotate(actor, {x, y, z});
}

void setRotateX(LiveActor* actor, f32 x) {
    getRotatePtr(actor)->x = x;
}

void setRotateY(LiveActor* actor, f32 y) {
    getRotatePtr(actor)->y = y;
}

void setRotateZ(LiveActor* actor, f32 z) {
    getRotatePtr(actor)->z = z;
}

sead::Vector3f* getScalePtr(LiveActor* actor) {
    return tryGetScalePtr(actor);
}

sead::Vector3f* tryGetScalePtr(LiveActor* actor) {
    return actor->getPoseKeeper()->getScalePtr();
}

f32 getScaleX(const LiveActor* actor) {
    return getScale(actor).x;
}

f32 getScaleY(const LiveActor* actor) {
    return getScale(actor).y;
}

f32 getScaleZ(const LiveActor* actor) {
    return getScale(actor).z;
}

void setScale(LiveActor* actor, const sead::Vector3f& scale) {
    *getScalePtr(actor) = scale;
}

void setScale(LiveActor* actor, f32 x, f32 y, f32 z) {
    setScale(actor, {x, y, z});
}

void setScaleAll(LiveActor* actor, f32 scale) {
    setScale(actor, {scale, scale, scale});
}

void setScaleX(LiveActor* actor, f32 x) {
    getScalePtr(actor)->x = x;
}

void setScaleY(LiveActor* actor, f32 y) {
    getScalePtr(actor)->y = y;
}

void setScaleZ(LiveActor* actor, f32 z) {
    getScalePtr(actor)->z = z;
}

bool isFrontTarget(const LiveActor* actor, const sead::Vector3f& pos) {
    return isFrontDir(actor, pos - getTrans(actor));
}

bool isFrontDir(const LiveActor* actor, const sead::Vector3f& dir) {
    sead::Vector3f front;
    calcFrontDir(&front, actor);
    return front.dot(dir) > 0.0f;
}

bool isBackTarget(const LiveActor* actor, const sead::Vector3f& pos) {
    return isBackDir(actor, pos - getTrans(actor));
}

bool isBackDir(const LiveActor* actor, const sead::Vector3f& dir) {
    sead::Vector3f back;
    calcBackDir(&back, actor);
    return back.dot(dir) > 0.0f;
}

bool isLeftTarget(const LiveActor* actor, const sead::Vector3f& pos) {
    return isLeftDir(actor, pos - getTrans(actor));
}

bool isLeftDir(const LiveActor* actor, const sead::Vector3f& dir) {
    sead::Vector3f left;
    calcLeftDir(&left, actor);
    return left.dot(dir) > 0.0f;
}

bool isRightTarget(const LiveActor* actor, const sead::Vector3f& pos) {
    return isRightDir(actor, pos - getTrans(actor));
}

bool isRightDir(const LiveActor* actor, const sead::Vector3f& dir) {
    sead::Vector3f right;
    calcRightDir(&right, actor);
    return right.dot(dir) > 0.0f;
}

bool isUpTarget(const LiveActor* actor, const sead::Vector3f& pos) {
    return isUpDir(actor, pos - getTrans(actor));
}

bool isUpDir(const LiveActor* actor, const sead::Vector3f& dir) {
    sead::Vector3f up;
    calcUpDir(&up, actor);
    return up.dot(dir) > 0.0f;
}

bool isDownTarget(const LiveActor* actor, const sead::Vector3f& pos) {
    return isDownDir(actor, pos - getTrans(actor));
}

bool isDownDir(const LiveActor* actor, const sead::Vector3f& dir) {
    sead::Vector3f down;
    calcDownDir(&down, actor);
    return down.dot(dir) > 0.0f;
}

const sead::Quatf& getQuat(const LiveActor* actor) {
    return actor->getPoseKeeper()->getQuat();
}

sead::Quatf* getQuatPtr(LiveActor* actor) {
    return tryGetQuatPtr(actor);
}

sead::Quatf* tryGetQuatPtr(LiveActor* actor) {
    return actor->getPoseKeeper()->getQuatPtr();
}

void setQuat(LiveActor* actor, const sead::Quatf& quat) {
    getQuatPtr(actor)->set(quat);
}

sead::Vector3f* getGravityPtr(const LiveActor* actor) {
    return actor->getPoseKeeper()->getGravityPtr();
}

void setGravity(const LiveActor* actor, const sead::Vector3f& gravity) {
    getGravityPtr(actor)->set(gravity);
}

const sead::Vector3f& getFront(const LiveActor* actor) {
    return actor->getPoseKeeper()->getFront();
}

sead::Vector3f* getFrontPtr(LiveActor* actor) {
    return actor->getPoseKeeper()->getFrontPtr();
}

void setFront(LiveActor* actor, const sead::Vector3f& front) {
    getFrontPtr(actor)->set(front);
}

const sead::Vector3f& getUp(const LiveActor* actor) {
    return actor->getPoseKeeper()->getUp();
}

sead::Vector3f* getUpPtr(LiveActor* actor) {
    return actor->getPoseKeeper()->getUpPtr();
}

void setUp(LiveActor* actor, const sead::Vector3f& up) {
    getUpPtr(actor)->set(up);
}

void multVecPoseNoTrans(sead::Vector3f* posOut, const LiveActor* actor,
                        const sead::Vector3f& posIn) {
    sead::Matrix34f mtx;
    makeMtxR(&mtx, actor);
    posOut->setMul(mtx, posIn);
}

void multVecPose(sead::Vector3f* posOut, const LiveActor* actor, const sead::Vector3f& posIn) {
    sead::Matrix34f mtx;
    makeMtxRT(&mtx, actor);
    posOut->setMul(mtx, posIn);
}

void multVecPoseAndScale(sead::Vector3f* posOut, const LiveActor* actor,
                         const sead::Vector3f& posIn) {
    sead::Matrix34f mtx;
    makeMtxSRT(&mtx, actor);
    posOut->setMul(mtx, posIn);
}

void multVecInvPose(sead::Vector3f* posOut, const LiveActor* actor, const sead::Vector3f& posIn) {
    sead::Matrix34f mtx;
    makeMtxRT(&mtx, actor);
    mtx.invert();
    posOut->setMul(mtx, posIn);
}

// TODO: Find equivalent. Sead implementation of vector.rotate(quat) with quat inverted
inline void rotateInv(sead::Vector3f* o, const sead::Quatf& q, const sead::Vector3f& v) {
    sead::Quatf r;
    r.x = -(q.y * v.z) + (q.z * v.y) + (q.w * v.x);
    r.y = (q.x * v.z) - (q.z * v.x) + (q.w * v.y);
    r.z = -(q.x * v.y) + (q.y * v.x) + (q.w * v.z);
    r.w = (q.x * v.x) + (q.y * v.y) + (q.z * v.z);

    o->x = (r.x * q.w) + (r.y * q.z) - (r.z * q.y) + (r.w * q.x);
    o->y = -(r.x * q.z) + (r.y * q.w) + (r.z * q.x) + (r.w * q.y);
    o->z = (r.x * q.y) - (r.y * q.x) + (r.z * q.w) + (r.w * q.z);
}

void multVecInvQuat(sead::Vector3f* posOut, const LiveActor* actor, const sead::Vector3f& posIn) {
    ActorPoseKeeperBase* poseKeeper = actor->getPoseKeeper();
    sead::Vector3f trans = posIn - poseKeeper->getTrans();
    rotateInv(posOut, poseKeeper->getQuat(), trans);
}

void multMtxInvPose(sead::Matrix34f* mtxOut, const LiveActor* actor, const sead::Matrix34f& mtxIn) {
    sead::Matrix34f mtx;
    makeMtxRT(&mtx, actor);
    mtx.invert();
    mtxOut->setMul(mtx, mtxIn);
}

void calcTransLocalOffset(sead::Vector3f* transOut, const LiveActor* actor,
                          const sead::Vector3f& transIn) {
    sead::Matrix34f mtx;
    makeMtxRT(&mtx, actor);
    calcTransLocalOffsetByMtx(transOut, mtx, transIn);
}

}  // namespace al

namespace alActorPoseFunction {

void calcBaseMtx(sead::Matrix34f* mtx, const al::LiveActor* actor) {
    actor->getPoseKeeper()->calcBaseMtx(mtx);
}

void updatePoseTRMSV(al::LiveActor* actor) {
    al::ActorPoseKeeperBase* poseKeeper = actor->getPoseKeeper();
    poseKeeper->updatePoseRotate(poseKeeper->getRotate());
}

}  // namespace alActorPoseFunction
