#include "Library/Matrix/MatrixUtil.h"

#include "Library/Math/MathUtil.h"

namespace al {

void makeMtxRotateTrans(sead::Matrix34f* outMtx, const sead::Vector3f& rotate,
                        const sead::Vector3f& trans) {
    sead::Vector3f rotateRad(sead::Mathf::deg2rad(rotate.x), sead::Mathf::deg2rad(rotate.y),
                             sead::Mathf::deg2rad(rotate.z));
    outMtx->makeRT(rotateRad, trans);
}

void makeMtxFromTwoAxis(sead::Matrix34f* outMtx, const sead::Vector3f& vectorA,
                        const sead::Vector3f& vectorB, s32 axisA, s32 axisB) {
    sead::Vector3f dir[3];

    s32 axisC;
    if (axisA == 0)
        axisC = axisB == 1 ? 2 : 1;
    else if (axisA == 1)
        axisC = axisB == 0 ? 2 : 0;
    else
        axisC = axisB == 0 ? 1 : 0;

    normalize(&dir[axisA], vectorA);
    dir[axisB] = vectorB;

    dir[axisC].setCross(dir[(axisC + 1) % 3], dir[(axisC + 2) % 3]);
    normalize(&dir[axisC]);

    dir[axisB].setCross(dir[(axisB + 1) % 3], dir[(axisB + 2) % 3]);

    outMtx->setBase(0, dir[0]);  // side
    outMtx->setBase(1, dir[1]);  // up
    outMtx->setBase(2, dir[2]);  // front
}

void makeMtxFrontUp(sead::Matrix34f* outMtx, const sead::Vector3f& front,
                    const sead::Vector3f& up) {
    sead::Vector3f frontNorm = front;
    normalize(&frontNorm);

    sead::Vector3f side;
    side.setCross(up, frontNorm);
    normalize(&side);

    sead::Vector3f upNorm;
    upNorm.setCross(frontNorm, side);

    outMtx->setBase(0, side);
    outMtx->setBase(1, upNorm);
    outMtx->setBase(2, frontNorm);
}

void makeMtxFrontSide(sead::Matrix34f* outMtx, const sead::Vector3f& front,
                      const sead::Vector3f& side) {
    sead::Vector3f frontNorm = front;
    normalize(&frontNorm);

    sead::Vector3f up;
    up.setCross(frontNorm, side);
    normalize(&up);

    sead::Vector3f sideNorm;
    sideNorm.setCross(up, frontNorm);

    outMtx->setBase(0, sideNorm);
    outMtx->setBase(1, up);
    outMtx->setBase(2, frontNorm);
}

void makeMtxUpFront(sead::Matrix34f* outMtx, const sead::Vector3f& up,
                    const sead::Vector3f& front) {
    sead::Vector3f upNorm = up;
    normalize(&upNorm);

    sead::Vector3f side;
    side.setCross(up, front);
    normalize(&side);

    sead::Vector3f frontNorm;
    frontNorm.setCross(side, upNorm);

    outMtx->setBase(0, side);
    outMtx->setBase(1, upNorm);
    outMtx->setBase(2, frontNorm);
}

void makeMtxUpSide(sead::Matrix34f* outMtx, const sead::Vector3f& up, const sead::Vector3f& side) {
    sead::Vector3f upNorm = up;
    normalize(&upNorm);

    sead::Vector3f front;
    front.setCross(side, up);
    normalize(&front);

    sead::Vector3f sideNorm;
    sideNorm.setCross(upNorm, front);

    outMtx->setBase(0, sideNorm);
    outMtx->setBase(1, upNorm);
    outMtx->setBase(2, front);
}

void makeMtxSideUp(sead::Matrix34f* outMtx, const sead::Vector3f& side, const sead::Vector3f& up) {
    makeMtxFromTwoAxis(outMtx, side, up, 0, 1);
}

void makeMtxSideFront(sead::Matrix34f* outMtx, const sead::Vector3f& side,
                      const sead::Vector3f& front) {
    makeMtxFromTwoAxis(outMtx, side, front, 0, 2);
}

void makeMtxFrontNoSupport(sead::Matrix34f* outMtx, const sead::Vector3f& front) {
    bool isYAxis = getMaxAbsElementIndex(front) == 1;

    sead::Vector3f up;
    up.x = isYAxis ? sead::Vector3f::ez.x : sead::Vector3f::ey.x;
    up.y = isYAxis ? sead::Vector3f::ez.y : sead::Vector3f::ey.y;
    up.z = isYAxis ? sead::Vector3f::ez.z : sead::Vector3f::ey.z;

    makeMtxFrontUp(outMtx, front, up);
}

void makeMtxFrontNoSupportPos(sead::Matrix34f* outMtx, const sead::Vector3f& front,
                              const sead::Vector3f& pos) {
    makeMtxFrontNoSupport(outMtx, front);
    outMtx->setBase(3, pos);
}

void makeMtxUpNoSupport(sead::Matrix34f* outMtx, const sead::Vector3f& up) {
    bool isZAxis = getMaxAbsElementIndex(up) == 2;

    sead::Vector3f front;
    front.x = isZAxis ? sead::Vector3f::ex.x : sead::Vector3f::ez.x;
    front.y = isZAxis ? sead::Vector3f::ex.y : sead::Vector3f::ez.y;
    front.z = isZAxis ? sead::Vector3f::ex.z : sead::Vector3f::ez.z;

    makeMtxUpFront(outMtx, up, front);
}

void makeMtxUpNoSupportPos(sead::Matrix34f* outMtx, const sead::Vector3f& up,
                           const sead::Vector3f& pos) {
    makeMtxUpNoSupport(outMtx, up);
    outMtx->setBase(3, pos);
}

void makeMtxFrontUpPos(sead::Matrix34f* outMtx, const sead::Vector3f& front,
                       const sead::Vector3f& up, const sead::Vector3f& pos) {
    makeMtxFrontUp(outMtx, front, up);
    outMtx->setBase(3, pos);
}

void makeMtxFrontSidePos(sead::Matrix34f* outMtx, const sead::Vector3f& front,
                         const sead::Vector3f& side, const sead::Vector3f& pos) {
    makeMtxFrontSide(outMtx, front, side);
    outMtx->setBase(3, pos);
}

void makeMtxUpFrontPos(sead::Matrix34f* outMtx, const sead::Vector3f& up,
                       const sead::Vector3f& front, const sead::Vector3f& pos) {
    makeMtxUpFront(outMtx, up, front);
    outMtx->setBase(3, pos);
}

void makeMtxUpSidePos(sead::Matrix34f* outMtx, const sead::Vector3f& up, const sead::Vector3f& side,
                      const sead::Vector3f& pos) {
    makeMtxUpSide(outMtx, up, side);
    outMtx->setBase(3, pos);
}

void makeMtxSideUpPos(sead::Matrix34f* outMtx, const sead::Vector3f& side, const sead::Vector3f& up,
                      const sead::Vector3f& pos) {
    makeMtxSideUp(outMtx, side, up);
    outMtx->setBase(3, pos);
}

void makeMtxSideFrontPos(sead::Matrix34f* outMtx, const sead::Vector3f& side,
                         const sead::Vector3f& front, const sead::Vector3f& pos) {
    makeMtxSideFront(outMtx, side, front);
    outMtx->setBase(3, pos);
}

void makeMtxFollowTarget(sead::Matrix34f* outMtx, const sead::Matrix34f& baseMtx,
                         const sead::Vector3f& trans, const sead::Vector3f& rotate) {
    sead::Matrix34f rotationMatrix;
    sead::Vector3f rotateRad(sead::Mathf::deg2rad(rotate.x), sead::Mathf::deg2rad(rotate.y),
                             sead::Mathf::deg2rad(rotate.z));
    rotationMatrix.makeR(rotateRad);

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

    sead::Matrix34f pose = rotationMatrix * translationMatrix;
    *outMtx = baseMtx * pose;
}

void rotateMtxXDirDegree(sead::Matrix34f* outMtx, const sead::Matrix34f& baseMtx, f32 angle) {
    sead::Matrix34f rotationMatrix;
    rotationMatrix.makeR(sead::Mathf::deg2rad(angle) * sead::Vector3f::ex);

    *outMtx = baseMtx * rotationMatrix;
}

void rotateMtxYDirDegree(sead::Matrix34f* outMtx, const sead::Matrix34f& baseMtx, f32 angle) {
    sead::Matrix34f rotationMatrix;
    rotationMatrix.makeR(sead::Mathf::deg2rad(angle) * sead::Vector3f::ey);

    *outMtx = baseMtx * rotationMatrix;
}

void rotateMtxZDirDegree(sead::Matrix34f* outMtx, const sead::Matrix34f& baseMtx, f32 angle) {
    sead::Matrix34f rotationMatrix;
    rotationMatrix.makeR(sead::Mathf::deg2rad(angle) * sead::Vector3f::ez);

    *outMtx = baseMtx * rotationMatrix;
}

void calcMtxScale(sead::Vector3f* outMtx, const sead::Matrix34f& mtx) {
    outMtx->x = sead::Mathf::sqrt(mtx.m[0][0] * mtx.m[0][0] + mtx.m[1][0] * mtx.m[1][0] +
                                  mtx.m[2][0] * mtx.m[2][0]);

    outMtx->y = sead::Mathf::sqrt(mtx.m[0][1] * mtx.m[0][1] + mtx.m[1][1] * mtx.m[1][1] +
                                  mtx.m[2][1] * mtx.m[2][1]);

    outMtx->z = sead::Mathf::sqrt(mtx.m[0][2] * mtx.m[0][2] + mtx.m[1][2] * mtx.m[1][2] +
                                  mtx.m[2][2] * mtx.m[2][2]);
}

void calcMtxScale(sead::Vector3f* outMtx, const Matrix43f& mtx) {
    outMtx->x = sead::Mathf::sqrt(mtx.m[0][0] * mtx.m[0][0] + mtx.m[0][1] * mtx.m[0][1] +
                                  mtx.m[0][2] * mtx.m[0][2]);

    outMtx->y = sead::Mathf::sqrt(mtx.m[1][0] * mtx.m[1][0] + mtx.m[1][1] * mtx.m[1][1] +
                                  mtx.m[1][2] * mtx.m[1][2]);

    outMtx->z = sead::Mathf::sqrt(mtx.m[2][0] * mtx.m[2][0] + mtx.m[2][1] * mtx.m[2][1] +
                                  mtx.m[2][2] * mtx.m[2][2]);
}

void normalizeMtxScale(sead::Matrix34f* outMtx, const sead::Matrix34f& mtx) {
    sead::Vector3f scale;
    calcMtxScale(&scale, mtx);

    if (!isNearZero(scale.x, 0.001f) && !isNearZero(scale.y, 0.001f) &&
        !isNearZero(scale.z, 0.001f)) {
        f32 xInv = 1.0f / scale.x;
        f32 yInv = 1.0f / scale.y;
        f32 zInv = 1.0f / scale.z;

        *outMtx = mtx;
        outMtx->scaleBases(xInv, yInv, zInv);
    }
}

bool tryNormalizeMtxScaleOrIdentity(sead::Matrix34f* outMtx, const sead::Matrix34f& mtx) {
    sead::Vector3f scale;
    calcMtxScale(&scale, mtx);

    if (!isNearZero(scale.x, 0.001f) && !isNearZero(scale.y, 0.001f) &&
        !isNearZero(scale.z, 0.001f)) {
        f32 xInv = 1.0f / scale.x;
        f32 yInv = 1.0f / scale.y;
        f32 zInv = 1.0f / scale.z;

        *outMtx = mtx;
        outMtx->scaleBases(xInv, yInv, zInv);
        return true;
    }

    sead::Vector3f base = mtx.getBase(3);
    outMtx->makeIdentity();
    outMtx->setBase(3, base);

    return false;
}

void preScaleMtx(sead::Matrix34f* outMtx, const sead::Vector3f& scale) {
    outMtx->scaleBases(scale.x, scale.y, scale.z);
}

}  // namespace al
