// -----------------------------------------------------------------------------
// wallhack.cpp
// -----------------------------------------------------------------------------
// Implementation of the glow wallhack. Mirrors the working Python example
// (glennf, UC "CS2 Glow in Python") but with a cached two-tier loop to keep
// the CPU cost low: the 64-slot controller resolution runs only 4x per
// second (RefreshPawns); the hot repaint path (Repaint) re-validates each
// cached pawn with one read and writes exactly six fields per pawn:
// m_glowColorOverride (packed uint32, little-endian), m_bGlowing,
// m_iGlowType and the glow timer pins. Team colors: friendly = blue,
// enemy = red, flashed enemy = white, defusing = white.
// -----------------------------------------------------------------------------

#include "wallhack.h"

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

#pragma comment(lib, "winmm.lib")

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

namespace features {

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

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

	if (self->m_offsets->glow == 0) {
		util::LogFatal("[wallhack] m_Glow unresolved, feature disabled");
		return 1;
	}

	timeBeginPeriod(1);
	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

	while (true) {
		if (cfg::g_config.glowLevel == cfg::GlowLevel::Off) {
			Sleep(500);
			continue;
		}

		const uintptr_t localPawn = self->m_player->GetLocalPawn();
		if (localPawn == 0) {
			Sleep(100);
			continue;
		}

		if (GetTickCount() - self->m_lastRefresh > 250) {
			self->m_lastRefresh = GetTickCount();
			self->RefreshPawns(localPawn);
		}

		self->Repaint(localPawn);
		Sleep(10);
	}

	timeEndPeriod(1);
	return 0;
}

void Wallhack::RefreshPawns(uintptr_t localPawn)
{
	m_localTeam = m_player->GetTeam();
	m_pawns.clear();

	for (int i = 0; i < sdk::kMaxPlayers; i++) {
		uintptr_t pawn = 0;
		uintptr_t identity = 0;
		if (!m_entities->GetPawnWithIdentity(i, pawn, identity)) {
			continue;
		}
		if (pawn == localPawn) {
			continue;
		}

		const int team = m_memory->Read<int>(pawn + m_offsets->teamNum);
		if (team != 2 && team != 3) {
			continue;
		}

		if (cfg::g_config.glowLevel == cfg::GlowLevel::Enemy && team == m_localTeam) {
			continue;
		}
		if (cfg::g_config.glowLevel == cfg::GlowLevel::Team && team != m_localTeam) {
			continue;
		}

		const int health = m_memory->Read<int>(pawn + m_offsets->health);
		if (health <= 0 || health > 100) {
			continue;
		}
		if (m_memory->Read<uint8_t>(pawn + m_offsets->lifeState) != 0) {
			continue;
		}

		m_pawns.push_back(CachedPawn{ pawn, identity, team });
	}
}

void Wallhack::Repaint(uintptr_t localPawn)
{
	for (const CachedPawn& cached : m_pawns) {
		if (cached.pawn == localPawn) {
			continue;
		}
		if (m_memory->Read<uintptr_t>(cached.identity) != cached.pawn) {
			continue;
		}
		PaintGlow(cached.pawn, cached.team, m_localTeam);
	}
}

void Wallhack::PaintGlow(uintptr_t pawn, int team, int localTeam)
{
	const uintptr_t glow = pawn + m_offsets->glow;

	float r, g, b;

	bool defusing = false;
	if (m_offsets->isDefusing != 0) {
		defusing = m_memory->Read<uint8_t>(pawn + m_offsets->isDefusing);
	}
	if (defusing) {
		r = 1.0f;
		g = 1.0f;
		b = 1.0f;
	} else if (team == localTeam) {
		r = 0.0f;
		g = 0.0f;
		b = 1.0f;
	} else {
		bool flashed = false;
		if (m_offsets->flashDuration != 0) {
			const float flash = m_memory->Read<float>(pawn + m_offsets->flashDuration);
			flashed = (flash > 2.0f && flash < 10.0f);
		}
		if (flashed) {
			r = 1.0f;
			g = 1.0f;
			b = 1.0f;
		} else {
			r = 1.0f;
			g = 0.0f;
			b = 0.0f;
		}
	}

	auto clamp = [](float v) { return v < 0.0f ? 0.0f : (v > 255.0f ? 255.0f : v); };
	auto to_argb = [clamp](float r, float g, float b, float a) -> uint32_t {
		return (static_cast<uint32_t>(clamp(r))) |
			(static_cast<uint32_t>(clamp(g)) << 8) |
			(static_cast<uint32_t>(clamp(b)) << 16) |
			(static_cast<uint32_t>(clamp(a)) << 24);
	};

	const uint32_t packedColor = to_argb(r * 255.0f, g * 255.0f, b * 255.0f, 1.0f * 255.0f);

	m_memory->Write<uint32_t>(glow + m_offsets->glowColorOverride, packedColor);
	m_memory->Write<bool>(glow + m_offsets->glowGlowing, true);
	m_memory->Write<int>(glow + m_offsets->glowType, 3);

	m_memory->Write<float>(glow + m_offsets->glowTime, FLT_MAX);
	m_memory->Write<float>(glow + m_offsets->glowStartTime, 0.0f);
	m_memory->Write<bool>(glow + m_offsets->glowFlashing, false);

	std::stringstream ss;
	ss << "[wallhack] paint pawn=0x" << std::hex << pawn << " glow=0x" << glow
		<< " color=0x" << packedColor << " team=" << std::dec << team
		<< " localTeam=" << localTeam << " defusing=" << (defusing ? 1 : 0)
		<< " defuseOffset=0x" << std::hex << m_offsets->isDefusing;
	util::LogDebug(ss.str());
}

} // namespace features