#ifndef SRC_MATH_HLSL
#define SRC_MATH_HLSL

#define FLT_MIN	asfloat(0x00800000)  // 1.175494351e-38f
#define FLT_MAX	asfloat(0x7F7FFFFF)  // 3.402823466e+38f
#define FLT_QNAN_POS asfloat(0x7FC00000) // +qNaN
#define FLT_QNAN_NEG asfloat(0xFFC00000) // -qNaN
#define FLT_SNAN_POS asfloat(0x7FA00000) // +sNaN
#define FLT_SNAN_NEG asfloat(0xFFA00000) // -sNaN
#define FLT_NAN	FLT_QNAN_POS
#define FLT_EPSILON	1.192092896e-07 // Smallest positive number, such that 1.0 + FLT_EPSILON != 1.0
#define FLT10_MAX 64512.f
#define FLT11_MAX 65024.f
#define FLT16_MAX 65504.f

#define PI 3.141592653589793238462643383279502884197
#define PI_X2 (PI * 2.0)
#define PI_X4 (PI_X4 * 4.0)

static const float3x3 IdentityMatrix = float3x3(
    1.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 1.0f);

float average(float3 color)
{
	return (color.x + color.y + color.z) / 3.f;
}
float average(float2 color)
{
	return (color.x + color.y) / 2.f;
}

float remap(float input, float oldMin, float oldMax, float newMin, float newMax)
{
	return ((input - oldMin) * ((newMax - newMin) / (oldMax - oldMin))) + newMin;
}
float3 remap(float3 input, float3 oldMin, float3 oldMax, float3 newMin, float3 newMax)
{
	return ((input - oldMin) * ((newMax - newMin) / (oldMax - oldMin))) + newMin;
}

// Returns 0, 1, -1/0/+1 or +/-FLT_MAX if "dividend" is 0
float safeDivision(float quotient, float dividend, int fallbackMode = 0)
{
  float result = 0; // We get warning 4000 if just directly return values
	if (dividend == 0)
  {
    if (fallbackMode == 0)
      result = 0;
    else if (fallbackMode == 1)
      result = 1;
    else if (fallbackMode == 2)
      result = sign(quotient); // This will return 0 for 0
    else
      result = FLT_MAX * sign(quotient);
  }
  else
  {
    result = quotient / dividend;
  }
  return result;
}
// Returns 0, 1 or FLT_MAX if "dividend" is 0
float3 safeDivision(float3 quotient, float3 dividend, int fallbackMode = 0)
{
  return float3(safeDivision(quotient.x, dividend.x, fallbackMode), safeDivision(quotient.y, dividend.y, fallbackMode), safeDivision(quotient.z, dividend.z, fallbackMode));
}

// Depending on the compiler settings, DX claims to strip away "isnan" checks as "NaN" can't happen in shaders (which isn't true...),
// this is an actual forced check for it.
bool IsNaN_Strict(float x)
{
#if 0 // float!=float is only true if the number is NaN. This doesn't always work, it's probably optimized away!
  return x.x != x.x;
#elif 1 // This will cover all the possible nan cases
  uint bits = asuint(x);
  return ((bits & 0x7F800000) == 0x7F800000) && ((bits & 0x007FFFFF) != 0);
#else // Dunno if this one is good
  return (asuint(x) & 0x7FFFFFFF) > 0x7F800000;
#endif
}
bool2 IsNaN_Strict(float2 x)
{
  return bool2(IsNaN_Strict(x.x), IsNaN_Strict(x.y));
}
bool3 IsNaN_Strict(float3 x)
{
  return bool3(IsNaN_Strict(x.x), IsNaN_Strict(x.y), IsNaN_Strict(x.z));
}
bool4 IsNaN_Strict(float4 x)
{
  return bool4(IsNaN_Strict(x.x), IsNaN_Strict(x.y), IsNaN_Strict(x.z), IsNaN_Strict(x.w));
}
bool IsAnyNaN_Strict(float2 x)
{
  return IsNaN_Strict(x.x) || IsNaN_Strict(x.y);
}
bool IsAnyNaN_Strict(float3 x)
{
  return IsNaN_Strict(x.x) || IsNaN_Strict(x.y) || IsNaN_Strict(x.z);
}
bool IsAnyNaN_Strict(float4 x)
{
  return IsNaN_Strict(x.x) || IsNaN_Strict(x.y) || IsNaN_Strict(x.z) || IsNaN_Strict(x.w);
}

bool IsInfinite_Strict(float x)
{
    return abs(x) > FLT_MAX;
}
bool3 IsInfinite_Strict(float3 x)
{
    return abs(x) > FLT_MAX;
}

float InverseLerp(float a, float b, float value)
{
  // Avoid division by zero, and default to the mid point (neutral)
  if (a == b) {
    return 0.5;
  }
  return (value - a) / (b - a);
}

float Sign_Fast(float x)
{
#if 1
  return x >= 0.0 ? 1.0 : -1.0;
#else // Faster version, though it mibht be unsafe for extreme values
  return mad(saturate(mad(x, FLT_MAX, 0.5f)), 2.f, -1.f);
#endif
}
float2 Sign_Fast(float2 x)
{
  return x >= 0.0 ? 1.0 : -1.0;
}
float3 Sign_Fast(float3 x)
{
  return x >= 0.0 ? 1.0 : -1.0;
}
float4 Sign_Fast(float4 x)
{
  return x >= 0.0 ? 1.0 : -1.0;
}

// Builds +1.0 or -1.0 by copying x's sign bit into 1.0f
// This returns -1.0 for a -0.f input
float Sign_UltraFast(float x)
{
  return asfloat((asuint(x) & 0x80000000u) | 0x3F800000u);
}
float2 Sign_UltraFast(float2 x)
{
  return asfloat((asuint(x) & 0x80000000u) | 0x3F800000u);
}
float3 Sign_UltraFast(float3 x)
{
  return asfloat((asuint(x) & 0x80000000u) | 0x3F800000u);
}
float4 Sign_UltraFast(float4 x)
{
  return asfloat((asuint(x) & 0x80000000u) | 0x3F800000u);
}

float safePow(float base, float exponent)
{
  return pow(abs(base), exponent) * Sign_Fast(base);
}
float3 safePow(float3 base, float exponent)
{
  return pow(abs(base), exponent) * Sign_Fast(base);
}

float sqr(float x) { return x * x; }
float2 sqr(float2 x) { return x * x; }
float3 sqr(float3 x) { return x * x; }
float4 sqr(float4 x) { return x * x; }

float min3(float _a, float _b, float _c) { return min(_a, min(_b, _c)); }
float3 min3(float3 _a, float3 _b, float3 _c) { return min(_a, min(_b, _c)); }
float min3(float3 _a) { return min(_a.x, min(_a.y, _a.z)); }
float3 max3(float3 _a, float3 _b, float3 _c) { return max(_a, max(_b, _c)); }
float max3(float _a, float _b, float _c) { return max(_a, max(_b, _c)); }
float max3(float3 _a) { return max(_a.x, max(_a.y, _a.z)); }

// Returns the median value of 3 channels
float GetMidValue(float3 x)
{
    return x.x + x.y + x.z - (min3(x) + max3(x));
}

// Returns the first channel if they are all the same
uint GetMaxIndex(float3 x)
{
  uint maxIndex = 0;
  if (x.g > x[maxIndex]) maxIndex = 1;
  if (x.b > x[maxIndex]) maxIndex = 2;
  return maxIndex;
}
uint GetMaxIndex(int3 x)
{
  uint maxIndex = 0;
  if (x.g > x[maxIndex]) maxIndex = 1;
  if (x.b > x[maxIndex]) maxIndex = 2;
  return maxIndex;
}
uint GetMinIndex(float3 x)
{
  uint minIndex = 0;
  if (x.g < x[minIndex]) minIndex = 1;
  if (x.b < x[minIndex]) minIndex = 2;
  return minIndex;
}
uint GetMinIndex(int3 x)
{
  uint minIndex = 0;
  if (x.g < x[minIndex]) minIndex = 1;
  if (x.b < x[minIndex]) minIndex = 2;
  return minIndex;
}
// Returns the index that isn't either min nor max
uint GetMidIndex(float3 x)
{
  uint minIndex = GetMinIndex(x);
  uint maxIndex = GetMaxIndex(x);
  return (minIndex == maxIndex) ? 0 : (3 - (minIndex + maxIndex));
}
uint GetMidIndex(int3 x)
{
  uint minIndex = GetMinIndex(x);
  uint maxIndex = GetMaxIndex(x);
  return (minIndex == maxIndex) ? 0 : (3 - (minIndex + maxIndex));
}

void SetIndexValue(inout float3 x, uint index, float value)
{
    if (index == 0) x.x = value;
    else if (index == 1) x.y = value;
    else x.z = value;
}

// Returns a random value betweed 0 and 1
// "seed" can be in any space (e.g. pixel or uv or whatever else)
float3 NRand3(float2 seed, float tr = 1.0)
{
  return frac(sin(dot(seed.xy, float2(34.483, 89.637) * tr)) * float3(29156.4765, 38273.5639, 47843.7546));
}

// Takes coordinates centered around zero, and a normal for a cube of side size 1, both with origin at 0.
// The normal is expected to be negative/inverted (facing origin) (basically it's just the cube side).
bool cubeCoordinatesIntersection(out float3 intersection, float3 coordinates, float3 sideNormal)
{
  intersection = 0;
  if (dot(sideNormal, coordinates) >= -1.f)
    return false; // No intersection, the line is parallel or facing away from the plane
  // Compute the X value for the directed line ray intersecting the plane
  float t = -1.f / dot(sideNormal, coordinates);
  intersection = coordinates * t;
  return true;
}

// Mirror + repeat
float2 MirrorUV(float2 uv)
{
#if 1
	float2 modded = fmod( uv, 2.0 );
	modded += ( modded < 0 ) ? 2.0 : 0.0; // Ensure positive values
  return ( modded <= 1.0 ) ? modded : ( 2.0 - modded );
#else // TODO: cheaper version?
  return 1.0 - abs(frac(uv * 0.5) * 2.0 - 1.0);
#endif
}

// Repeat (no mirror)
float2 RepeatUV(float2 uv)
{
    uv = abs(frac(uv * 0.5) * 2.0 - 1.0);
    return uv;
}

#endif // SRC_MATH_HLSL