// -----------------------------------------------------------------------------
// flickshot.cpp
// -----------------------------------------------------------------------------
// Implementation of the flickshot. The thread watches CAPS LOCK with edge
// detection; on a fresh press it picks the enemy closest to the crosshair
// (same selection as force aim: the screen is treated as a 4x4 grid and
// only enemies inside the middle 3x3 block, 12.5%..87.5% of both axes, are
// eligible; 3D-distance fallback when no view matrix is available), snaps
// the view to the selected aim bone, waits for the game to apply the angle
// and fires one shot.
// -----------------------------------------------------------------------------

#include "flickshot.h"
#include "aimhelper.h"

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

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

namespace features {

void Flickshot::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 Flickshot::Start()
{
	CreateThread(nullptr, 0, &Flickshot::ThreadProc, this, 0, nullptr);
}

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

	while (true) {
		const bool down = util::IsKeyDown(cfg::g_config.flickshotKey);
		if (down == self->m_lastDown) {
			Sleep(5);
			continue;
		}
		self->m_lastDown = down;
		if (!down) {
			Sleep(5);
			continue;
		}

		util::LogDebug("[flickshot] triggered");
		self->ExecuteFlick();

		while (util::IsKeyDown(cfg::g_config.flickshotKey)) {
			Sleep(5);
		}
		self->m_lastDown = false;
	}
	return 0;
}

void Flickshot::ExecuteFlick()
{
	const uintptr_t meBase = m_player->GetLocalPawn();
	if (meBase == 0) {
		util::LogDebug("[flickshot] no local pawn");
		return;
	}

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

	int aimBone = cfg::g_config.GetAimBone();
	if (aimBone < 0) {
		aimBone = cfg::g_config.boneHead;
	}
	if (aimBone < 0) {
		util::LogDebug("[flickshot] no aim bone resolved");
		return;
	}

	int bone = aimBone;
	if (cfg::g_config.aimMode == cfg::AimMode::Random) {
		bone = PickRandomBone();
	}

	const int myTeam = m_player->GetTeam();
	sdk::Vec3 myPos;
	if (!m_player->GetPosition(myPos)) {
		util::LogDebug("[flickshot] cannot read own position");
		return;
	}
	const float myEyeZ = myPos.z + 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 = (m_offsets->viewMatrix != 0);
	if (hasViewMatrix) {
		m_memory->ReadArray<float>(
			m_player->GetClientBase() + 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 = m_entities->GetPawn(i);
		if (pawn == 0 || pawn == meBase) {
			continue;
		}

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

		sdk::Vec3 candidatePos;
		if (!m_entities->GetBonePosition(i, bone, candidatePos)) {
			if (!m_entities->GetOrigin(i, candidatePos)) {
				continue;
			}
			candidatePos.z += aim::FallbackBoneHeight(
				bone, 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("[flickshot] no enemy in active screen area");
		return;
	}

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

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

		const sdk::Vec3 aimAngle = aim::CalculateAngle(myPos, targetPos, m_player->GetViewOrigin());

		const float factor = static_cast<float>(cfg::g_config.smoothAmount) / 100.0f;
		sdk::Vec3 currentAngles;
		if (m_player->GetAngles(currentAngles)) {
			for (int i = 0; i < 5; i++) {
				currentAngles = aim::SmoothedStep(currentAngles, aimAngle, factor);
				m_player->SetAngles(currentAngles);
				Sleep(5);
			}
			m_player->SetAngles(aimAngle);
			Sleep(5);
		} else {
			m_player->SetAngles(aimAngle);
		}
	}

	mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
	Sleep(15);
	mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
	Sleep(1);

	std::stringstream ss;
	ss << "[flickshot] fired on target=" << targetId;
	util::LogDebug(ss.str());
}

int Flickshot::PickRandomBone()
{
	static bool seeded = false;
	if (!seeded) {
		srand(static_cast<unsigned>(time(nullptr)));
		seeded = true;
	}

	const int pick = rand() % 4;
	switch (pick) {
		case 0: return cfg::g_config.boneHead;
		case 1: return cfg::g_config.boneChest;
		case 2: return cfg::g_config.boneStomach;
		case 3: return cfg::g_config.boneChest;
		default: return cfg::g_config.boneHead;
	}
}

} // namespace features
