#include "combat.h"

using namespace rbx;

rbx::vector3_t cross_product(rbx::vector3_t a, rbx::vector3_t b) {
    return rbx::vector3_t(
        a.y * b.z - a.z * b.y,
        a.z * b.x - a.x * b.z,
        a.x * b.y - a.y * b.x
    );
}

rbx::vector3_t normalize(rbx::vector3_t a) {
    float l = sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
    return l ? rbx::vector3_t(a.x / l, a.y / l, a.z / l) : a;
}

rbx::matrix3_t lerp_matrix3(rbx::matrix3_t a, rbx::matrix3_t b, float t) {
    rbx::matrix3_t res;
    for (int i = 0; i < 9; ++i) {
        res.data[i] = a.data[i] + (b.data[i] - a.data[i]) * t;
    }
    return res;
}

rbx::matrix3_t lerp_matrix3_smooth(rbx::matrix3_t a, rbx::matrix3_t b, float x, float y) {
    rbx::matrix3_t res;
    for (int i = 0; i < 9; ++i) {
        if (i % 3 == 0)
            res.data[i] = a.data[i] + (b.data[i] - a.data[i]) * x;
        else
            res.data[i] = a.data[i] + (b.data[i] - a.data[i]) * y;
    }
    return res;
}

rbx::matrix3_t get_target_matrix(rbx::vector3_t camera, rbx::vector3_t target) {
    auto dis = rbx::vector3_t(target.x - camera.x, target.y - camera.y, target.z - camera.z);
    rbx::vector3_t dir = normalize(dis);
    rbx::vector3_t right = normalize(cross_product(rbx::vector3_t(0, 1, 0), dir));
    rbx::vector3_t up = cross_product(dir, right);

    rbx::matrix3_t target_matrix;
    target_matrix.data[0] = right.x * -1;
    target_matrix.data[1] = up.x;
    target_matrix.data[2] = -dir.x;
    target_matrix.data[3] = right.y;
    target_matrix.data[4] = up.y;
    target_matrix.data[5] = -dir.y;
    target_matrix.data[6] = right.z * -1;
    target_matrix.data[7] = up.z;
    target_matrix.data[8] = -dir.z;

    return target_matrix;
}

void combat::perform_mouse_aimbot(rbx::vector2_t screen) {
    POINT mouse_pos;
    if (!GetCursorPos(&mouse_pos)) return;
    if (!ScreenToClient(globals::our_window, &mouse_pos)) return;

    auto dx = screen.x - mouse_pos.x;
    auto dy = screen.y - mouse_pos.y;

    if (abs(dx) < 1 && abs(dy) < 1) return;

    auto hdc = GetDC(globals::our_window);
    auto curr_dpi = GetDeviceCaps(hdc, LOGPIXELSX);
    auto scale = curr_dpi / 96.0f;
    ReleaseDC(globals::our_window, hdc);

    dx *= 0.2f * scale;
    dy *= 0.2f * scale;

    INPUT input = {};
    input.type = INPUT_MOUSE;
    input.mi.dx = static_cast<LONG>(dx);
    input.mi.dy = static_cast<LONG>(dy);
    input.mi.dwFlags = MOUSEEVENTF_MOVE;
    SendInput(1, &input, sizeof(INPUT));
}

void combat::perform_camera_aimbot(rbx::vector3_t target) {
    auto cam = globals::g_camera;
    auto cam_pos = cam.get_camera_position();
    auto cam_rot = cam.get_camera_rotation();

    auto t_matrix = get_target_matrix(cam_pos, target);
    rbx::matrix3_t target_matrix;

    {
        // smoothing stuff
        float smoothx = 1.0f / 3.f; // smoothing-x value
        float smoothy = 1.0f / 3.f; // smoothing-y value

        smoothx = max(0.01f, min(1.0f, smoothx));
        smoothy = max(0.01f, min(1.0f, smoothy));

        target_matrix = lerp_matrix3_smooth(cam_rot, t_matrix, smoothx, smoothy);
    }

    cam.set_camera_rotation(target_matrix);
}

void combat::combat_thread() {
    sleep_ms(2000); // slight wait to make sure everything starts
	while (true) {
        if (!cheats::combat::aimbot) continue;
        if (!globals::g_datamodel.self || !globals::g_visual_engine.self || !globals::g_players_service.self || !globals::g_camera.self) continue;

        entity player;
        part player_part;
        auto closest = FLT_MAX;

        {
            // getting closest part of a player to use as target
            std::lock_guard<std::mutex> lock(cache::mutex_c);
            for (auto p : cache::targets_c) {
                if (!p.player_object.self || p.player_object.self == globals::g_players_service.get_local_player().self) continue;

                auto p_part = p.head;
                auto part_pos = p_part.get_part_position();
                auto screen = globals::g_visual_engine.world_to_screen(part_pos);
                if (screen.x == 0 || screen.y == 0) continue;

                POINT m;
                if (!GetCursorPos(&m)) continue;
                if (!ScreenToClient(globals::our_window, &m)) continue;

                auto dx = screen.x - m.x;
                auto dy = screen.y - m.y;
                auto dist = sqrt(dx * dx + dy * dy);

                if (rbx::cheats::combat::use_fov && dist > cheats::combat::fov_radius) continue;

                if (dist < closest) {
                    closest = dist;
                    player = p.player_object;
                    player_part = p_part;
                }
            }
        }

        if (GetAsyncKeyState('Q') & 0x8000) {
            if (closest >= FLT_MAX - 1e6f) continue; // no closest target found

            auto part_pos = player_part.get_part_position();
            rbx::vector2_t screen = globals::g_visual_engine.world_to_screen(part_pos);
            if (screen.x == 0 || screen.y == 0) continue;

            switch (cheats::combat::aimbot_type) {
            case 0:
                perform_camera_aimbot(part_pos);
                break;
            case 1:
                perform_mouse_aimbot(screen);
                break;
            }
        }
	}
}