#pragma once

namespace engine 
{
    template < typename _value_t >
        requires std::is_arithmetic_v< _value_t >
    using enough_float_t = std::conditional_t< sizeof( _value_t ) <= sizeof( float ), float, double >;

    template < typename _ret_t >
        requires std::is_floating_point_v< _ret_t >
    inline constexpr auto k_pi = static_cast< _ret_t >( std::numbers::pi );

    template < typename _ret_t >
        requires std::is_floating_point_v< _ret_t >
    inline constexpr auto k_pi2 = static_cast< _ret_t >( k_pi< double > *2.0 );

    template < typename _ret_t >
        requires std::is_floating_point_v< _ret_t >
    inline constexpr auto k_rad_pi = static_cast< _ret_t >( 180.0 / k_pi< double > );

    template < typename _ret_t >
        requires std::is_floating_point_v< _ret_t >
    inline constexpr auto k_deg_pi = static_cast< _ret_t >( k_pi< double > / 180.0 );

    template < typename _ret_t >
        requires std::is_floating_point_v< _ret_t >
    inline constexpr auto k_fov_pi = static_cast< _ret_t >( k_pi< double > / 360.0 );

    template < typename _value_t >
        requires std::is_arithmetic_v< _value_t >
    ALWAYS_INLINE constexpr auto to_angle( const _value_t fov )
    {
        using ret_t = enough_float_t< _value_t >;

        return static_cast< ret_t >( fov * k_fov_pi< ret_t > );
    }

    template < typename _value_t >
        requires std::is_arithmetic_v< _value_t >
    ALWAYS_INLINE constexpr auto to_deg( const _value_t rad )
    {
        using ret_t = enough_float_t< _value_t >;

        return static_cast< ret_t >( rad * k_rad_pi< ret_t > );
    }

    template < typename _value_t >
        requires std::is_arithmetic_v< _value_t >
    ALWAYS_INLINE constexpr auto to_rad( const _value_t deg )
    {
        using ret_t = enough_float_t< _value_t >;

        return static_cast< ret_t >( deg * k_deg_pi< ret_t > );
    }

    struct matrix_plane : math::vector3
    {
        double w;

        matrix_plane( ) : w( 0 ) { }
        matrix_plane( double w ) : w( w ) { }

        math::vector3 to_vector( ) {
            math::vector3 value;
            value.x = this->x;
            value.y = this->y;
            value.z = this->z;
            return value;
        }
    };

    class c_matrix 
    {
    public:
        double m[ 4 ][ 4 ];
        matrix_plane x_plane, y_plane, z_plane, w_plane;

        c_matrix( ) : x_plane( ), y_plane( ), z_plane( ), w_plane( ) { }
        c_matrix( matrix_plane x_plane, matrix_plane y_plane, matrix_plane z_plane, matrix_plane w_plane )
            : x_plane( x_plane ), y_plane( y_plane ), z_plane( z_plane ), w_plane( w_plane ) { }

        c_matrix to_multiplication( c_matrix m_matrix );
        c_matrix to_rotation_matrix( math::vector3& rotation );
    };

    struct frotator
    {
        double Pitch;
        double Yaw;
        double Roll;
    };

    struct quantum_matrix // FQuat
    {
        double x; // 0x00(0x08)
        double y; // 0x08(0x08)
        double z; // 0x10(0x08)
        double w; // 0x18(0x08)
    };

    struct transform // FTransform
    {
        quantum_matrix rotation;
        math::vector3 translation;
        char pad[ 0x4 ]; // 0x38(0x08)
        math::vector3 scale;
        char pad1[ 0x4 ]; // 0x58(0x08)

        transform( ) : rotation( ), translation( 0.f, 0.f, 0.f ), scale( 0.f, 0.f, 0.f ), pad( ), pad1( ) { }
        transform( const quantum_matrix& rot, const math::vector3& translation, const math::vector3& scale )
        {
            this->rotation = rot;
            this->translation = translation;

            this->pad[ 0x4 ] = 0;
            this->scale = scale;
            this->pad1[ 0x4 ] = 0;
        }

        c_matrix to_matrix( );
    };

    class c_engine : public c_matrix
    {
    public:

        int width{ };
        int height{ };

        int width_center{ };
        int height_center{ };
        
        float field_of_view = 0;

        math::vector3 rotation{ };
        math::vector3 location{ };

        math::vector3 waypoint{ };

    public:
        bool in_screen( math::vector3 bone );
        double get_cross_distance( double x1, double y1, double x2, double y2 );
        bool is_shot( math::vector3 lur, math::vector3 wl );
        math::vector3 get_axes( math::vector3& world_location );
        math::vector3 world_to_screen( math::vector3& world_location );
        void get_radar_range( float* x, float* y, float range );
        void calculate_radar_point( math::vector3 vOrigin, float& screenx, float& screeny, ImVec2 radarPos );
        math::vector3 get_bone( std::uint64_t mesh, uint32_t bone_index );
        frotator get_player_rotation( const math::vector3& angles );
        bool world_to_point( math::vector3& world_location, math::vector2* screen_out );
        math::vector2 get_cursor( );
        bool is_hovered( math::vector2 Start, math::vector2 Size );
    };

} inline auto g_engine = std::make_unique<engine::c_engine>( );