#pragma region CPL License
/*
Nuclex Native Framework
Copyright (C) 2002-2015 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#pragma endregion // CPL License
#ifndef NUCLEX_GEOMETRY_MATH_H
#error This file must be included through Math.h
#endif
namespace Nuclex { namespace Geometry {
  // ------------------------------------------------------------------------------------------- //
  /// Math routines for geometry classes based on long double precision floats
  template <>
  class Math {
    /// The PI constant
    public: NUCLEX_GEOMETRY_API static constexpr long double Pi = (
      3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825L
    );
    /// Two times PI, useful for some operations
    public: NUCLEX_GEOMETRY_API static constexpr long double Perigon = (
      6.283185307179586476925286766559005768394338798750211641949889184615632812572417997256069651L
    );
    /// Calculates the square root of the specified value
    /// Value of which the square root is calculated
    /// The square root of the specified number
    public: NUCLEX_GEOMETRY_API static long double SquareRoot(long double value) {
      return std::sqrt(value);
    }
    /// Returns a random number from 0.0 to the specified maximum
    /// The random number generator that will be used
    /// Upper limit for the random number (exclusive)
    /// A random number from 0.0 to the specified maximum
    public: template  static long double Random(
      TRandomNumberEngine &random, long double max
    ) {
      std::uniform_real_distribution distribution(0.0, max);
      return distribution(random);
    }
    /// Returns a random number in the specified range
    /// The random number generator that will be used
    /// Minimum possible value of the random number
    /// Upper limit for the random number (exclusive)
    /// A random number within the specified range
    public: template  static long double Random(
      TRandomNumberEngine &random, long double min, long double max
    ) {
      std::uniform_real_distribution distribution(min, max);
      return distribution(random);
    }
    /// Calculates the sine of the specified angle
    /// Angle for which the sine will be calculated
    /// The sine of the specified angle
    public: NUCLEX_GEOMETRY_API static long double Sine(long double angleInRadians) {
      return std::sin(angleInRadians);
    }
    /// Calculates the cosine of the specified angle
    /// Angle for which the cosine will be calculated
    /// The cosine of the specified angle
    public: NUCLEX_GEOMETRY_API static long double Cosine(long double angleInRadians) {
      return std::cos(angleInRadians);
    }
    /// Calculates the angle of the specified sine value
    /// Sine value of which the angle will be calculated
    /// The angle of the specified sine value in radians
    public: NUCLEX_GEOMETRY_API static long double ArcSine(long double sineValue) {
      return std::asin(sineValue);
    }
    /// Calculates the angle of the specified cosine value
    /// Cosine value for which the angle will be calculated
    /// The angle of the specified cosine value in radians
    public: NUCLEX_GEOMETRY_API static long double ArcCosine(long double cosineValue) {
      return std::acos(cosineValue);
    }
    /// Calculates the angle of the tangent specified by a direction
    /// X coordinate of the direction
    /// Y coordinate of the direction
    /// The angle of the tangent specified through the direction
    public: NUCLEX_GEOMETRY_API static long double ArcTangent(long double x, long double y) {
      return std::atan2(y, x);
    }
    /// Returns the absolute value of the specified value
    /// Value of which the absolute value will be returned
    /// The absolute value of the provided value
    public: NUCLEX_GEOMETRY_API static long double Abs(long double value) {
      return std::fabs(value);
    }
    /// Calculates  to the power of 
    /// Value that will be multiplied
    /// To which power the value will be multiplied
    ///  to the power of 
    public: NUCLEX_GEOMETRY_API static long double Power(long double value, long double power) {
      return std::pow(value, power);
    }
    /// Returns the smaller of two values
    /// First of the two values
    /// Second of the two values
    /// The smaller of the two provided values
    public: NUCLEX_GEOMETRY_API static long double Min(long double first, long double second) {
      return std::min(first, second);
    }
    /// Returns the smallest of three values
    /// First of the three values
    /// Second of the three values
    /// Third of the three values
    /// The smallest of the three provided values
    public: NUCLEX_GEOMETRY_API static long double Min(
      long double first, long double second, long double third
    ) {
      return std::min(std::min(first, second), third);
    }
    /// Returns the larger of two values
    /// First of the two values
    /// Second of the two values
    /// The larger of the two provided values
    public: NUCLEX_GEOMETRY_API static long double Max(long double first, long double second) {
      return std::max(first, second);
    }
    /// Returns the largest of three values
    /// First of the three values
    /// Second of the three values
    /// Third of the three values
    /// The largest of the three provided values
    public: NUCLEX_GEOMETRY_API static long double Max(
      long double first, long double second, long double third
    ) {
      return std::max(std::max(first, second), third);
    }
    /// Determines the extremes of two values
    /// First of the values whose extremes will be found
    /// Second of the values whose extremes will be found
    /// 
    ///   A pair containing the smaller and the larger value in that order
    /// 
    public: NUCLEX_GEOMETRY_API static std::pair Extremes(
      long double first, long double second
    ) {
      if(first < second) {
        return std::pair(first, second);
      } else {
        return std::pair(second, first);
      }
    }
    /// Determines the extremes of three values
    /// First of the values whose extremes will be found
    /// Second of the values whose extremes will be found
    /// Third of the values whose extremes will be found
    /// 
    ///   A pair containing the smallest and the largest value of the three inputs
    /// 
    public: NUCLEX_GEOMETRY_API static std::pair Extremes(
      long double first, long double second, long double third
    ) {
      if(first < second) {
        if(second < third) { // first < second < third
          return std::make_pair(first, third);
        } else if(first < third) { // first < third < second
          return std::make_pair(first, second);
        } else { // third < first < second
          return std::make_pair(third, second);
        }
      } else {
        if(first < third) { // second < first < third
          return std::make_pair(second, third);
        } else if(second < third) { // second < third < first
          return std::make_pair(second, first);
        } else { // third < second < first
          return std::make_pair(third, first);
        }
      }
    }
    /// Unwinds a value in a circular (repeating) value range
    /// Value that will be unwound
    /// Lower limit of the circular value range
    /// Upper limit of the circular value range
    /// The unwound value
    public: NUCLEX_GEOMETRY_API static long double Unwind(
      long double value, long double min, long double max
    ) {
      long double range = (max - min);
      long double floor = std::floor((value - min) / range);
      return value - (floor * range);
    }
    /// Limits the value to the specified range
    /// Value that will be limited
    /// Minimum the value cannot go under
    /// Maximum the value cannot go above
    /// The value limited to the specified range
    public: NUCLEX_GEOMETRY_API static long double Clamp(
      long double value, long double min, long double max
    ) {
      if(value < min) {
        return min;
      }
      if(value > max) {
        return max;
      }
      return value;
    }
    /// Linearly interpolates between two values
    /// Value at the left interpolation point
    /// Value at the right interpolation point
    /// Time (0.0 .. 1.0) for which to interpolate the value
    /// The interpolated value at the specified time
    public: NUCLEX_GEOMETRY_API static long double Lerp(
      long double left, long double right, long double t
    ) {
      return (right - left) * t + left;
    }
    /// Calculates the time of a linearly interpolated value
    /// Value at the left interpolation point
    /// Value at the right interpolation point
    /// Value that resulted from the interpolation
    /// Time at which the value is placed in the interpolation range
    public: NUCLEX_GEOMETRY_API static long double Unlerp(
      long double left, long double right, long double value
    ) {
      return (value - left) / (right - left);
    }
    /// Checks whether the specified value is invalid (not a number)
    /// Value that will be checked
    /// True if the specified value is not a number
    public: NUCLEX_GEOMETRY_API static bool IsNan(long double value) {
      return std::isnan(value);
    }
  };
  // ------------------------------------------------------------------------------------------- //
}} // namespace Nuclex::Geometry