#include "ItemPhysics.h"
#include "Client.h"

#include "Utils/MoveUtils.h"

glm::mat4x4 ItemPhysics::glm_rotate(glm::mat4x4& mat, float angle, const glm::vec3& axis) {
    static auto mod = Client::findMod<ItemPhysics>(_("Item Physics"));
    if (!mod) return rotate(mat, angle, axis);
    
    const auto renderData = mod->renderData;

    if (renderData == nullptr)
        return rotate(mat, angle, axis);

    auto curr = reinterpret_cast<ItemActor*>(renderData->actor);

    static float height = 0.5f;

    bool isOnGround = Utils::Move::isOnGroundItem(curr);

    if (!mod->actorData.contains(curr)) {
        std::random_device rd;
        std::mt19937 gen(rd());

        std::uniform_int_distribution dist(0, 1);
        std::uniform_int_distribution dist2(0, 359);

        const auto vec = glm::vec3(dist2(gen), dist2(gen), dist2(gen));
        const auto sign = glm::vec3(dist(gen) * 2 - 1, dist(gen) * 2 - 1, dist(gen) * 2 - 1);

        auto def = std::tuple{ isOnGround ? 0.f : height, vec, sign };
        mod->actorData.emplace(curr, def);
    }

    const float deltaTime = 1.f / static_cast<float>(Client::framerate);

    float& yMod = std::get<0>(mod->actorData.at(curr));

    yMod -= height * deltaTime;

    if (yMod <= 0.f)
        yMod = 0.f;

    glm::vec3 pos = renderData->position;
    pos.y += yMod;

    auto& vec = std::get<1>(mod->actorData.at(curr));
    auto& sign = std::get<2>(mod->actorData.at(curr));

    auto& settings = mod->settings;
    
    if (!isOnGround || yMod > 0.f) {
        vec.x += static_cast<float>(sign.x) * deltaTime * (8.f * mod->speed) * 16.f;
        vec.y += static_cast<float>(sign.y) * deltaTime * (8.f * mod->speed) * 18.f;
        vec.z += static_cast<float>(sign.z) * deltaTime * (8.f * mod->speed) * 16.f;

        if (vec.x > 360.f)
            vec.x -= 360.f;

        if (vec.x < 0.f)
            vec.x += 360.f;

        if (vec.y > 360.f)
            vec.y -= 360.f;

        if (vec.y < 0.f)
            vec.y += 360.f;

        if (vec.z > 360.f)
            vec.z -= 360.f;

        if (vec.z < 0.f)
            vec.z += 360.f;
    }

    glm::vec3 renderVec = vec;

    if (isOnGround && yMod == 0.f && (sign.x != 0 || sign.y != 0 && sign.z != 0)) {
        if (sign.x != 0 || sign.y != 0 && sign.z != 0) {
            vec.x += static_cast<float>(sign.x) * deltaTime * (8.f * mod->speed) * 18.f;

            if (curr->getStack().getBlock() != nullptr) {
                vec.z += static_cast<float>(sign.z) * deltaTime * (8.f * mod->speed) * 18.f;

                if (vec.x > 360.f || vec.x < 0.f) {
                    vec.x = 0.f;
                    sign.x = 0;
                }

                if (vec.z > 360.f || vec.z < 0.f) {
                    vec.z = 0.f;
                    sign.z = 0;
                }
            }
            else {
                vec.y += static_cast<float>(sign.y) * deltaTime * (8.f * mod->speed) * 16.f;

                if (vec.x - 90.f > 360.f || vec.x - 90.f < 0.f) {
                    vec.x = 90.f;
                    sign.x = 0;
                }

                if (vec.y > 360.f || vec.y < 0.f) {
                    vec.y = 0.f;
                    sign.y = 0;
                }
            }
        }
    }

    if (isOnGround) {
        if (!curr->getStack().getBlock()) {
            pos.y -= 0.12;
        }
        else {
            pos.y -= 0.3;
        }
    }

    //mat = translate(mat, { pos.x, pos.y, pos.z });

    if (mod->spin) {
        mat = rotate(mat, glm::radians(renderVec.x), { 1.f, 0.f, 0.f });
        mat = rotate(mat, glm::radians(renderVec.y), { 0.f, 1.f, 0.f });
        mat = rotate(mat, glm::radians(renderVec.z), { 0.f, 0.f, 1.f });
    }

    return mat;
}

auto ItemPhysics::onItemRenderer_render(ActorRenderData* data) -> void {
    renderData = data;
}

auto ItemPhysics::onClient() -> void {
    if (!Client::clientinstance || !Client::clientinstance->getLocalPlayer()) {
        actorData.clear();
        renderData = nullptr;
    }

    if (!enabled) return;
    if (patched) return;

    if (!itempositionconst_) return;

    posRel = *reinterpret_cast<uint32_t*>(itempositionconst_);
    patched = true;

    if (glm_rotateHook == nullptr) {
        glm_rotateHook = std::make_unique<HookClass>(glm_rotateRef_, glm_rotate);
    }

    newposRel = static_cast<float*>(AllocateBuffer(reinterpret_cast<void*>(itempositionconst_)));
    *newposRel = 0.f;

    {
        uintptr_t relAddress = reinterpret_cast<uintptr_t>(newposRel) - (reinterpret_cast<uintptr_t>(itempositionconst_) + 4);
        std::array<std::byte, 4> newRipRel{};

        for (size_t i = 0; i < 4; ++i) {
            newRipRel[i] = static_cast<std::byte>((relAddress >> (i * 8)) & 0xFF);
        }

        {
            DWORD oldprotect;
            VirtualProtect(itempositionconst_, 4, PAGE_EXECUTE_READWRITE, &oldprotect);
            memcpy(itempositionconst_, newRipRel.data(), 4);
            VirtualProtect(itempositionconst_, 4, oldprotect, &oldprotect);
        }
    }

    glm_rotateHook->enable();

    {
        DWORD oldprotect;
        VirtualProtect(glm_rotateRef_, 1, PAGE_EXECUTE_READWRITE, &oldprotect);
        memcpy(glm_rotateRef_, (BYTE*)"\xE8", 1);
        VirtualProtect(glm_rotateRef_, 1, oldprotect, &oldprotect);
    }

    {

        DWORD oldprotect;
        VirtualProtect(glm_translateRef_, 5, PAGE_EXECUTE_READWRITE, &oldprotect);
        memset(glm_translateRef_, 0x90, 5);
        VirtualProtect(glm_translateRef_, 5, oldprotect, &oldprotect);
    }
}

auto ItemPhysics::onDisabled() -> void {
    if (patched) {
        if (itempositionconst_) {
            DWORD oldprotect;
            VirtualProtect(itempositionconst_, 4, PAGE_EXECUTE_READWRITE, &oldprotect);
            memcpy(itempositionconst_, &posRel, 4);
            VirtualProtect(itempositionconst_, 4, oldprotect, &oldprotect);
        }

        if (newposRel)
            FreeBuffer(newposRel);

        if (glm_rotateHook)
            glm_rotateHook->disable();

        if (glm_translateRef_) {
            DWORD oldprotect;
            VirtualProtect(glm_translateRef_, 5, PAGE_EXECUTE_READWRITE, &oldprotect);
            memcpy(glm_translateRef_, ogBytes, 5);
            VirtualProtect(glm_translateRef_, 5, oldprotect, &oldprotect);
        }

        patched = false;
    }

    actorData.clear();
}
