#ifndef GC_MATH_H_
#define GC_MATH_H_

#if __cplusplus>=201103L
#define GC_MATH__CONSTEXPR	constexpr
#else
#define GC_MATH__CONSTEXPR
#endif


#include <math.h>
#include <float.h>

#ifndef M_PI  /*quasi-standard const, not present in all compilers, but available under GCC*/
#define M_PI		3.14159265358979323846
#define M_PI_2		1.57079632679489661923
#define M_PI_4		0.78539816339744830962
#define M_1_PI		0.31830988618379067154
#define M_2_PI		0.63661977236758134308
#endif

#define GC_K_COMPOSE2(X_, Y_)  (X_ ## Y_)

#define GC_K_PIx(x_) ((x_)*GC_K_PI)
#define GC_K_PId(x_) ((x_)/GC_K_PI)
#define GC_K_PIxd(x_,d_) ((GC_K_PI*(x_))/(d_))

#define GC_K_PI static_cast<float>(M_PI) //180deg
#define GC_K_PI_2 static_cast<float>(M_PI_2) //90deg
#define GC_K_PI_3 GC_K_PId(3.0f)  //60deg
#define GC_K_2_PI_3 GC_K_PIxd(2.0f,3.0f)  //120deg
#define GC_K_2_PI static_cast<float>(GC_K_PIx(2)) //360deg
#define GC_K_4_PI static_cast<float>(GC_K_PIx(4)) //720deg

#define GC_K_180DEG (180.0f)
#define GC_K_360DEG (360.0f)
#define GC_K_90DEG  (90.0f)

#define GC_K_RAD_TO_DEG GC_K_PId(180.0f)
#define GC_K_DEG_TO_RAD (1.0f/GC_K_RAD_TO_DEG)

#define GC_K_NM_TO_METERS (1852.0f)
#define GC_K_METERS_TO_NM (1.0f/GC_K_NM_TO_METERS)
#define GC_K_METERS_TO_FEETS (3.280839895f)
#define GC_K_FEETS_TO_METERS (1.0f/GC_K_METERS_TO_FEETS)

#define GC_K_MS_TO_KNOTS (3600.0f/GC_K_NM_TO_METERS)
#define GC_K_KNOTS_TO_MS (1.0f/GC_K_MS_TO_KNOTS)

#define GC_DEG_TO_RAD(deg_)	((deg_)*GC_K_DEG_TO_RAD)				/*prefer macro against deg_to_rad() only in static member initialization, to prevent compiler generated code injection in static initializer list*/
#define GC_FEETS_TO_METERS(feet_)	((feet_)*GC_K_FEETS_TO_METERS)	/*prefer macro against feet_to_meters() only in static member initialization, to prevent compiler generated code injection in static initializer list*/


namespace gc_math
{


static inline bool float_exact_equal(float a, float b)	/*DEPRECATED*/
{
	return a==b; /* parasoft-suppress SELEXESC++-RC++12 "float exact compare, to use only in test code where exact compare against integer value" */
}

static inline bool float_equal(float a, float b)
{
	float delta=a-b;
	bool result= (delta>=-FLT_EPSILON) && (delta<=FLT_EPSILON);
	return result;
}

static inline int float_compare_epsilon(float a, float b, float epsilon /*warning: must be >=0*/)
{
	float delta=a-b;
	if (delta<-epsilon) return -1;
	if (delta>epsilon) return 1;
	return 0;
}

static inline bool float_is_zero(float v)
{
  return float_equal(v, 0.0f);
}

static inline bool float_is_exact_zero(float v)
{
  return v==0;
}

static inline int sign(float v)
{
  if(v >= 0)
  {
    return 1;
  }
  else
  {
    return -1;
  }
}

extern const float k_epsilon;

extern const float k_180deg;
extern const float k_360deg;
extern const float k_90deg;

extern const float k_pi;
extern const float k_2_pi;
extern const float k_4_pi;
extern const float k_half_pi;
extern const float k_pi_over_3;   //60deg
extern const float k_2_pi_over_3; //120deg

extern const float k_rad_to_deg;
extern const float k_deg_to_rad;

extern const float k_nm_to_meters;
extern const float k_meters_to_nm;

extern const float k_meters_to_feets;
extern const float k_feets_to_meters;

extern const float k_ms_to_knots;
extern const float k_knots_to_ms;

inline float meters_to_feet(float m) { return m*gc_math::k_meters_to_feets;}
inline float feet_to_meters(float ft) { return ft*gc_math::k_feets_to_meters;}

inline float nm_to_meters(float nm) { return nm*gc_math::k_nm_to_meters;}
inline float meters_to_nm(float m) { return m*gc_math::k_meters_to_nm;}

inline float ms_to_knots(float ms) { return ms*gc_math::k_ms_to_knots;}
inline float knots_to_ms(float kts) { return kts*gc_math::k_knots_to_ms;}

inline float deg_to_rad(float deg) { return deg*gc_math::k_deg_to_rad;}
inline float rad_to_deg(float rad) { return rad*gc_math::k_rad_to_deg;}

float angle_bound_pi(float x);
float angle_bound_2pi(float x);

float angle_bound_pi_by_mod(float value);

bool angle_check_pi(float x);
bool angle_check_pi_pos(float x);
bool angle_check_pi2(float x);
bool angle_check_2pi_pos(float x);
bool angle_check_2pi3_pos(float x);

float angle_sum_pi(float a, float b);
float angle_sum_2pi(float a, float b);
float angle_dif_pi(float a, float b);

void rad_to_dms(float angle_rad, int& sign, int& arc_deg, int& arc_min, float& arc_sec);
void pol_to_rect(float rng, float az_rad, float el_rad, float& x, float& y, float& z);
void rect_to_pol(float x, float y, float z, float& rng, float& az_rad, float& el_rad);
float dist_from_pol(float rng1, float az1_rad, float el1_rad, float rng2, float az2_rad, float el2_rad);

inline int nint(float x) { return roundf(x); }
inline int ntrunc(float x) { return truncf(x); }

float angle_bound_180(float x);
float angle_bound_360(float x);
int angle_bound_int_359(int x);
float angle_sum_180(float a, float b);
float angle_sum_360(float a, float b);

static inline int float_compare_deg_rad(float deg, float rad)
{
	return float_compare_epsilon(deg, gc_math::rad_to_deg(rad), GC_K_RAD_TO_DEG);
}

template<typename T,typename U,typename V> inline void clip_min_max(T& data, U min, V max, bool& clip)
{
  if (data<min)
  {
    data = min;
    clip = true;
  }

  if (data>max)
  {
    data = max;
    clip = true;
  }
}

static inline void clip_min_max(float& data, float min, float max, float min_margin, float max_margin, bool& clip)
{
    if (data<(min-min_margin))
    {
      data = (min-min_margin);
      clip = true;
    }

    if (data>(max+max_margin))
    {
      data = (max+max_margin);
      clip = true;
    }
}

template<typename T,typename U,typename V> inline void check_min_max(T& data, U min, V max, bool& valid)
{
  int chkEnum = static_cast<int>(data);
  if((chkEnum<min || chkEnum>max))
  {
    valid = false;
  }
}

static inline void check_min_max(float data, float min, float max, float min_margin, float max_margin, bool& valid)
{
  if(((data<(min-min_margin)) || (data>(max+max_margin))))
  {
    valid = false;
  }
}

}

#endif /*GC_MATH_H_*/
