#ifndef AIMBOT_CPP
#define AIMBOT_CPP
#include <core/inc.hpp>

#include <core/game/features/controller/controller.hpp>

auto cheat_interface::c_aimbot::find_look_at_rotation( math::vector3& start, math::vector3& target ) -> math::vector3 {

	math::vector3 direction = target - start;

	auto yaw = atan2( direction.y, direction.x ) * ( 180.0 / std::numbers::pi );
	auto pitch = -( ( acos( direction.z / start.distance( target ) ) * 180.0f / std::numbers::pi ) - 90.0f );

	return math::vector3( pitch, yaw, 0.0 );
}

auto cheat_interface::c_aimbot::get_best_bone( int bone_id ) -> int
{
	switch ( bone_id ) {
	case 0:
		return enums::c_bones::head;
		break;

	case 1:
		return enums::c_bones::neck;
		break;

	case 2:
		return enums::c_bones::chest;
		break;

	case 3:
		return enums::c_bones::pelvis;
		break;

	case 4:
		return enums::c_bones::left_shoulder;
		break;

	case 5:
		return enums::c_bones::right_shoulder;
		break;

	case 6:
		return enums::c_bones::left_knee;
		break;

	case 7:
		return enums::c_bones::right_knee;
		break;
	}

	return enums::c_bones::head;
}


auto cheat_interface::c_aimbot::aim_lock( math::vector3 aim_location ) -> void
{
	if ( !g_ctx->view_state || !g_ctx->localplayer.player_controller )
		return;

	auto aim_rotation = find_look_at_rotation( g_engine->location, aim_location );
	if ( !aim_rotation.is_valid( ) ) 
		return;

	math::vector3 rotation = math::vector3( g_engine->rotation.x,
		g_engine->rotation.y, g_engine->rotation.z
	);

	aim_rotation.relative( rotation );
	aim_rotation = aim_rotation.normalize( );

	auto local_view_angles = g_ctx->localplayer.player_controller->get_view_angles( );

	auto delta = math::vector3( aim_rotation - local_view_angles );

	auto smoothed_angles = math::vector3(
		local_view_angles + ( delta / g_vars->aim.smooth ) ).normalize( );

	g_ctx->localplayer.player_controller->weapon_offset_correction( game::a_weapon_correction_rotator( smoothed_angles.x, smoothed_angles.y ) );
}

auto cheat_interface::c_aimbot::run_aimbot( ) -> void
{
	if ( !g_vars->aim.enabled ) 
		return;

	auto closest_player = object::c_closest( );
	auto target_distance = flt_max;

	for ( auto player : g_ctx->m_players )
	{
		if ( !player.m_mesh ) 
			return;

		if ( player.m_actor == g_ctx->localplayer.player_pawn ) 
			continue;

		if ( g_vars->aim.ignore_team && ( !g_ctx->in_lobby && player.m_is_teammate ) ) 
			continue;

		bool is_dying = player.m_actor->is_dying( );
		if ( is_dying ) 
			continue;

		if ( g_vars->aim.ignore_knocked && player.is_dbno ) 
			continue;

		if ( g_vars->aim.ignore_invisible && player.is_visible ) 
			continue;

		auto transform = player.m_mesh->bone_array( ).get_itter( );
		if ( !transform.data( ) )
			continue;

		auto head_bone = player.m_mesh->get_bone_location( game::gve<engine::transform>( transform, 110 ) );
		if ( !head_bone.is_valid( ) ) 
			continue;

		auto head_screen = g_engine->world_to_screen( head_bone );

		if ( !g_engine->in_screen( head_screen ) )
			continue;

		auto dx = head_screen.x - ( g_render->width_center );
		auto dy = head_screen.y - ( g_render->height_center );
		auto distance_square = sqrtf( dx * dx + dy * dy );

		if ( distance_square < g_vars->aim.fov_c && distance_square < target_distance )
		{
			target_distance = distance_square;
			closest_player.m_mesh = player.m_mesh;
			closest_player.m_actor = player.m_actor;
		}
	}

	if ( closest_player.m_mesh )
	{
		auto transform = closest_player.m_mesh->bone_array( ).get_itter( );
		if ( !transform.data( ) ) 
			return;
	
		auto bone = closest_player.m_mesh->get_bone_location( game::gve<engine::transform>( transform, get_best_bone( g_vars->aim.target_bone ) ) );
		if ( !bone.is_valid( ) ) 
			return;

		if ( g_vars->aim.prediction )
		{
			auto do_prediction = [ & ]() -> void
			{
				prediction::prediction_context context;
				context.animation_type = g_ctx->localplayer.animation_type;

				if ( !g_prediction->is_weapon( context ) )
					return;

				auto __speed = g_ctx->localplayer.weapon->bullet_speed();
				auto __gravity = g_ctx->localplayer.weapon->bullet_gravity();

				if ( __speed <= 0 || __gravity <= 0 )
					return;

				auto root_component = closest_player.m_actor->root_component();
				if ( !root_component ) 
					return;

				auto velocity = closest_player.m_root_component->component_velocity();
				auto distance = g_engine->location.distance( bone );

				prediction::prediction_context ctx;

				ctx.distance = distance;
				ctx.position = bone;
				ctx.player_velocity = velocity;
				ctx.weapon_data_map.bullet_scale = __gravity;
				ctx.weapon_data_map.bullet_speed = __speed;

				bone = g_prediction->resolve_position( ctx );
			};

			do_prediction();
		}

		auto bone_screen = g_engine->world_to_screen( bone );

		auto cross_hair = g_engine->get_cross_distance( bone_screen.x, bone_screen.y, g_render->width_center, g_render->height_center );
		if ( cross_hair >= g_vars->aim.fov_c ) return;
	
		auto is_held = ( GetAsyncKeyState( g_vars->aim.aim_keycode ) & 0x8000 ) || ( GetAsyncKeyState( g_vars->aim.second_aim_keycode ) & 0x8000 );
		if ( !is_held ) return;

		prediction::prediction_context context;
		context.animation_type = g_ctx->localplayer.animation_type;

		if ( g_vars->aim.aim_when_held && !g_prediction->is_weapon( context ) )
			return;

		this->aim_lock( bone );
	}
}

auto cheat_interface::c_aimbot::run_trigger() -> void
{
	if ( !g_vars->trigger.triggerbot ) 
		return;

	auto is_held = ( GetAsyncKeyState( g_vars->trigger.trigger_keycode ) );
	if ( !is_held ) 
		return;

	auto& closest_player = this->closest_actor;
	if ( !closest_player.m_mesh ) 
		return;

	if ( g_vars->trigger.trigger_distance < closest_player.distance )
		return;

	auto player_controller = g_ctx->localplayer.player_controller;
	if ( !player_controller ) 
		return;

	auto bone = g_engine->get_bone( closest_player.m_mesh->get_current_class(), get_best_bone( g_vars->aim.target_bone ) );

	if ( !g_engine->is_shot( player_controller->rectile(), bone ) ) 
		return;

	static bool is_initialized = false;

	if ( !is_initialized ) {
		g_mouse->initialize_handler(); is_initialized = true;
	}

	prediction::prediction_context context;
	context.animation_type = g_ctx->localplayer.animation_type;

	if ( g_vars->trigger.shotgun_only && !g_prediction->is_shotgun( context ) )
		return;

	if ( is_initialized )
		g_mouse->simulate_left_click();

	std::this_thread::sleep_for( std::chrono::milliseconds( g_vars->trigger.trigger_delay ) );
}

auto cheat_interface::c_aimbot::tick( ) -> void
{
	constexpr unsigned int aimbot_update_interval = 1;

	auto last_aimbot_tick = std::chrono::steady_clock::now( );

	constexpr std::array<unsigned int, 1> intervals = {
		aimbot_update_interval
	};

	constexpr unsigned int lowest_interval = *std::min_element( intervals.begin( ), intervals.end( ) );

	for ( ;; )
	{
		{
			this->update_function( aimbot_update_interval, last_aimbot_tick, [ & ] {
				this->run_aimbot( );
			} );

			std::this_thread::sleep_for( std::chrono::milliseconds( lowest_interval ) );
		}
	}
}

auto cheat_interface::c_aimbot::second_tick( ) -> void
{
	constexpr unsigned int triggerbot_update_interval = 1;

	auto last_trigger_tick = std::chrono::steady_clock::now( );

	constexpr std::array<unsigned int, 1> intervals = {
		triggerbot_update_interval
	};

	constexpr unsigned int lowest_interval = *std::min_element( intervals.begin( ), intervals.end( ) );

	for ( ;; )
	{
		{
			this->update_function( triggerbot_update_interval, last_trigger_tick, [ & ] {
				this->run_trigger( );
			});

			std::this_thread::sleep_for( std::chrono::milliseconds( lowest_interval ) );
		}
	}
}


#endif