text: // Copyright Take Vos 2021.
  // Distributed under the Boost Software License, Version 1.0.
  // (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
  
  #pragma once
  
  #include "vector.hpp"
  #include "../rapid/numeric_array.hpp"
  #include <compare>
  
  namespace tt::inline v1 {
  namespace geo {
  template<int D>
  class scale;
  
  /** A high-level geometric extent
   *
   * A extent, for both 2D or 3D is internally represented
   * as a 4D homogeneous extent. Which can be efficiently implemented
   * as a __m128 SSE register.
   */
  template<int D>
  class extent {
  public:
      static_assert(D == 2 || D == 3, "Only 2D or 3D extents are supported");
  
      constexpr extent(extent const &) noexcept = default;
      constexpr extent(extent &&) noexcept = default;
      constexpr extent &operator=(extent const &) noexcept = default;
      constexpr extent &operator=(extent &&) noexcept = default;
  
      /** Construct a extent from a lower dimension extent.
       */
      template<int E>
      requires(E < D) [[nodiscard]] constexpr extent(extent<E> const &other) noexcept : _v(static_cast<f32x4>(other))
      {
          tt_axiom(holds_invariant());
      }
  
      /** Convert a extent to its f32x4-nummeric_array.
       */
      [[nodiscard]] constexpr explicit operator f32x4() const noexcept
      {
          return _v;
      }
  
      [[nodiscard]] constexpr explicit extent(f32x4 const &other) noexcept : _v(other) {}
  
      [[nodiscard]] constexpr explicit operator bool() const noexcept
      {
          if constexpr (D == 2) {
              return _v.x() != 0.0f or _v.y() != 0.0f;
          } else if constexpr (D == 3) {
              return _v.x() != 0.0f or _v.y() != 0.0f or _v.z() != 0.0f;
          } else {
              tt_no_default();
          }
      }
  
      template<int E>
      [[nodiscard]] constexpr explicit operator vector<E>() const noexcept requires(E >= D)
      {
          tt_axiom(holds_invariant());
          return vector<E>{static_cast<f32x4>(*this)};
      }
  
      /** Construct a empty extent / zero length.
       */
      [[nodiscard]] constexpr extent() noexcept : _v(0.0f, 0.0f, 0.0f, 0.0f)
      {
          tt_axiom(holds_invariant());
      }
  
      /** Construct a 2D extent from x and y elements.
       * @param x The x element.
       * @param y The y element.
       */
      [[nodiscard]] constexpr extent(float width, float height) noexcept requires(D == 2) : _v(width, height, 0.0f, 0.0f)
      {
          tt_axiom(holds_invariant());
      }
  
      /** Construct a 3D extent from x, y and z elements.
       * @param x The x element.
       * @param y The y element.
       * @param z The z element.
       */
      [[nodiscard]] constexpr extent(float width, float height, float depth = 0.0f) noexcept requires(D == 3) :
          _v(width, height, depth, 0.0f)
      {
          tt_axiom(holds_invariant());
      }
  
      [[nodiscard]] static constexpr extent infinity() noexcept requires(D == 2)
      {
          return extent{std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity()};
      }
  
      [[nodiscard]] static constexpr extent infinity() noexcept requires(D == 3)
      {
          return extent{
              std::numeric_limits<float>::infinity(),
              std::numeric_limits<float>::infinity(),
              std::numeric_limits<float>::infinity()};
      }
  
      [[nodiscard]] static constexpr extent large() noexcept requires(D == 2)
      {
          return extent{32767.0f, 32767.0f};
      }
  
      [[nodiscard]] static constexpr extent large() noexcept requires(D == 3)
      {
          return extent{32767.0f, 32767.0f, 32767.0f};
      }
  
      [[nodiscard]] static constexpr extent nan() noexcept requires(D == 2)
      {
          auto r = extent{};
          r._v.x() = std::numeric_limits<float>::signaling_NaN();
          r._v.y() = std::numeric_limits<float>::signaling_NaN();
          return r;
      }
  
      [[nodiscard]] static constexpr extent nan() noexcept requires(D == 3)
      {
          auto r = extent{};
          r._v.x() = std::numeric_limits<float>::signaling_NaN();
          r._v.y() = std::numeric_limits<float>::signaling_NaN();
          r._v.z() = std::numeric_limits<float>::signaling_NaN();
          return r;
      }
  
      /** Access the x-as-width element from the extent.
       * A extent can be seen as having a width, height and depth,
       * these accessors are aliases for x, y, and z.
       *
       * @return a reference to the x element.
       */
      [[nodiscard]] constexpr float &width() noexcept
      {
          return _v.x();
      }
  
      /** Access the y-as-height element from the extent.
       * A extent can be seen as having a width, height and depth,
       * these accessors are aliases for x, y, and z.
       *
       * @return a reference to the y element.
       */
      [[nodiscard]] constexpr float &height() noexcept
      {
          return _v.y();
      }
  
      /** Access the z-as-depth element from the extent.
       * A extent can be seen as having a width, height and depth,
       * these accessors are aliases for x, y, and z.
       *
       * @return a reference to the z element.
       */
      [[nodiscard]] constexpr float &depth() noexcept requires(D == 3)
      {
          return _v.z();
      }
  
      /** Access the x-as-width element from the extent.
       * A extent can be seen as having a width, height and depth,
       * these accessors are aliases for x, y, and z.
       *
       * @return a reference to the x element.
       */
      [[nodiscard]] constexpr float const &width() const noexcept
      {
          return _v.x();
      }
  
      /** Access the y-as-height element from the extent.
       * A extent can be seen as having a width, height and depth,
       * these accessors are aliases for x, y, and z.
       *
       * @return a reference to the y element.
       */
      [[nodiscard]] constexpr float const &height() const noexcept
      {
          return _v.y();
      }
  
      /** Access the z-as-depth element from the extent.
       * A extent can be seen as having a width, height and depth,
       * these accessors are aliases for x, y, and z.
       *
       * @return a reference to the z element.
       */
      [[nodiscard]] constexpr float const &depth() const noexcept requires(D == 3)
      {
          return _v.z();
      }
  
      [[nodiscard]] constexpr vector<D> right() const noexcept
      {
          return vector<D>{_v.x000()};
      }
  
      [[nodiscard]] constexpr vector<D> up() const noexcept
      {
          return vector<D>{_v._0y00()};
      }
  
      constexpr extent &operator+=(extent const &rhs) noexcept
      {
          return *this = *this + rhs;
      }
  
      /** Add two extents from each other.
       * @param lhs The first extent.
       * @param rhs The second extent.
       * @return A new extent.
       */
      [[nodiscard]] constexpr friend extent operator+(extent const &lhs, extent const &rhs) noexcept
      {
          tt_axiom(lhs.holds_invariant() && rhs.holds_invariant());
          return extent{lhs._v + rhs._v};
      }
  
      /** Subtract two extents from each other.
       * @param lhs The first extent.
       * @param rhs The second extent.
       * @return A new extent.
       */
      [[nodiscard]] constexpr friend extent operator-(extent const &lhs, extent const &rhs) noexcept
      {
          tt_axiom(lhs.holds_invariant() && rhs.holds_invariant());
          return extent{lhs._v - rhs._v};
      }
  
      constexpr friend scale<D> operator/(extent const &lhs, extent const &rhs) noexcept;
  
      /** Scale the extent by a scaler.
       * @param lhs The extent to scale.
       * @param rhs The scaling factor.
       * @return The scaled extent.
       */
      [[nodiscard]] constexpr friend extent operator*(extent const &lhs, float const &rhs) noexcept
      {
          tt_axiom(lhs.holds_invariant());
          return extent{lhs._v * rhs};
      }
  
      template<int E>
      [[nodiscard]] constexpr friend auto operator+(extent const &lhs, vector<E> const &rhs) noexcept
      {
          tt_axiom(lhs.holds_invariant());
          tt_axiom(rhs.holds_invariant());
  
          return extent<std::max(D, E)>{static_cast<f32x4>(lhs) + static_cast<f32x4>(rhs)};
      }
  
      template<int E>
      [[nodiscard]] constexpr friend auto operator+(vector<E> const &lhs, extent const &rhs) noexcept
      {
          tt_axiom(lhs.holds_invariant());
          tt_axiom(rhs.holds_invariant());
  
          return vector<std::max(D, E)>{static_cast<f32x4>(lhs) + static_cast<f32x4>(rhs)};
      }
  
      /** Add a scaler to the extent.
       * @param lhs The extent to scale.
       * @param rhs The scaling factor.
       * @return The scaled extent.
       */
      [[nodiscard]] constexpr friend extent operator+(extent const &lhs, float const &rhs) noexcept
      {
          tt_axiom(lhs.holds_invariant());
  
          auto r = extent{};
          for (std::size_t i = 0; i != D; ++i) {
              r._v[i] = lhs._v[i] + rhs;
          }
  
          return r;
      }
  
      /** Scale the extent by a scaler.
       * @param lhs The extent to scale.
       * @param rhs The scaling factor.
       * @return The scaled extent.
       */
      [[nodiscard]] constexpr friend extent operator*(float const &lhs, extent const &rhs) noexcept
      {
          tt_axiom(rhs.holds_invariant());
          return extent{lhs * rhs._v};
      }
  
      /** Compare if two extents are equal.
       * @param lhs The first extent.
       * @param rhs The second extent.
       * @return True if both extents are completely equal to each other.
       */
      [[nodiscard]] constexpr friend bool operator==(extent const &lhs, extent const &rhs) noexcept
      {
          tt_axiom(lhs.holds_invariant() && rhs.holds_invariant());
          return lhs._v == rhs._v;
      }
  
      [[nodiscard]] constexpr friend std::partial_ordering operator<=>(extent const &lhs, extent const &rhs) noexcept
          requires(D == 3)
      {
          constexpr std::size_t mask = 0b111;
  
          ttlet equal = eq(lhs._v, rhs._v) & mask;
          if (equal == mask) {
              // Only equivalent if all elements are equal.
              return std::partial_ordering::equivalent;
          }
  
          ttlet less = lt(lhs._v, rhs._v) & mask;
          if ((less | equal) == mask) {
              // If one or more elements is less (but none are greater) then the ordering is less.
              return std::partial_ordering::less;
          }
  
          ttlet greater = lt(lhs._v, rhs._v) & mask;
          if ((greater | equal) == mask) {
              // If one or more elements is greater (but none are less) then the ordering is greater.
              return std::partial_ordering::greater;
          }
  
          // Some elements are less and others are greater, we don't have an ordering.
          return std::partial_ordering::unordered;
      }
  
      [[nodiscard]] constexpr friend std::partial_ordering operator<=>(extent const &lhs, extent const &rhs) noexcept
          requires(D == 2)
      {
          constexpr std::size_t mask = 0b11;
  
          ttlet equal = eq(lhs._v, rhs._v) & mask;
          if (equal == mask) {
              // Only equivalent if all elements are equal.
              return std::partial_ordering::equivalent;
          }
  
          ttlet less = lt(lhs._v, rhs._v) & mask;
          if ((less | equal) == mask) {
              // If one or more elements is less (but none are greater) then the ordering is less.
              return std::partial_ordering::less;
          }
  
          ttlet greater = lt(lhs._v, rhs._v) & mask;
          if ((greater | equal) == mask) {
              // If one or more elements is greater (but none are less) then the ordering is greater.
              return std::partial_ordering::greater;
          }
  
          // Some elements are less and others are greater, we don't have an ordering.
          return std::partial_ordering::unordered;
      }
  
      /** Get the squared length of the extent.
       * @param rhs The extent.
       * @return The length of the extent.
       */
      [[nodiscard]] constexpr friend float squared_hypot(extent const &rhs) noexcept
      {
          tt_axiom(rhs.holds_invariant());
          return squared_hypot<element_mask>(rhs._v);
      }
  
      /** Get the length of the extent.
       * @param rhs The extent.
       * @return The length of the extent.
       */
      [[nodiscard]] constexpr friend float hypot(extent const &rhs) noexcept
      {
          tt_axiom(rhs.holds_invariant());
          return hypot<element_mask>(rhs._v);
      }
  
      /** Get the length of the extent.
       * @param rhs The extent.
       * @return One over the length of the extent.
       */
      [[nodiscard]] constexpr friend float rcp_hypot(extent const &rhs) noexcept
      {
          tt_axiom(rhs.holds_invariant());
          return rcp_hypot<element_mask>(rhs._v);
      }
  
      /** Normalize a extent to a unit extent.
       * @param rhs The extent.
       * @return A extent with the same direction as the given extent, but its length is 1.0.
       */
      [[nodiscard]] constexpr friend extent normalize(extent const &rhs) noexcept
      {
          tt_axiom(rhs.holds_invariant());
          return extent{normalize<element_mask>(rhs._v)};
      }
  
      [[nodiscard]] constexpr friend extent ceil(extent const &rhs) noexcept
      {
          tt_axiom(rhs.holds_invariant());
          return extent{ceil(f32x4{rhs})};
      }
  
      [[nodiscard]] constexpr friend extent floor(extent const &rhs) noexcept
      {
          tt_axiom(rhs.holds_invariant());
          return extent{floor(static_cast<f32x4>(rhs))};
      }
  
      [[nodiscard]] constexpr friend extent min(extent const &lhs, extent const &rhs) noexcept
      {
          return extent{min(static_cast<f32x4>(lhs), static_cast<f32x4>(rhs))};
      }
  
      [[nodiscard]] constexpr friend extent max(extent const &lhs, extent const &rhs) noexcept
      {
          return extent{max(static_cast<f32x4>(lhs), static_cast<f32x4>(rhs))};
      }
  
      [[nodiscard]] constexpr friend extent clamp(extent const &value, extent const &min, extent const &max) noexcept
      {
          return extent{clamp(static_cast<f32x4>(value), static_cast<f32x4>(min), static_cast<f32x4>(max))};
      }
  
      /** Check if the extent is valid.
       * Extends must be positive.
       * This function will check if w is zero, and with 2D extent is z is zero.
       */
      [[nodiscard]] constexpr bool holds_invariant() const noexcept
      {
          return _v.x() >= 0.0f && _v.y() >= 0.0f && _v.z() >= 0.0f && _v.w() == 0.0f && (D == 3 || _v.z() == 0.0f);
      }
  
      [[nodiscard]] friend std::string to_string(extent const &rhs) noexcept
      {
          if constexpr (D == 2) {
              return std::format("[{}, {}]", rhs._v.x(), rhs._v.y());
          } else if constexpr (D == 3) {
              return std::format("[{}, {}, {}]", rhs._v.x(), rhs._v.y(), rhs._v.z());
          } else {
              tt_static_no_default();
          }
      }
  
      friend std::ostream &operator<<(std::ostream &lhs, extent const &rhs) noexcept
      {
          return lhs << to_string(rhs);
      }
  
  private:
      f32x4 _v;
  
      static constexpr std::size_t element_mask = (1_uz << D) - 1;
  };
  
  } // namespace geo
  
  using extent2 = geo::extent<2>;
  using extent3 = geo::extent<3>;
  
  } // namespace tt::inline v1
  
  template<typename CharT>
  struct std::formatter<tt::geo::extent<2>, CharT> {
      auto parse(auto &pc)
      {
          return pc.end();
      }
  
      auto format(tt::geo::extent<2> const &t, auto &fc)
      {
          return std::vformat_to(fc.out(), "[{}, {}]", std::make_format_args(t.width(), t.height()));
      }
  };
  
  template<typename CharT>
  struct std::formatter<tt::geo::extent<3>, CharT> : formatter<float, CharT> {
      auto parse(auto &pc)
      {
          return pc.end();
      }
  
      auto format(tt::geo::extent<3> const &t, auto &fc)
      {
          return std::vformat_to(fc.out(), "[{}, {}, {}]", std::make_format_args(t.width(), t.height(), t.depth()));
      }
  };
  