#include "Library/Math/MathGridUtil.h"

#include "Library/Math/MathUtil.h"

namespace al {
const f32 gIndexToDegreeX[7] = {270.0f, 180.0f, 0.0f, 0.0f, 0.0f, 0.0f, 90.0f};
const f32 gIndexToDegreeY[7] = {180.0f, 0.0f, 270.0f, 0.0f, 90.0f, 0.0f, 0.0f};
const f32 gIndexToDegreeZ[5] = {270.0f, 180.0f, 0.0f, 0.0f, 90.0f};

void calcWorldPosFromGridIndex(sead::Vector3f* outPos, const sead::Matrix34f& mtx,
                               const sead::Vector3i& gridIndex) {
    sead::Vector3f grid = {(f32)gridIndex.x, (f32)gridIndex.y, (f32)gridIndex.z};
    outPos->setMul(mtx, grid);
}

void calcGridIndexFromWorldPos(sead::Vector3i* outGridIndex, const sead::Matrix34f& mtx,
                               const sead::Vector3f& pos) {
    sead::Vector3f posOrigin = pos - mtx.getTranslation();

    outGridIndex->x =
        sead::Mathf::round(mtx.getBase(0).dot(posOrigin) / mtx.getBase(0).squaredLength());
    outGridIndex->y =
        sead::Mathf::round(mtx.getBase(1).dot(posOrigin) / mtx.getBase(1).squaredLength());
    outGridIndex->z =
        sead::Mathf::round(mtx.getBase(2).dot(posOrigin) / mtx.getBase(2).squaredLength());
}

sead::Vector3i calcGridIndexNext(const sead::Vector3i& gridIndex, s32 dirIndex) {
    sead::Vector3i nextGridIndex = gridIndex;
    s32 axis = sead::Mathi::abs(dirIndex);
    s32 dir = 0 < dirIndex ? 1 : -1;

    s32* selectedAxis;
    switch (axis) {
    case DirIndex_X:
        selectedAxis = &nextGridIndex.x;
        break;
    case DirIndex_Y:
        selectedAxis = &nextGridIndex.y;
        break;
    case DirIndex_Z:
        selectedAxis = &nextGridIndex.z;
        break;
    default:
        selectedAxis = &nextGridIndex.z;
        break;
    }

    *selectedAxis += dir;
    return nextGridIndex;
}

f32 calcDegreeDirIndexSignedX(s32 dirIndex) {
    if ((u32)(dirIndex + 3) < 7U)
        return gIndexToDegreeX[dirIndex + 3];
    return 0;
}

f32 calcDegreeDirIndexSignedY(s32 dirIndex) {
    if ((u32)(dirIndex + 3) < 7U)
        return gIndexToDegreeY[dirIndex + 3];
    return 0;
}

f32 calcDegreeDirIndexSignedZ(s32 dirIndex) {
    if ((u32)(dirIndex + 2) < 5U)
        return gIndexToDegreeZ[dirIndex + 2];
    return 0;
}

s32 calcDirIndexNearXY(const sead::Vector3i& startGridIndex, const sead::Vector3i& endGridIndex) {
    s32 x = endGridIndex.x - startGridIndex.x;
    s32 y = endGridIndex.y - startGridIndex.y;

    if (y == 0 && x == 0)
        return DirIndex_None;

    if (sead::Mathi::abs(x) >= sead::Mathi::abs(y))
        return x > 0 ? DirIndex_X : DirIndex_NegX;
    else
        return y > 0 ? DirIndex_Y : DirIndex_NegY;
}

s32 calcDirIndexNearYZ(const sead::Vector3i& startGridIndex, const sead::Vector3i& endGridIndex) {
    s32 y = endGridIndex.y - startGridIndex.y;
    s32 z = endGridIndex.z - startGridIndex.z;

    if (z == 0 && y == 0)
        return DirIndex_None;

    if (sead::Mathi::abs(y) >= sead::Mathi::abs(z))
        return 0 < y ? DirIndex_Y : DirIndex_NegY;
    else
        return 0 < z ? DirIndex_Z : DirIndex_NegZ;
}

s32 calcDirIndexNearZX(const sead::Vector3i& startGridIndex, const sead::Vector3i& endGridIndex) {
    s32 z = endGridIndex.z - startGridIndex.z;
    s32 x = endGridIndex.x - startGridIndex.x;

    if (x == 0 && z == 0)
        return DirIndex_None;

    if (sead::Mathi::abs(z) >= sead::Mathi::abs(x))
        return 0 < z ? DirIndex_Z : DirIndex_NegZ;
    else
        return 0 < x ? DirIndex_X : DirIndex_NegX;
}

s32 signDirIndexX(s32 dirIndex) {
    if (sead::Mathi::abs(dirIndex) != DirIndex_X)
        return 0;
    return sign(dirIndex);
}

s32 signDirIndexY(s32 dirIndex) {
    if (sead::Mathi::abs(dirIndex) != DirIndex_Y)
        return 0;
    return sign(dirIndex);
}

s32 signDirIndexZ(s32 dirIndex) {
    if (sead::Mathi::abs(dirIndex) != DirIndex_Z)
        return 0;
    return sign(dirIndex);
}

void calcGridMinMaxFromOBB(sead::Vector3i* outGridIndexMin, sead::Vector3i* outGridIndexMax,
                           const sead::Matrix34f& gridMtx, const sead::Matrix34f& boxMtx,
                           const sead::BoundBox3f& boundBox) {
    outGridIndexMin->x = sead::Mathi::maxNumber();
    outGridIndexMin->y = sead::Mathi::maxNumber();
    outGridIndexMin->z = sead::Mathi::maxNumber();
    outGridIndexMax->x = sead::Mathi::minNumber();
    outGridIndexMax->y = sead::Mathi::minNumber();
    outGridIndexMax->z = sead::Mathi::minNumber();
    expandGridFromOBB(outGridIndexMin, outGridIndexMax, gridMtx, boxMtx, boundBox);
}

void expandGridFromOBB(sead::Vector3i* outGridIndexMin, sead::Vector3i* outGridIndexMax,
                       const sead::Matrix34f& gridMtx, const sead::Matrix34f& boxMtx,
                       const sead::BoundBox3f& boundBox) {
    sead::Vector3f points[8];
    points[0] = {boundBox.getMin().x, boundBox.getMin().y, boundBox.getMin().z};
    points[1] = {boundBox.getMax().x, boundBox.getMin().y, boundBox.getMin().z};
    points[2] = {boundBox.getMax().x, boundBox.getMin().y, boundBox.getMax().z};
    points[3] = {boundBox.getMin().x, boundBox.getMin().y, boundBox.getMax().z};
    points[4] = {boundBox.getMin().x, boundBox.getMax().y, boundBox.getMin().z};
    points[5] = {boundBox.getMax().x, boundBox.getMax().y, boundBox.getMin().z};
    points[6] = {boundBox.getMax().x, boundBox.getMax().y, boundBox.getMax().z};
    points[7] = {boundBox.getMin().x, boundBox.getMax().y, boundBox.getMax().z};

    for (s32 i = 0; i < 8; i++) {
        sead::Vector3f gridPos;
        gridPos.setMul(boxMtx, points[i]);

        sead::Vector3i gridIndex;
        calcGridIndexFromWorldPos(&gridIndex, gridMtx, gridPos);

        if (outGridIndexMin->x > gridIndex.x)
            outGridIndexMin->x = gridIndex.x;
        if (outGridIndexMin->y > gridIndex.y)
            outGridIndexMin->y = gridIndex.y;
        if (outGridIndexMin->z > gridIndex.z)
            outGridIndexMin->z = gridIndex.z;

        if (outGridIndexMax->x < gridIndex.x)
            outGridIndexMax->x = gridIndex.x;
        if (outGridIndexMax->y < gridIndex.y)
            outGridIndexMax->y = gridIndex.y;
        if (outGridIndexMax->z < gridIndex.z)
            outGridIndexMax->z = gridIndex.z;
    }
}

}  // namespace al
