// -----------------------------------------------------------------------------
// forceaim.cpp
// -----------------------------------------------------------------------------
// Implementation of the force-aim feature. While ALT is held the thread
// projects every valid enemy onto the screen with the view matrix and aims
// at the one closest to the crosshair. Only enemies inside the active area
// are eligible: the screen is treated as a 4x4 grid and the middle 3x3
// block (12.5%..87.5% of both axes) is active. If the area is empty,
// nothing happens. The target is resolved through the controller ->
// m_hPlayerPawn chain on every frame.
// -----------------------------------------------------------------------------

#include "forceaim.h"
#include "aimhelper.h"

#include <windows.h>
#include <cmath>
#include <sstream>

#include "../config.h"
#include "../util/input.h"
#include "../util/logger.h"

namespace features {

void ForceAim::Initialize(const core::ProcessMemory* memory, const core::GameOffsets* offsets,
	const sdk::EntityList* entities, const sdk::LocalPlayer* player)
{
	m_memory = memory;
	m_offsets = offsets;
	m_entities = entities;
	m_player = player;
}

void ForceAim::Start()
{
	CreateThread(nullptr, 0, &ForceAim::ThreadProc, this, 0, nullptr);
}

unsigned long __stdcall ForceAim::ThreadProc(void* param)
{
	ForceAim* self = static_cast<ForceAim*>(param);
	if (self == nullptr) {
		return 1;
	}

	while (true) {
		if (!util::IsKeyDown(cfg::g_config.forceAim2Key)) {
			Sleep(10);
			continue;
		}

		const uintptr_t meBase = self->m_player->GetLocalPawn();
		if (meBase == 0) {
			util::LogDebug("[forceaim] no local pawn");
			Sleep(10);
			continue;
		}

		if (self->m_memory->Read<uint8_t>(meBase + self->m_offsets->lifeState) != 0) {
			util::LogDebug("[forceaim] dead, skipping");
			Sleep(10);
			continue;
		}

		const int myTeam = self->m_player->GetTeam();
		int aimBone = cfg::g_config.GetAimBone();
		if (aimBone < 0) {
			aimBone = cfg::g_config.boneHead;
		}
		if (aimBone < 0) {
			util::LogDebug("[forceaim] no aim bone resolved");
			Sleep(10);
			continue;
		}

		sdk::Vec3 myPos;
		if (!self->m_player->GetPosition(myPos)) {
			util::LogDebug("[forceaim] cannot read own position");
			Sleep(10);
			continue;
		}
		const float myEyeZ = myPos.z + self->m_player->GetViewOrigin();

		const int screenW = GetSystemMetrics(SM_CXSCREEN);
		const int screenH = GetSystemMetrics(SM_CYSCREEN);
		const float crossX = screenW * 0.5f;
		const float crossY = screenH * 0.5f;
		const float regionMinX = screenW * 0.125f;
		const float regionMaxX = screenW * 0.875f;
		const float regionMinY = screenH * 0.125f;
		const float regionMaxY = screenH * 0.875f;

		float viewMatrix[16] = {};
		const bool hasViewMatrix = (self->m_offsets->viewMatrix != 0);
		if (hasViewMatrix) {
			self->m_memory->ReadArray<float>(
				self->m_player->GetClientBase() + self->m_offsets->viewMatrix, viewMatrix, 16);
		}

		int targetId = -1;
		sdk::Vec3 targetPos{};
		float bestScore = 1e30f;
		for (int i = 0; i < sdk::kMaxPlayers; i++) {
			const uintptr_t pawn = self->m_entities->GetPawn(i);
			if (pawn == 0 || pawn == meBase) {
				continue;
			}

			if (!self->m_entities->IsValidPawn(pawn, myTeam)) {
				continue;
			}

			sdk::Vec3 candidatePos;
			if (!self->m_entities->GetBonePosition(i, aimBone, candidatePos)) {
				if (!self->m_entities->GetOrigin(i, candidatePos)) {
					continue;
				}
				candidatePos.z += aim::FallbackBoneHeight(
					aimBone, cfg::g_config.boneHead, cfg::g_config.boneStomach);
			}

			if (hasViewMatrix) {
				float sx, sy;
				if (!aim::WorldToScreen(viewMatrix, candidatePos, screenW, screenH, sx, sy)) {
					continue;
				}
				if (sx < regionMinX || sx > regionMaxX ||
					sy < regionMinY || sy > regionMaxY) {
					continue;
				}

				const float dx = sx - crossX;
				const float dy = sy - crossY;
				const float score = dx * dx + dy * dy;
				if (score < bestScore) {
					bestScore = score;
					targetId = i;
					targetPos = candidatePos;
				}
			} else {
				const float dx = myPos.x - candidatePos.x;
				const float dy = myPos.y - candidatePos.y;
				const float dz = myEyeZ - candidatePos.z;
				const float score = std::sqrt(dx * dx + dy * dy + dz * dz);
				if (score < bestScore) {
					bestScore = score;
					targetId = i;
					targetPos = candidatePos;
				}
			}
		}

		if (targetId < 0) {
			util::LogDebug("[forceaim] no enemy in active screen area");
			Sleep(10);
			continue;
		}

		{
			std::stringstream ss;
			ss << "[forceaim] target=" << targetId << " bone=" << aimBone
				<< " score=" << bestScore;
			util::LogDebug(ss.str());

			const uintptr_t enemyPawn = self->m_entities->GetPawn(targetId);
			sdk::Vec3 enemyVelocity;
			sdk::Vec3 myVelocity;
			if (enemyPawn != 0) {
				self->m_memory->ReadArray<float>(enemyPawn + self->m_offsets->vecVelocity, &enemyVelocity.x, 3);
			}
			self->m_memory->ReadArray<float>(meBase + self->m_offsets->vecVelocity, &myVelocity.x, 3);
			aim::ApplyVelocityCompensation(targetPos, enemyVelocity, myVelocity, cfg::g_config.smoothAmount);

			const sdk::Vec3 aimAngle = aim::CalculateAngle(myPos, targetPos, self->m_player->GetViewOrigin());
			sdk::Vec3 currentAngles;
			if (self->m_player->GetAngles(currentAngles)) {
				const float factor = static_cast<float>(cfg::g_config.smoothAmount) / 100.0f;
				self->m_player->SetAngles(aim::SmoothedStep(currentAngles, aimAngle, factor));
			} else {
				self->m_player->SetAngles(aimAngle);
			}
		}

		Sleep(10);
	}
	return 0;
}

} // namespace features