#include "aim.h"
#include "../../sdk/interfaces/interfaces.hpp"
#include "random"
#include "windows.h"
#include <iostream>
#include "../../sdk/source2-sdk/classes/cusercmd.h"

class MemoryManagement {
public:
	template <class T>
	T ReadMem(uintptr_t addr) {
		T x;
		ReadProcessMemory(GetCurrentProcess(), (LPBYTE*)addr, &x, sizeof(x), NULL);
		return x;
	}

	bool ReadRawMem(uintptr_t addr, void* buffer, size_t size) {
		SIZE_T bytesRead;
		if (ReadProcessMemory(GetCurrentProcess(), reinterpret_cast<LPCVOID>(addr), buffer, size, &bytesRead)) {
			return bytesRead == size;
		}
		return false;
	}
};
MemoryManagement MemMan;

inline Vector CalculateAngle(
	Vector& localPosition,
	Vector& enemyPosition,
	Vector& viewAngles) noexcept
{
	return (enemyPosition - localPosition).ToAngle() - viewAngles;
};

inline Vector calculateBestAngle(Vector angle, Vector configFov) {
	Vector newAngle{};

	float calcFov = std::hypot(angle.x + configFov.x, angle.y + configFov.y);
	float fov = configFov.z;

	if (calcFov < fov) {
		calcFov = fov;
		newAngle = angle;
	}
	return newAngle;
}

static CCSPlayerController* g_pLocalPlayerController = nullptr;
static C_CSPlayerPawn* g_pLocalPlayerPawn = nullptr;

QAngle_t GetRecoil(CBaseUserCmdPB* pCmd, C_CSPlayerPawn* pLocal)
{
	static QAngle_t OldPunch;//get last tick AimPunch angles
	if (pLocal->GetShotsFired() >= 1)//only update aimpunch while shooting
	{
		QAngle_t viewAngles = pCmd->pViewangles->angValue;
		QAngle_t delta = viewAngles - (viewAngles + (OldPunch - (pLocal->GetAimPuchAngle() * 2.f)));//get current AimPunch angles delta

		return pLocal->GetAimPuchAngle() * 2.0f;//return correct aimpunch delta
	}
	else
	{
		return QAngle_t{ 0, 0 ,0 };//return 0 if is not shooting
	}
}

using namespace std;

void aim::aimBot(C_CSPlayerPawn* enemyPlayer, uintptr_t boneArray, CUserCmd* pCmd, CBaseUserCmdPB* pBaseCmd) {
	if (!interfaces::pEngine->IsInGame())
		return;

	g_pLocalPlayerController = CGameEntitySystem::GetLocalPlayerController();
	if (!g_pLocalPlayerController)
		return;

	g_pLocalPlayerPawn = g_pLocalPlayerController->m_hPawn().Get<C_CSPlayerPawn>();
	if (!g_pLocalPlayerPawn)
		return;

	if (g_pLocalPlayerPawn->m_iHealth() <= 0)
		return;

	if (!pBaseCmd->pViewangles)
		return;

	if (pBaseCmd->pViewangles->angValue.IsZero())
		return;

	//if (aim::checkSpotted) {
	//	GameTrace_t trace = GameTrace_t();
	//	TraceFilter_t filter = TraceFilter_t(0x1C1003, g_pLocalPlayerPawn, nullptr, 4);
	//	Ray_t ray = Ray_t();
	//	interfaces::pTraceMgr->TraceShape2(&ray, enemyPlayer->m_pGameSceneNode()->GetSkeletonInstance()->m_bone_cache->GetOrigin(6), g_pLocalPlayerPawn->GetEyePosition(), &filter, &trace);
	//	if (trace.m_pHitEntity != enemyPlayer || !trace.IsVisible())
	//		return;
	//}

	if (aim::checkSpotted)
	{
		TraceFilter_t filter(0x1C3003, g_pLocalPlayerPawn, nullptr, 4);
		Ray_t ray = Ray_t();
		GameTrace_t trace = GameTrace_t();

		interfaces::pTraceMgr->TraceShape2(&ray, g_pLocalPlayerPawn->GetEyePosition(), (Vector)enemyPlayer->m_pGameSceneNode()->getBoneArray(), &filter, &trace);

		if ((trace.IsVisible() || (trace.m_pHitEntity && trace.m_pHitEntity->GetRefEHandle().GetEntryIndex() == enemyPlayer->GetRefEHandle().GetEntryIndex())))// && !(bSmokeCheck && !Utilities::LineGoesThroughSmoke(vecSource, vecPoint, flMaxDensity)))
			return;
	}

	Vector aimPos;
	Vector newAngle;
	Vector angle;

	if (playerLock)
		if (lockedPlayer != 0 && lockedPlayer != enemyPlayer) return;
	if (enemyPlayer == g_pLocalPlayerPawn) {
		lockedPlayer = 0;
		return;
	}

	aimPos = MemMan.ReadMem<Vector>(boneArray + boneMap[bones[boneSelect]] * 32);
	Vector angValue = Vector(pBaseCmd->pViewangles->angValue.x, pBaseCmd->pViewangles->angValue.y, pBaseCmd->pViewangles->angValue.z);
	auto eyePos = g_pLocalPlayerPawn->GetEyePosition();
	angle = CalculateAngle(eyePos, aimPos, angValue);
	newAngle = calculateBestAngle(angle, { 0, 0, fov });
	auto aimPunch = GetRecoil(pBaseCmd, g_pLocalPlayerPawn);

	//if (rcs) {

	//	//Vector oldAngles = angValue;
	//	static Vector oldAngles = { 0, 0, 0 };
	//	Vector rcs = { 0, 0, 0 };

	//	if (g_pLocalPlayerPawn->GetShotsFired() > 1 && g_pLocalPlayerPawn->GetShotsFired() < 9999 /* Spectator check */) {
	//		auto qAimPunc = g_pLocalPlayerPawn->GetAimPuchAngle();
	//		Vector aimPunch = Vector(qAimPunc.x, qAimPunc.y, qAimPunc.z);
	//		rcs.x = (aimPunch.x - oldAngles.x) * 2.f / (0.022f * sens);
	//		rcs.y = (aimPunch.y - oldAngles.y) * 2.f / (0.022f * sens);

	//		oldAngles.x = aimPunch.y;
	//		oldAngles.y = aimPunch.x;
	//	}
	//	newAngle = calculateBestAngle(angle, { rcs.x, rcs.y, fov });
	//};

	//newAngle.x = (newAngle.x / (0.022f * sens)) / smoothing;
	//newAngle.y = (newAngle.y / (0.022f * sens)) / smoothing;
	if (rcs) {
		if (!newAngle.IsZero()) {
			newAngle.x += ((newAngle.x - aimPunch.x) / (0.022f * sens)) / smoothing;// minus AimPunch angle to counteract recoil
			newAngle.y += ((newAngle.y - aimPunch.y) / (0.022f * sens)) / smoothing;
			newAngle.Normalize();
		}
	}
	else {
		newAngle.x = (newAngle.x / (0.022f * sens)) / smoothing;
		newAngle.y = (newAngle.y / (0.022f * sens)) / smoothing;
	}

	if (newAngle.IsZero()) {
		lockedPlayer = 0;
		return;
	}

	//newAngle.Clamp();

	if (GetAsyncKeyState(VK_LBUTTON)) {
		aim::moveMouseToLocation(newAngle);
	}
	else if (alaimlock) {
		aim::moveMouseToLocation(newAngle);
	}

	lockedPlayer = enemyPlayer;
}

void aim::moveMouseToLocation(Vector pos) {
	if (pos.x == 0.f && pos.y == 0.f && pos.z == 0.f) return;

	auto new_x = -pos.y;
	auto new_y = pos.x;

	mouse_event(MOUSEEVENTF_MOVE, new_x, new_y, 0, 0);
}

Vector aim::recoilControl(bool move, C_CSPlayerPawn* localPawn) {
	if (!localPawn)
		return {};

	//if (localPawn->m_iHealth() <= 0)
		//return {};

	localPawn->GetAimPuchAngleCache();
	localPawn->getViewAngles();

	static Vector oldAngles = { 0, 0, 0 };
	Vector newAngles = { 0, 0, 0 };

	if (localPawn->GetShotsFired() == 54587654) return newAngles; // Spectator check

	if (localPawn->GetShotsFired() > 1) {
		auto qAimPunc = localPawn->GetAimPuchAngle();
		Vector aimPunch = Vector(qAimPunc.x, qAimPunc.y, qAimPunc.z);
		newAngles.x = (aimPunch.x - oldAngles.x) * 2.f / (0.022f * sens);
		newAngles.y = (aimPunch.y - oldAngles.y) * 2.f / (0.022f * sens);

		if (move) aim::moveMouseToLocation(newAngles * -1);

		oldAngles = aimPunch;
		return newAngles;
	}
	else {
		oldAngles = { 0, 0, 0 };
		return newAngles;
	}
}

bool clicked = false;

const int trigger_cooldown()
{
	// Generate a random float between 0.0 and 0.5, add 0.15F to it, then cast to int milliseconds
	return static_cast<int>((static_cast<float>(rand() % 50) / 100.0F + 0.15F) * 1000);
}