#include "../engine/Shared.h"
#include "Global.h"
#include "Instance.h"
#include "bx/bx.h"
#include "bx/math.h"
#include <cassert>
#include <cstdint>

namespace
{
    SharedData* SharedInstance = nullptr;
    Game::GameInstance* GameInst = nullptr;
} // namespace

void Transform::CreateTransform(float* out, bx::Vec3 pos, bx::Quaternion rot, bx::Vec3 scale)
{
    if (out == nullptr) return;
    float rMat[16]{0};
    float tMat[16]{0};
    float sMat[16]{0};
    bx::mtxFromQuaternion(rMat, bx::Quaternion{rot.x, rot.y, rot.z, rot.w});
    bx::mtxTranslate(tMat, pos.x, pos.y, pos.z);
    bx::mtxScale(sMat, scale.x, scale.y, scale.z);
    float buf[16]{0};
    bx::mtxMul(buf, rMat, sMat);
    bx::mtxMul(out, buf, tMat);
}

void Transform::Translate(Vec3 offset)
{
    Position = bx::add(Position, {offset.x, offset.y, offset.z});
}

void Transform::TranslateLocal(Vec3 offset)
{
    Vec3 localOffset = GlobalToLocalDirection(offset);
    Position = bx::add(Position, {localOffset.x, localOffset.y, localOffset.z});
}

void Transform::Rotate(Vec3 rotation)
{
    float rot[16]{0};
    bx::mtxRotateXYZ(rot, rotation.x, rotation.y, rotation.z);
    float temp[16]{0};
    bx::mtxMul(temp, rot, Rotation.M);
    bx::memCopy(Rotation.M, temp, sizeof(temp));
}

void Transform::RotateLocal(Vec3 rotation)
{
    float rot[16]{0};
    bx::mtxRotateXYZ(rot, rotation.x, rotation.y, rotation.z);
    float temp[16]{0};
    bx::mtxMul(temp, Rotation.M, rot);
    bx::memCopy(Rotation.M, temp, sizeof(temp));
}

Vec3 Transform::Right() const
{
    return {M.M[0], M.M[1], M.M[2]};
}

Vec3 Transform::Up() const
{
    return {M.M[4], M.M[5], M.M[6]};
}

Vec3 Transform::Forward() const
{
    return {M.M[8], M.M[9], M.M[10]};
}

void Transform::UpdateMatrix()
{
    Mat4 pos;
    Mat4 scale;
    bx::mtxTranslate(pos.M, Position.x, Position.y, Position.z);
    bx::mtxScale(scale.M, Scale.x, Scale.y, Scale.z);
    Mat4 temp;
    bx::mtxMul(temp.M, scale.M, Rotation.M);
    bx::mtxMul(M.M, temp.M, pos.M);
    bx::mtxInverse(MI.M, M.M);
}

void Transform::UpdateMatrixForCam()
{
    Mat4 pos;
    bx::mtxTranslate(pos.M, Position.x, Position.y, Position.z);
    bx::mtxMul(M.M, pos.M, Rotation.M);
    bx::mtxInverse(MI.M, M.M);
}

namespace Game
{
    SharedData& GetShared()
    {
        assert(SharedInstance != nullptr);
        return *SharedInstance;
    }

    void SetShared(SharedData& instance)
    {
        SharedInstance = &instance;
    }

    GameInstance& GetInstance()
    {
        assert(GameInst != nullptr);
        return *GameInst;
    }

    void SetInstance(Game::GameInstance& instance)
    {
        GameInst = &instance;
    }

    void* AllocateScratch(size_t byteCount, size_t align)
    {
        size_t offset = GetInstance().UsedScratchAmount;
        uint8_t* base = static_cast<uint8_t*>(GetShared().Game.TransientStorage);
        uint8_t* current = base + offset;
        size_t offsetAligned = ((offset + align - 1) / align) * align;
        uint8_t* ptrAligned = base + offsetAligned;
        size_t newOffset = offsetAligned + byteCount;
        if (newOffset > GetShared().Game.TransientStorageSize) return nullptr;
        GetInstance().UsedScratchAmount = newOffset;
        return reinterpret_cast<void*>(ptrAligned);
    }
} // namespace Game
Vec3 Transform::GlobalToLocalDirection(Vec3 global)
{
    UpdateMatrix();
    float in[4]{global.x, global.y, global.z, 0.0f};
    float out[4]{0.0f};
    bx::vec4MulMtx(out, in, MI.Transpose().M);
    return {out[0], out[1], out[2]};
}
bx::Vec3 Transform::GlobalToLocalPoint(Vec3 global)
{
    UpdateMatrix();
    float in[4]{global.x, global.y, global.z, 1.0f};
    float out[4]{0.0f};
    bx::vec4MulMtx(out, in, MI.M);
    return {out[0], out[1], out[2]};
}
Vec3 Transform::LocalToGlobalDirection(Vec3 local)
{
    UpdateMatrix();
    float in[4]{local.x, local.y, local.z, 0.0f};
    float out[4]{0.0f};
    bx::vec4MulMtx(out, in, M.Transpose().M);
    return {out[0], out[1], out[2]};
}
bx::Vec3 Transform::LocalToGlobalPoint(Vec3 local)
{
    UpdateMatrix();
    float in[4]{local.x, local.y, local.z, 1.0f};
    float out[4]{0.0f};
    bx::vec4MulMtx(out, in, M.M);
    return {out[0], out[1], out[2]};
}
void Transform::SetPosition(Vec3 pos)
{
    Position = {pos.x, pos.y, pos.z};
}
Vec3 Transform::GetPosition()
{
    return {Position.x, Position.y, Position.z};
}
Mat4 Mat4::Inverse()
{
    Mat4 result;
    bx::mtxInverse(result.M, M);
    return result;
}
Mat4 Mat4::Transpose()
{
    Mat4 result;
    bx::mtxTranspose(result.M, M);
    return result;
}
