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

auto engine::c_engine::in_screen( math::vector3 bone ) -> bool {

	if ( bone.x > 0 && bone.x < this->width && bone.y > 0 && bone.y < this->height ) 
	{
		return true;
	}
	else 
	{
		return false;
	}
}

auto engine::c_engine::is_shot( math::vector3 lur, math::vector3 wl ) -> bool {

	if ( lur.x >= wl.x - 20 && lur.x <= wl.x + 20 && lur.y >= wl.y - 20 && lur.y <= wl.y + 20 && lur.z >= wl.z - 30 && lur.z <= wl.z + 30 )
	{
		return true;
	}
	else 
	{
		return false;
	}
}

auto engine::c_engine::get_cross_distance( double x1, double y1, double x2, double y2 ) -> double
{
	return sqrtf( powf( ( x2 - x1 ), 2 ) + powf( ( y2 - y1 ), 2 ) );
}

auto engine::transform::to_matrix( ) -> engine::c_matrix 
{
	engine::c_matrix matrix = {};

	auto x2 = this->rotation.x * 2;
	auto y2 = this->rotation.y * 2;
	auto z2 = this->rotation.z * 2;

	auto xx2 = this->rotation.x * x2;
	auto yy2 = this->rotation.y * y2;
	auto zz2 = this->rotation.z * z2;

	auto yz2 = this->rotation.y * z2;
	auto wx2 = this->rotation.w * x2;

	auto xy2 = this->rotation.x * y2;
	auto wz2 = this->rotation.w * z2;

	auto xz2 = this->rotation.x * z2;
	auto wy2 = this->rotation.w * y2;

	matrix.x_plane.x = ( 1.0 - ( yy2 + zz2 ) ) * this->scale.x;
	matrix.x_plane.y = ( xy2 + wz2 ) * this->scale.x;
	matrix.x_plane.z = ( xz2 - wy2 ) * this->scale.x;

	matrix.y_plane.x = ( xy2 - wz2 ) * this->scale.y;
	matrix.y_plane.y = ( 1.0 - ( xx2 + zz2 ) ) * this->scale.y;
	matrix.y_plane.z = ( yz2 + wx2 ) * this->scale.y;

	matrix.z_plane.x = ( xz2 + wy2 ) * this->scale.z;
	matrix.z_plane.y = ( yz2 - wx2 ) * this->scale.z;
	matrix.z_plane.z = ( 1.0 - ( xx2 + yy2 ) ) * this->scale.z;

	matrix.w_plane.x = this->translation.x;
	matrix.w_plane.y = this->translation.y;
	matrix.w_plane.z = this->translation.z;

	matrix.w_plane.w = 1.0;

	return matrix;
}


auto engine::c_matrix::to_rotation_matrix( math::vector3& rotation ) -> engine::c_matrix
{
	engine::c_matrix matrix = {};

	auto rad_pitch = ( rotation.x * std::numbers::pi / 180.f );
	auto rad_yaw = ( rotation.y * std::numbers::pi / 180.f );
	auto rad_roll = ( rotation.z * std::numbers::pi / 180.f );

	auto sin_pitch = sin( rad_pitch );
	auto cos_pitch = cos( rad_pitch );

	auto sin_yaw = sin( rad_yaw );
	auto cos_yaw = cos( rad_yaw );

	auto sin_roll = sin( rad_roll );
	auto cos_roll = cos( rad_roll );

	matrix.x_plane.x = cos_pitch * cos_yaw;
	matrix.x_plane.y = cos_pitch * sin_yaw;
	matrix.x_plane.z = sin_pitch;
	matrix.x_plane.w = 0.f;

	matrix.y_plane.x = sin_roll * sin_pitch * cos_yaw - cos_roll * sin_yaw;
	matrix.y_plane.y = sin_roll * sin_pitch * sin_yaw + cos_roll * cos_yaw;
	matrix.y_plane.z = -sin_roll * cos_pitch;
	matrix.y_plane.w = 0.f;

	matrix.z_plane.x = -( cos_roll * sin_pitch * cos_yaw + sin_roll * sin_yaw );
	matrix.z_plane.y = cos_yaw * sin_roll - cos_roll * sin_pitch * sin_yaw;
	matrix.z_plane.z = cos_roll * cos_pitch;
	matrix.z_plane.w = 0.f;

	matrix.w_plane.w = 1.f;

	return matrix;
}

auto engine::c_matrix::to_multiplication( engine::c_matrix m_matrix ) -> engine::c_matrix 
{
	engine::c_matrix matrix{};

	matrix.w_plane.x = (
		this->w_plane.x * m_matrix.x_plane.x +
		this->w_plane.y * m_matrix.y_plane.x +
		this->w_plane.z * m_matrix.z_plane.x +
		this->w_plane.w * m_matrix.w_plane.x
		);

	matrix.w_plane.y = (
		this->w_plane.x * m_matrix.x_plane.y +
		this->w_plane.y * m_matrix.y_plane.y +
		this->w_plane.z * m_matrix.z_plane.y +
		this->w_plane.w * m_matrix.w_plane.y
		);

	matrix.w_plane.z = (
		this->w_plane.x * m_matrix.x_plane.z +
		this->w_plane.y * m_matrix.y_plane.z +
		this->w_plane.z * m_matrix.z_plane.z +
		this->w_plane.w * m_matrix.w_plane.z
		);

	matrix.w_plane.w = (
		this->w_plane.x * m_matrix.x_plane.w +
		this->w_plane.y * m_matrix.y_plane.w +
		this->w_plane.z * m_matrix.z_plane.w +
		this->w_plane.w * m_matrix.w_plane.w
		);

	return matrix;
}

auto engine::c_engine::get_axes( math::vector3& world_location ) -> math::vector3
{
	auto rotation_factor = math::vector3( this->rotation.x, this->rotation.y, this->rotation.z );
	auto matrix = this->to_rotation_matrix( rotation_factor );

	auto axis_x = math::vector3( matrix.x_plane.x, matrix.x_plane.y, matrix.x_plane.z );
	auto axis_y = math::vector3( matrix.y_plane.x, matrix.y_plane.y, matrix.y_plane.z );
	auto axis_z = math::vector3( matrix.z_plane.x, matrix.z_plane.y, matrix.z_plane.z );

	auto delta = static_cast< math::vector3 >(
		world_location - this->location
		);

	auto transform = math::vector3(
		delta.vector_scalar( axis_y ),
		delta.vector_scalar( axis_z ),
		delta.vector_scalar( axis_x )
	);

	transform.z = ( transform.z < 1.f ) ? 1.f : transform.z;

	return transform;
}


auto engine::c_engine::world_to_point( math::vector3& world_location, math::vector2* screen_out ) -> bool
{
	auto axis = this->get_axes(
		world_location
	);

	const float fov_radians = this->field_of_view * static_cast< double >( std::numbers::pi ) / 360.0f;

	*screen_out = math::vector2(
		this->width_center + axis.x * ( this->width_center / tan( fov_radians ) ) / axis.z,
		this->height_center - axis.y * ( this->width_center / tan( fov_radians ) ) / axis.z
	);

	return true;
}


auto engine::c_engine::world_to_screen( math::vector3& world_location ) -> math::vector3
{
	auto axis = this->get_axes(
		world_location
	);

	const float fov_radians = this->field_of_view * static_cast< double >( std::numbers::pi ) / 360.0f;

	return math::vector3(
		this->width_center + axis.x * ( this->width_center / tan( fov_radians ) ) / axis.z,
		this->height_center - axis.y * ( this->width_center / tan( fov_radians ) ) / axis.z,
		0.0f
	);
}

auto engine::c_engine::get_radar_range(
	float* x, float* y, float range
) -> void
{
	if ( fabs( ( *x ) ) > range || fabs( ( *y ) ) > range )
	{
		if ( ( *y ) > ( *x ) )
		{
			if ( ( *y ) > -( *x ) )
			{
				( *x ) = range * ( *x ) / ( *y );
				( *y ) = range;
			}
			else
			{
				( *y ) = -range * ( *y ) / ( *x );
				( *x ) = -range;
			}
		}
		else
		{
			if ( ( *y ) > -( *x ) )
			{
				( *y ) = range * ( *y ) / ( *x );
				( *x ) = range;
			}
			else
			{
				( *x ) = -range * ( *x ) / ( *y );
				( *y ) = -range;
			}
		}
	}
}


auto engine::c_engine::calculate_radar_point(
	math::vector3 vOrigin, float& screenx, float& screeny, ImVec2 radarPos
) -> void {
	auto vAngle = this->rotation;
	auto fYaw = vAngle.y * M_PI / 180.0f;
	float dx = vOrigin.x - this->location.x;
	float dy = vOrigin.y - this->location.y;

	float fsin_yaw = sinf( fYaw );
	float fminus_cos_yaw = -cosf( fYaw );

	float x = dy * fminus_cos_yaw + dx * fsin_yaw;
	x = -x;
	float y = dx * fminus_cos_yaw - dy * fsin_yaw;

	float range = ( float ) 60.f * 1000.f;

	this->get_radar_range( &x, &y, range );

	ImVec2 DrawPos = ImVec2( radarPos.x, radarPos.y );
	ImVec2 DrawSize = ImVec2( g_vars->radar.radar_size, g_vars->radar.radar_size );

	int rad_x = ( int ) DrawPos.x;
	int rad_y = ( int ) DrawPos.y;

	float r_siz_x = DrawSize.x;
	float r_siz_y = DrawSize.y;

	int x_max = ( int ) r_siz_x + rad_x - 5;
	int y_max = ( int ) r_siz_y + rad_y - 5;

	screenx = rad_x + ( ( int ) r_siz_x / 2 + int( x / range * r_siz_x ) );
	screeny = rad_y + ( ( int ) r_siz_y / 2 + int( y / range * r_siz_y ) );

	if ( screenx > x_max )
		screenx = x_max;

	if ( screenx < rad_x )
		screenx = rad_x;

	if ( screeny > y_max )
		screeny = y_max;

	if ( screeny < rad_y )
		screeny = rad_y;
}

auto engine::c_engine::get_bone( std::uint64_t mesh,
	uint32_t bone_index ) -> math::vector3
{
	auto bone_array = g_vm->read<std::uint64_t>( mesh + 0x5C8 );
	if ( !bone_array ) {
		bone_array = g_vm->read<std::uint64_t>( mesh + 0x5C8 + 0x10 );
	}

	auto bone = g_vm->read<engine::transform>( std::uintptr_t( bone_array ) + ( bone_index * 0x60 ) );

	auto ctw = g_vm->read<engine::transform>( std::uintptr_t( mesh ) + 0x1E0 );

	auto matrix = bone.to_matrix( ).to_multiplication(
		ctw.to_matrix( )
	);

	return math::vector3(
		matrix.w_plane.x,
		matrix.w_plane.y,
		matrix.w_plane.z
	);
}

#define DEG2RAD(x) ((float)(x) * (float)(std::numbers::pi / 180.f))

auto engine::c_engine::get_player_rotation(
	const math::vector3& angles ) -> frotator
{
	frotator forward;
	float sp, sy, cp, cy;

	sy = sin( DEG2RAD( angles.y ) );
	cy = cos( DEG2RAD( angles.y ) );

	sp = sin( DEG2RAD( angles.x ) );
	cp = cos( DEG2RAD( angles.x ) );

	forward.Yaw = cp * sy;
	forward.Pitch = cp * cy;
	forward.Roll = -sp;
	return forward;
}

auto engine::c_engine::get_cursor( )-> math::vector2 
{
	POINT CursorPoint = {};
	GetCursorPos( &CursorPoint );

	return math::vector2( double( CursorPoint.x ), double( CursorPoint.y ) );
}

auto engine::c_engine::is_hovered( math::vector2 Start, math::vector2 Size ) -> bool 
{
	auto cursor_pos = this->get_cursor( );

	if ( cursor_pos.x > Start.x && cursor_pos.y > Start.y )
	{
		if ( cursor_pos.x < Start.x + Size.x && cursor_pos.y < Start.y + Size.y )
		{
			return true;
		}
	}

	return false;
}

#endif // ! guard