#pragma once
#include <d3d9.h>
#include <vector>
#include <string>
#include <algorithm>
#include <Windows.h>
#include "../extern/Imgui/imgui.h"
#include "../extern/Imgui/imgui_internal.h"
#include "../utils/settings.hpp"

#include "offsets.hpp"
#include <numbers>
#include <mutex>
#include <dwmapi.h>
#include <xmmintrin.h>
#include <array>
#include <cstdlib>
#include <random>
#include <direct.h>
#include <fstream>
#include <sstream>
#define M_PI 3.14159265358979323846264338327950288419716939937510

 int Width = GetSystemMetrics(SM_CXSCREEN);
 int Height = GetSystemMetrics(SM_CYSCREEN);

class Vector2 {
public:
	Vector2() : x(0.f), y(0.f) {}
	Vector2(double _x, double _y) : x(_x), y(_y) {}
	~Vector2() {}

	double x;
	double y;
};

class Vector3 {
public:
	double x, y, z;

	Vector3() : x(0.0), y(0.0), z(0.0) {}
	Vector3(double _x, double _y, double _z) : x(_x), y(_y), z(_z) {}

	inline double Dot(const Vector3& v) const {
		return x * v.x + y * v.y + z * v.z;
	}

	inline double Distance(const Vector3& v) const {
		return sqrt(pow(v.x - x, 2.0) + pow(v.y - y, 2.0) + pow(v.z - z, 2.0));
	}

	inline double Length() const {
		return sqrt(x * x + y * y + z * z);
	}

	inline bool IsZero() const {
		return (x == 0.0 && y == 0.0 && z == 0.0);
	}

	Vector3 operator+(const Vector3& v) const {
		return { x + v.x, y + v.y, z + v.z };
	}

	Vector3 operator-(const Vector3& v) const {
		return { x - v.x, y - v.y, z - v.z };
	}

	Vector3 operator*(double f) const {
		return { x * f, y * f, z * f };
	}
};

struct FPlane : Vector3 {
	double W;

	FPlane() : W(0) {}
	FPlane(double W) : W(W) {}
};

class FMatrix {
public:
	double m[4][4];
	FPlane XPlane, YPlane, ZPlane, WPlane;

	FMatrix() : XPlane(), YPlane(), ZPlane(), WPlane() {}
	FMatrix(FPlane XPlane, FPlane YPlane, FPlane ZPlane, FPlane WPlane)
		: XPlane(XPlane), YPlane(YPlane), ZPlane(ZPlane), WPlane(WPlane) {
	}

	D3DMATRIX ToD3DMATRIX() const {
		D3DMATRIX Result;
		Result._11 = XPlane.x; Result._12 = XPlane.y; Result._13 = XPlane.z; Result._14 = XPlane.W;
		Result._21 = YPlane.x; Result._22 = YPlane.y; Result._23 = YPlane.z; Result._24 = YPlane.W;
		Result._31 = ZPlane.x; Result._32 = ZPlane.y; Result._33 = ZPlane.z; Result._34 = ZPlane.W;
		Result._41 = WPlane.x; Result._42 = WPlane.y; Result._43 = WPlane.z; Result._44 = WPlane.W;
		return Result;
	}
};

struct FQuat {
	double x;
	double y;
	double z;
	double w;
};
struct FTransform
{
	FQuat rotation;
	Vector3 translation;
	uint8_t pad1c[0x8];
	Vector3 scale3d;
	uint8_t pad2c[0x8];

	D3DMATRIX to_matrix_with_scale()
	{
		D3DMATRIX m{};

		const Vector3 Scale
		(
			(scale3d.x == 0.0) ? 1.0 : scale3d.x,
			(scale3d.y == 0.0) ? 1.0 : scale3d.y,
			(scale3d.z == 0.0) ? 1.0 : scale3d.z
		);

		const double x2 = rotation.x + rotation.x;
		const double y2 = rotation.y + rotation.y;
		const double z2 = rotation.z + rotation.z;
		const double xx2 = rotation.x * x2;
		const double yy2 = rotation.y * y2;
		const double zz2 = rotation.z * z2;
		const double yz2 = rotation.y * z2;
		const double wx2 = rotation.w * x2;
		const double xy2 = rotation.x * y2;
		const double wz2 = rotation.w * z2;
		const double xz2 = rotation.x * z2;
		const double wy2 = rotation.w * y2;

		m._41 = translation.x;
		m._42 = translation.y;
		m._43 = translation.z;
		m._11 = (1.0f - (yy2 + zz2)) * Scale.x;
		m._22 = (1.0f - (xx2 + zz2)) * Scale.y;
		m._33 = (1.0f - (xx2 + yy2)) * Scale.z;
		m._32 = (yz2 - wx2) * Scale.z;
		m._23 = (yz2 + wx2) * Scale.y;
		m._21 = (xy2 - wz2) * Scale.y;
		m._12 = (xy2 + wz2) * Scale.x;
		m._31 = (xz2 + wy2) * Scale.z;
		m._13 = (xz2 - wy2) * Scale.x;
		m._14 = 0.0f;
		m._24 = 0.0f;
		m._34 = 0.0f;
		m._44 = 1.0f;

		return m;
	}
};

inline D3DMATRIX matrix_multiplication(D3DMATRIX pm1, D3DMATRIX pm2)
{

	D3DMATRIX pout{};
	pout._11 = pm1._11 * pm2._11 + pm1._12 * pm2._21 + pm1._13 * pm2._31 + pm1._14 * pm2._41;
	pout._12 = pm1._11 * pm2._12 + pm1._12 * pm2._22 + pm1._13 * pm2._32 + pm1._14 * pm2._42;
	pout._13 = pm1._11 * pm2._13 + pm1._12 * pm2._23 + pm1._13 * pm2._33 + pm1._14 * pm2._43;
	pout._14 = pm1._11 * pm2._14 + pm1._12 * pm2._24 + pm1._13 * pm2._34 + pm1._14 * pm2._44;
	pout._21 = pm1._21 * pm2._11 + pm1._22 * pm2._21 + pm1._23 * pm2._31 + pm1._24 * pm2._41;
	pout._22 = pm1._21 * pm2._12 + pm1._22 * pm2._22 + pm1._23 * pm2._32 + pm1._24 * pm2._42;
	pout._23 = pm1._21 * pm2._13 + pm1._22 * pm2._23 + pm1._23 * pm2._33 + pm1._24 * pm2._43;
	pout._24 = pm1._21 * pm2._14 + pm1._22 * pm2._24 + pm1._23 * pm2._34 + pm1._24 * pm2._44;
	pout._31 = pm1._31 * pm2._11 + pm1._32 * pm2._21 + pm1._33 * pm2._31 + pm1._34 * pm2._41;
	pout._32 = pm1._31 * pm2._12 + pm1._32 * pm2._22 + pm1._33 * pm2._32 + pm1._34 * pm2._42;
	pout._33 = pm1._31 * pm2._13 + pm1._32 * pm2._23 + pm1._33 * pm2._33 + pm1._34 * pm2._43;
	pout._34 = pm1._31 * pm2._14 + pm1._32 * pm2._24 + pm1._33 * pm2._34 + pm1._34 * pm2._44;
	pout._41 = pm1._41 * pm2._11 + pm1._42 * pm2._21 + pm1._43 * pm2._31 + pm1._44 * pm2._41;
	pout._42 = pm1._41 * pm2._12 + pm1._42 * pm2._22 + pm1._43 * pm2._32 + pm1._44 * pm2._42;
	pout._43 = pm1._41 * pm2._13 + pm1._42 * pm2._23 + pm1._43 * pm2._33 + pm1._44 * pm2._43;
	pout._44 = pm1._41 * pm2._14 + pm1._42 * pm2._24 + pm1._43 * pm2._34 + pm1._44 * pm2._44;
	return pout;
}


struct Camera {
	Vector3 Location;
	Vector3 Rotation;
	float FieldOfView;
};
template<class type>
class tarray {
public:
	tarray() : data(nullptr), count(std::int32_t()), maxx(std::int32_t()) {}
	tarray(type* data, std::int32_t count, std::int32_t maxx) : data(data), count(count), maxx(maxx) {}

	const bool is_valid() const noexcept {
		return !(this->data == nullptr);
	}

	const std::int32_t size() const noexcept {
		return this->count;
	}

	type& operator[](std::int32_t index) noexcept {
		return this->data[index];
	}

	const type& operator[](std::int32_t index) const noexcept {
		return this->data[index];
	}

	bool is_valid_index(std::int32_t index) const noexcept {
		return index < this->size();
	}

	std::vector<type> get_all() const {
		std::vector<type> result;

		if (!this->data || this->count <= 0)
			return result;

		result.reserve(this->count);

		for (std::int32_t i = 0; i < this->count; i++) {
			type element = kernel->read_t<type>(reinterpret_cast<std::uintptr_t>(this->data) + (i * sizeof(type)));
			result.push_back(element);
		}

		return result;
	}

	type* data;
	std::int32_t count;
	std::int32_t maxx;
};



inline D3DMATRIX to_matrix(Vector3 rot, Vector3 origin = Vector3(0, 0, 0))
{

	float radpitch = (rot.x * M_PI / 180);
	float radyaw = (rot.y * M_PI / 180);
	float radroll = (rot.z * M_PI / 180);
	float sp = sinf(radpitch);
	float cp = cosf(radpitch);
	float sy = sinf(radyaw);
	float cy = cosf(radyaw);
	float sr = sinf(radroll);
	float cr = cosf(radroll);
	D3DMATRIX matrix{};
	matrix.m[0][0] = cp * cy;
	matrix.m[0][1] = cp * sy;
	matrix.m[0][2] = sp;
	matrix.m[0][3] = 0.f;
	matrix.m[1][0] = sr * sp * cy - cr * sy;
	matrix.m[1][1] = sr * sp * sy + cr * cy;
	matrix.m[1][2] = -sr * cp;
	matrix.m[1][3] = 0.f;
	matrix.m[2][0] = -(cr * sp * cy + sr * sy);
	matrix.m[2][1] = cy * sr - cr * sp * sy;
	matrix.m[2][2] = cr * cp;
	matrix.m[2][3] = 0.f;
	matrix.m[3][0] = origin.x;
	matrix.m[3][1] = origin.y;
	matrix.m[3][2] = origin.z;
	matrix.m[3][3] = 1.f;
	return matrix;
}
inline void DrawBox(float x, float y, float w, float h, const ImColor& color, float thickness, BoxType type, bool outlined = false)
{
	ImDrawList* draw = ImGui::GetBackgroundDrawList();

	if (outlined)
	{
		const ImColor outlineColor = IM_COL32(0, 0, 0, 255);
		const float outlineThickness = thickness + 1.5f;
		const float offset = 1.5f;

		switch (type)
		{
		case CORNER:
		{
			const float lw = w * 0.33f;
			const float lh = h * 0.33f;

			draw->AddLine(ImVec2(x - offset, y - offset), ImVec2(x + lw + offset, y - offset), outlineColor, outlineThickness);
			draw->AddLine(ImVec2(x - offset, y - offset), ImVec2(x - offset, y + lh + offset), outlineColor, outlineThickness);

			draw->AddLine(ImVec2(x + w - lw - offset, y - offset), ImVec2(x + w + offset, y - offset), outlineColor, outlineThickness);
			draw->AddLine(ImVec2(x + w + offset, y - offset), ImVec2(x + w + offset, y + lh + offset), outlineColor, outlineThickness);

			draw->AddLine(ImVec2(x - offset, y + h - lh - offset), ImVec2(x - offset, y + h + offset), outlineColor, outlineThickness);
			draw->AddLine(ImVec2(x - offset, y + h + offset), ImVec2(x + lw + offset, y + h + offset), outlineColor, outlineThickness);

			draw->AddLine(ImVec2(x + w - lw - offset, y + h + offset), ImVec2(x + w + offset, y + h + offset), outlineColor, outlineThickness);
			draw->AddLine(ImVec2(x + w + offset, y + h - lh - offset), ImVec2(x + w + offset, y + h + offset), outlineColor, outlineThickness);
			break;
		}
		case OUTLINE:
		case FILLED:
			draw->AddRect(ImVec2(x - offset, y - offset), ImVec2(x + w + offset, y + h + offset), outlineColor, 0.0f, 0, outlineThickness);
			break;
		}
	}

	switch (type)
	{
	case CORNER:
	{
		const float lw = w * 0.33f;
		const float lh = h * 0.33f;

		draw->AddLine(ImVec2(x, y), ImVec2(x + lw, y), color, thickness);
		draw->AddLine(ImVec2(x, y), ImVec2(x, y + lh), color, thickness);
		draw->AddLine(ImVec2(x + w - lw, y), ImVec2(x + w, y), color, thickness);
		draw->AddLine(ImVec2(x + w, y), ImVec2(x + w, y + lh), color, thickness);

		draw->AddLine(ImVec2(x, y + h - lh), ImVec2(x, y + h), color, thickness);
		draw->AddLine(ImVec2(x, y + h), ImVec2(x + lw, y + h), color, thickness);
		draw->AddLine(ImVec2(x + w - lw, y + h), ImVec2(x + w, y + h), color, thickness);
		draw->AddLine(ImVec2(x + w, y + h - lh), ImVec2(x + w, y + h), color, thickness);
		break;
	}
	case OUTLINE:
		draw->AddRect(ImVec2(x, y), ImVec2(x + w, y + h), color, 0.0f, 0, thickness);
		break;

	case FILLED:
	{
		ImVec4 colorValue = color.Value;
		draw->AddRectFilled(ImVec2(x, y), ImVec2(x + w, y + h),
			IM_COL32(
				static_cast<int>(colorValue.x * 255),
				static_cast<int>(colorValue.y * 255),
				static_cast<int>(colorValue.z * 255),
				64 
			)
		);
		draw->AddRect(ImVec2(x, y), ImVec2(x + w, y + h), color, 0.0f, 0, thickness);
		break;
	}
	}
}

inline void DrawTextOutlined(const char* text, ImVec2 pos, float size, ImColor color)
{
	ImDrawList* draw = ImGui::GetBackgroundDrawList();
	ImFont* font = ImGui::GetFont();

	const float outlineOffset = 1.0f;
	const ImU32 outlineColor = IM_COL32(0, 0, 0, 200);

	draw->AddText(font, size, ImVec2(pos.x - outlineOffset, pos.y), outlineColor, text);
	draw->AddText(font, size, ImVec2(pos.x + outlineOffset, pos.y), outlineColor, text);
	draw->AddText(font, size, ImVec2(pos.x, pos.y - outlineOffset), outlineColor, text);
	draw->AddText(font, size, ImVec2(pos.x, pos.y + outlineOffset), outlineColor, text);

	draw->AddText(font, size, pos, color, text);
}

bool is_in_screen(Vector2 screen_location)
{
	return screen_location.x > 0 && screen_location.x < Width &&
		screen_location.y > 0 && screen_location.y < Height;
}

void circle(const Vector2& pos, float radius, ImU32 color, int segments = 64, float thickness = 1.0f) {
	ImGui::GetBackgroundDrawList()->AddCircle(ImVec2(pos.x, pos.y), radius, color, segments, thickness);
}

void draw_fov_circle(int fov_size, ImColor color, float thickness = 1.0f, int segments = 64) {
	ImVec2 screen_size = ImGui::GetIO().DisplaySize;

	Vector2 center(screen_size.x / 2.0f, screen_size.y / 2.0f);

	circle(center, (float)fov_size, color, segments, thickness);
}