// -----------------------------------------------------------------------------
// triggerbot.cpp
// -----------------------------------------------------------------------------
// Implementation of the triggerbot. While the trigger key is held and the
// crosshair rests on a valid enemy, the thread waits the configured delay,
// re-checks the target, snaps the view to the selected aim bone and fires.
// The trigger key must not be the left mouse button itself (that would
// conflict with normal aiming).
// -----------------------------------------------------------------------------

#include "triggerbot.h"
#include "aimhelper.h"

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

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

namespace features {

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

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

	while (true) {
		if (!cfg::g_config.triggerEnabled) {
			Sleep(500);
			continue;
		}

		if (!util::IsKeyDown(cfg::g_config.triggerKey) || cfg::g_config.triggerKey == 1) {
			Sleep(5);
			continue;
		}

		const uintptr_t meBase = self->m_player->GetLocalPawn();
		if (meBase == 0 || self->m_memory->Read<uint8_t>(meBase + self->m_offsets->lifeState) != 0) {
			Sleep(10);
			continue;
		}

		const int crosshairId = self->m_player->GetCrosshairId();
		Sleep(cfg::g_config.triggerDelayMs);
		if (!util::IsKeyDown(cfg::g_config.triggerKey)) {
			Sleep(5);
			continue;
		}

		if (crosshairId < 0) {
			util::LogDebug("[triggerbot] no target in crosshair");
		} else if (!self->m_entities->IsValidEnemy(crosshairId, self->m_player->GetTeam())) {
			const uintptr_t pawn = self->m_entities->GetPawn(crosshairId);
			std::stringstream ss;
			ss << "[triggerbot] reject target=" << crosshairId
				<< " pawn=0x" << std::hex << pawn
				<< " class=" << (crosshairId >= sdk::kMaxPlayers ? self->m_entities->GetEntityClassName(crosshairId) : "<controller>")
				<< " team=" << std::dec << (pawn != 0 ? self->m_memory->Read<int>(pawn + self->m_offsets->teamNum) : -1)
				<< " hp=" << (pawn != 0 ? self->m_memory->Read<int>(pawn + self->m_offsets->health) : -1)
				<< " life=" << (pawn != 0 ? static_cast<int>(self->m_memory->Read<uint8_t>(pawn + self->m_offsets->lifeState)) : -1);
			util::LogDebug(ss.str());
		} else {
			std::stringstream ss;
			ss << "[triggerbot] fire target=" << crosshairId;
			util::LogDebug(ss.str());

			if (self->AimAtSelectedBone(crosshairId)) {
				Sleep(20);
			}
			Click();
		}

		Sleep(5);
	}
	return 0;
}

bool Triggerbot::AimAtSelectedBone(int slot)
{
	const uintptr_t meBase = m_player->GetLocalPawn();
	const int aimBone = cfg::g_config.GetAimBone();
	if (meBase == 0 || aimBone < 0) {
		return false;
	}

	sdk::Vec3 myPos;
	if (!m_player->GetPosition(myPos)) {
		return false;
	}

	sdk::Vec3 enemyPos;
	if (!m_entities->GetBonePosition(slot, aimBone, enemyPos)) {
		if (!m_entities->GetOrigin(slot, enemyPos)) {
			return false;
		}
		enemyPos.z += aim::FallbackBoneHeight(
			aimBone, cfg::g_config.boneHead, cfg::g_config.boneStomach);
	}

	const uintptr_t enemyPawn = m_entities->GetPawn(slot);
	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(enemyPos, enemyVelocity, myVelocity, cfg::g_config.smoothAmount);

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

	std::stringstream ss;
	ss << "[triggerbot] aim target=" << slot << " bone=" << aimBone
		<< " angle=(" << aimAngle.x << "," << aimAngle.y << "," << aimAngle.z << ")";
	util::LogDebug(ss.str());
	return true;
}

void Triggerbot::Click()
{
	mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
	Sleep(1);
	mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
	Sleep(1);
}

} // namespace features