// SPDX-License-Identifier: Apache-2.0
// Copyright Apache Software Foundation 2019
/** @file

  Scaled integral values.

  In many situations it is desirable to define scaling factors or base units (a "metric"). This template
  enables this to be done in a type and scaling safe manner where the defined factors carry their scaling
  information as part of the type.
*/
#pragma once

#include <cstdint>
#include <ratio>
#include <ostream>
#include <type_traits>

#include "swoc/swoc_version.h"
#include "swoc/swoc_meta.h"

namespace swoc { inline namespace SWOC_VERSION_NS {

namespace tag {
/// A generic tag for @c Scalar types, used as the default.
struct generic;
} // namespace tag

template <intmax_t N, typename C, typename T> class Scalar;

namespace detail {
/// @cond INTERNAL_DETAIL

// @internal - although these conversion methods look bulky, in practice they compile down to
// very small amounts of code due to the conditions being compile time constant - the non-taken
// clauses are dead code and eliminated by the compiler.

// The general case where neither N nor S are a multiple of the other seems a bit long but this
// minimizes the risk of integer overflow.  I need to validate that under -O2 the compiler will
// only do 1 division to get both the quotient and remainder for (n/N) and (n%N). In cases where
// N,S are powers of 2 I have verified recent GNU compilers will optimize to bit operations.

// Convert a count @a c that is scale @a S to scale @c N
template <intmax_t N, intmax_t S, typename C>
C
scale_conversion_round_up(C c) {
  using R = std::ratio<N, S>;
  if constexpr (N == S) {
    return c;
  } else if constexpr (R::den == 1) {
    return c / R::num + (0 != c % R::num); // N is a multiple of S.
  } else if constexpr (R::num == 1) {
    return c * R::den; // S is a multiple of N.
  }
  return (c / R::num) * R::den + ((c % R::num) * R::den) / R::num + (0 != (c % R::num));
}

// Convert a count @a c that is scale @a S to scale @c N
template <intmax_t N, intmax_t S, typename C>
C
scale_conversion_round_down(C c) {
  using R = std::ratio<N, S>;
  if constexpr (N == S) {
    return c;
  } else if constexpr (R::den == 1) {
    return c / R::num; // N = k S
  } else if constexpr (R::num == 1) {
    return c * R::den; // S = k N
  }
  return (c / R::num) * R::den + ((c % R::num) * R::den) / R::num;
}

/* Helper classes for @c Scalar

   These wrap values to capture extra information for @c Scalar methods. This includes whether to
   round up or down when converting and, when the wrapped data is also a @c Scalar, the scale.

   These are not intended for direct use but by the @c round_up and @c round_down free functions
   which capture the information about the argument and construct an instance of one of these
   classes to pass it on to a @c Scalar method.

   Scale conversions between @c Scalar instances are handled in these classes via the templated
   methods @c scale_conversion_round_up and @c scale_conversion_round_down.

   Conversions between scales and types for the scalar helpers is done inside the helper classes
   and a user type conversion operator exists so the helper can be converted by the compiler to
   the correct type. For the units base conversion this is done in @c Scalar because the
   generality of the needed conversion is too broad to be easily used. It can be done but there is
   some ugliness due to the fact that in some cases two user conversions are needed, which is
   difficult to deal with. I have tried it both ways and overall this seems a cleaner
   implementation.

   Much of this is driven by the fact that the assignment operator, in some cases, can not be
   templated and therefore to have a nice interface for assignment this split is needed.

   Note - the key point is the actual conversion is not done when the wrapper instance is created
   but when the wrapper instance is assigned. That is what enables the conversion to be done in
   the context of the destination, which is not otherwise possible.
 */

// Unit value, to be rounded up.
template <typename C> struct scalar_unit_round_up_t {
  C _n;

  template <intmax_t N, typename I>
  constexpr I
  scale() const {
    return static_cast<I>(_n / N + (0 != (_n % N)));
  }
};

// Unit value, to be rounded down.
template <typename C> struct scalar_unit_round_down_t {
  C _n;

  template <intmax_t N, typename I>
  constexpr I
  scale() const {
    return static_cast<I>(_n / N);
  }
};

// Scalar value, to be rounded up.
template <intmax_t N, typename C, typename T> struct scalar_round_up_t {
  C _n;

  template <intmax_t S, typename I>
  constexpr
  operator Scalar<S, I, T>() const {
    return Scalar<S, I, T>(scale_conversion_round_up<S, N>(_n));
  }
};

// Scalar value, to be rounded down.
template <intmax_t N, typename C, typename T> struct scalar_round_down_t {
  C _n;

  template <intmax_t S, typename I>
  constexpr
  operator Scalar<S, I, T>() const {
    return Scalar<S, I, T>(scale_conversion_round_down<S, N>(_n));
  }
};

/// @endcond
} // namespace detail

/** A class to hold scaled values.

    Instances of this class have a @a count and a @a scale. The "value" of the instance is @a
    count * @a scale.  The scale is stored in the compiler in the class symbol table and so only
    the count is a run time value. An instance with a large scale can be assigned to an instance
    with a smaller scale and the conversion is done automatically. Conversions from a smaller to
    larger scale must be explicit using @c round_up and @c round_down. This prevents
    inadvertent changes in value. Because the scales are not the same these conversions can be
    lossy and the two conversions determine whether, in such a case, the result should be rounded
    up or down to the nearest scale value.

    @a N sets the scale. @a C is the type used to hold the count, which is in units of @a N.

    @a T is a "tag" type which is used only to distinguish the base metric for the scale. Scalar
    types that have different tags are not interoperable although they can be converted manually by
    converting to units and then explicitly constructing a new Scalar instance. This is by design.
    This can be ignored - if not specified then it defaults to a "generic" tag. The type can be (and
    usually is) defined in name only).

    @note This is modeled somewhat on @c std::chrono and serves a similar function for different
    and simpler cases (where the ratio is always an integer, never a fraction).

    @see round_up
    @see round_down
 */
template <intmax_t N, typename C = int, typename T = tag::generic> class Scalar {
  using self_type = Scalar; ///< Self reference type.

public:
  /// Scaling factor - make it external accessible.
  constexpr static intmax_t SCALE = N;
  using Counter                   = C; ///< Type used to hold the count.
  using Tag                       = T; ///< Tag for the scalar.

  static_assert(N > 0, "The scaling factor (1st template argument) must be a positive integer");
  static_assert(std::is_integral<C>::value, "The counter type (2nd template argument) must be an integral type");

  constexpr Scalar(); ///< Default constructor.

  /// Construct to have value that is @a n scaled units.
  explicit constexpr Scalar(Counter n);

  /// Copy constructor.
  constexpr Scalar(self_type const &that); /// Copy constructor.

  /// Copy constructor for same scale.
  template <typename I> constexpr Scalar(Scalar<N, I, T> const &that);

  /// Direct conversion constructor.
  /// @note Requires that @c S be an integer multiple of @c SCALE.
  template <intmax_t S, typename I> constexpr Scalar(Scalar<S, I, T> const &that);

  /// @cond INTERNAL_DETAIL
  // Assignment from internal rounding structures.
  // Conversion constructor.
  constexpr Scalar(detail::scalar_round_up_t<N, C, T> const &v);

  // Conversion constructor.
  constexpr Scalar(detail::scalar_round_down_t<N, C, T> const &that);

  // Conversion constructor.
  template <typename I> constexpr Scalar(detail::scalar_unit_round_up_t<I> v);

  // Conversion constructor.
  template <typename I> constexpr Scalar(detail::scalar_unit_round_down_t<I> v);
  /// @endcond

  /** Assign value from @a that.
   *
   * @tparam S Scale.
   * @tparam I Integral type.
   * @param that Source value.
   * @return @a this.
   *
   * @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this
   * isn't the case then the @c round_up or @c round_down must be used to indicate the rounding
   * direction.
   */
  template <intmax_t S, typename I> self_type &operator=(Scalar<S, I, T> const &that);

  /// Self type assignment.
  self_type &operator=(self_type const &that);

  /// @cond INTERNAL_DETAIL
  /** Internal method to assign a unit value to be rounded up to the internal @c SCALE.
   *
   * @tparam I The underlying scale type.
   * @param n The value to be scaled.
   * @return @a this
   */
  template <typename I> self_type &operator=(detail::scalar_unit_round_up_t<I> n);

  /** Internal method to assign a unit value to be rounded down to the internal @c SCALE.
   *
   * @tparam I The underlying scale type.
   * @param n The value to be scaled.
   * @return @a this
   */
  template <typename I> self_type &operator=(detail::scalar_unit_round_down_t<I> n);

  /** Internal method to assign a differently scaled SCALAR to be rounded up.
   *
   * @param v The embedding of the value to be scaled.
   * @return @a this
   *
   * The template parameters of @a v carry the static information needed to correctly scale
   * it to the local scale.
   */
  self_type &operator=(detail::scalar_round_up_t<N, C, T> v);

  /** Internal method to assign a differently scaled SCALAR to be rounded down.
   *
   * @param v The embedding of the value to be scaled.
   * @return @a this
   *
   * The template parameters of @a v carry the static information needed to correctly scale
   * it to the local scale.
   */
  self_type &operator=(detail::scalar_round_down_t<N, C, T> v);
  /// @endcond

  /** Set the scaled count to @a n.
   *
   * @param n The number of scale units.
   * @return @a this
   *
   * This sets the count to be @a n, making the effective value @c SCALE*n. This is frequently useful
   * for parsing, when the input is also in scaled units (e.g., the number of megabytes to use for
   * a file).
   */
  self_type &assign(Counter n);

  /// The value @a that is scaled appropriately.
  /// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this
  /// isn't the case then the @c round_up or @c round_down must be used to indicate the rounding
  /// direction.
  template <intmax_t S, typename I> self_type &assign(Scalar<S, I, T> const &that);

  /// @cond INTERNAL_DETAIL
  template <typename I> self_type &assign(detail::scalar_unit_round_up_t<I> n);

  template <typename I> self_type &assign(detail::scalar_unit_round_down_t<I> n);

  self_type &assign(detail::scalar_round_up_t<N, C, T> v);

  self_type &assign(detail::scalar_round_down_t<N, C, T> v);
  /// @endcond

  /// The number of scale units.
  constexpr Counter count() const;

  /// The scaled value.
  constexpr Counter value() const;

  /// User conversion to scaled value.
  constexpr operator Counter() const;

  /// Addition operator.
  /// The value is scaled from @a that to @a this.
  /// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
  /// the @c scale_up or @c scale_down casts must be used to indicate the rounding direction.
  self_type &operator+=(self_type const &that);

  /** Cross scale addition.
   *
   * @tparam S Source scale.
   * @tparam I Source raw type.
   * @param that Value.
   * @return @a this
   *
   * @a that is scaled as needed to be added to @a this.
   */
  template <intmax_t S, typename I> self_type &operator+=(Scalar<S, I, T> const &that);

  /// @cond INTERNAL_DETAIL
  template <typename I> self_type &operator+=(detail::scalar_unit_round_up_t<I> n);

  template <typename I> self_type &operator+=(detail::scalar_unit_round_down_t<I> n);

  self_type &operator+=(detail::scalar_round_up_t<N, C, T> v);

  self_type &operator+=(detail::scalar_round_down_t<N, C, T> v);
  /// @endcond

  /// Increment - increase count by 1.
  self_type &operator++();

  /// Increment - increase count by 1.
  self_type operator++(int);

  /// Decrement - decrease count by 1.
  self_type &operator--();

  /// Decrement - decrease count by 1.
  self_type operator--(int);

  /// Increment by @a n.
  self_type &inc(Counter n);

  /// Decrement by @a n.
  self_type &dec(Counter n);

  /// Subtraction operator.
  /// The value is scaled from @a that to @a this.
  /// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
  /// the @c scale_up or @c scale_down casts must be used to indicate the rounding direction.
  self_type &operator-=(self_type const &that);

  /** Subtraction.
   *
   * @tparam S Scale.
   * @tparam I Raw type.
   * @param that Value to subtract.
   * @return @a this
   *
   * The value of @a that is subtracted from the value of @a this.
   */
  template <intmax_t S, typename I> self_type &operator-=(Scalar<S, I, T> const &that);

  /// @cond INTERNAL_DETAIL
  template <typename I> self_type &operator-=(detail::scalar_unit_round_up_t<I> n);

  template <typename I> self_type &operator-=(detail::scalar_unit_round_down_t<I> n);

  self_type &operator-=(detail::scalar_round_up_t<N, C, T> v);

  self_type &operator-=(detail::scalar_round_down_t<N, C, T> v);
  /// @endcond

  /// Multiplication - multiple the count by @a n.
  self_type &operator*=(C n);

  /// Division - divide (rounding down) the count by @a n.
  self_type &operator/=(C n);

  /// Utility overload of the function operator to create instances at the same scale.
  self_type operator()(Counter n) const;

  /// Return a value at the same scale with a count increased by @a n.
  self_type plus(Counter n) const;

  /// Return a value at the same scale with a count decreased by @a n.
  self_type minus(Counter n) const;

  /// Run time access to the scale (template arg @a N).
  static constexpr intmax_t scale();

protected:
  Counter _n; ///< Number of scale units.
};

/// @cond Scalar_INTERNAL
// Avoid issues with doxygen matching externally defined methods.
template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar() : _n() {}

template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar(Counter n) : _n(n) {}

template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar(self_type const &that) : _n(that._n) {}

template <intmax_t N, typename C, typename T>
template <typename I>
constexpr Scalar<N, C, T>::Scalar(Scalar<N, I, T> const &that) : _n(static_cast<C>(that.count())) {}

template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
constexpr Scalar<N, C, T>::Scalar(Scalar<S, I, T> const &that) : _n(std::ratio<S, N>::num * that.count()) {
  static_assert(std::ratio<S, N>::den == 1,
                "Construction not permitted - target scale is not an integral multiple of source scale.");
}

template <intmax_t N, typename C, typename T>
constexpr Scalar<N, C, T>::Scalar(detail::scalar_round_up_t<N, C, T> const &v) : _n(v._n) {}

template <intmax_t N, typename C, typename T>
constexpr Scalar<N, C, T>::Scalar(detail::scalar_round_down_t<N, C, T> const &v) : _n(v._n) {}

template <intmax_t N, typename C, typename T>
template <typename I>
constexpr Scalar<N, C, T>::Scalar(detail::scalar_unit_round_up_t<I> v) : _n(v.template scale<N, C>()) {}

template <intmax_t N, typename C, typename T>
template <typename I>
constexpr Scalar<N, C, T>::Scalar(detail::scalar_unit_round_down_t<I> v) : _n(v.template scale<N, C>()) {}

template <intmax_t N, typename C, typename T>
constexpr auto
Scalar<N, C, T>::count() const -> Counter {
  return _n;
}

template <intmax_t N, typename C, typename T>
constexpr C
Scalar<N, C, T>::value() const {
  return _n * SCALE;
}

template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::operator Counter() const {
  return _n * SCALE;
}

template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::assign(Counter n) -> self_type & {
  _n = n;
  return *this;
}

template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::operator=(self_type const &that) -> self_type & {
  _n = that._n;
  return *this;
}

template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::operator=(detail::scalar_round_up_t<N, C, T> v) -> self_type & {
  _n = v._n;
  return *this;
}

template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::assign(detail::scalar_round_up_t<N, C, T> v) -> self_type & {
  _n = v._n;
  return *this;
}

template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::operator=(detail::scalar_round_down_t<N, C, T> v) -> self_type & {
  _n = v._n;
  return *this;
}

template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::assign(detail::scalar_round_down_t<N, C, T> v) -> self_type & {
  _n = v._n;
  return *this;
}

template <intmax_t N, typename C, typename T>
template <typename I>
inline auto
Scalar<N, C, T>::operator=(detail::scalar_unit_round_up_t<I> v) -> self_type & {
  _n = v.template scale<N, C>();
  return *this;
}

template <intmax_t N, typename C, typename T>
template <typename I>
inline auto
Scalar<N, C, T>::assign(detail::scalar_unit_round_up_t<I> v) -> self_type & {
  _n = v.template scale<N, C>();
  return *this;
}

template <intmax_t N, typename C, typename T>
template <typename I>
inline auto
Scalar<N, C, T>::operator=(detail::scalar_unit_round_down_t<I> v) -> self_type & {
  _n = v.template scale<N, C>();
  return *this;
}

template <intmax_t N, typename C, typename T>
template <typename I>
inline auto
Scalar<N, C, T>::assign(detail::scalar_unit_round_down_t<I> v) -> self_type & {
  _n = v.template scale<N, C>();
  return *this;
}

template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
auto
Scalar<N, C, T>::operator=(Scalar<S, I, T> const &that) -> self_type & {
  using R = std::ratio<S, N>;
  static_assert(R::den == 1, "Assignment not permitted - target scale is not an integral multiple of source scale.");
  _n = that.count() * R::num;
  return *this;
}

template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
auto
Scalar<N, C, T>::assign(Scalar<S, I, T> const &that) -> self_type & {
  using R = std::ratio<S, N>;
  static_assert(R::den == 1, "Assignment not permitted - target scale is not an integral multiple of source scale.");
  _n = that.count() * R::num;
  return *this;
}

template <intmax_t N, typename C, typename T>
constexpr inline intmax_t
Scalar<N, C, T>::scale() {
  return SCALE;
}

/// @endcond Scalar_INTERNAL

// --- Functions ---

/** Prepare units @a n to be assigned to a @c Scalar, rounding up as needed.
 *
 * @tparam C The type of the value.
 * @param n The value.
 * @return An unspecified type suitable to be assigned to a @c Scalar.
 */
template <typename C>
constexpr detail::scalar_unit_round_up_t<C>
round_up(C n) {
  return {n};
}

/** Prepare a @c Scalar instance to be assigned to another @c Scalar, rounding up as needed.
 *
 * @tparam N @c Scalar scale value.
 * @tparam C @c Scalar internal storage type.
 * @tparam T @c Scalar tag.
 * @param v The @c Scalar value.
 * @return
 */
template <intmax_t N, typename C, typename T>
constexpr detail::scalar_round_up_t<N, C, T>
round_up(Scalar<N, C, T> v) {
  return {v.count()};
}

/** Prepare units @a n to be assigned to a @c Scalar, rounding down as needed.
 *
 * @tparam C The type of the value.
 * @param n The value.
 * @return An unspecified type suitable to be assigned to a @c Scalar.
 */
template <typename C>
constexpr detail::scalar_unit_round_down_t<C>
round_down(C n) {
  return {n};
}

/** Prepare a @c Scalar instance to be assigned to another @c Scalar, rounding down as needed.
 *
 * @tparam N @c Scalar scale value.
 * @tparam C @c Scalar internal storage type.
 * @tparam T @c Scalar tag.
 * @param v The @c Scalar value.
 * @return
 */
template <intmax_t N, typename C, typename T>
constexpr detail::scalar_round_down_t<N, C, T>
round_down(Scalar<N, C, T> v) {
  return {v.count()};
}

// --- Compare operators
// These optimize nicely because if R::num or R::den is 1 the compiler will drop it.

template <intmax_t N, typename C1, intmax_t S, typename I, typename T>
bool
operator<(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs) {
  using R = std::ratio<N, S>;
  return lhs.count() * R::num < rhs.count() * R::den;
}

template <intmax_t N, typename C1, intmax_t S, typename I, typename T>
bool
operator==(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs) {
  using R = std::ratio<N, S>;
  return lhs.count() * R::num == rhs.count() * R::den;
}

template <intmax_t N, typename C1, intmax_t S, typename I, typename T>
bool
operator<=(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs) {
  using R = std::ratio<N, S>;
  return lhs.count() * R::num <= rhs.count() * R::den;
}

// Derived compares.
template <intmax_t N, typename C, intmax_t S, typename I, typename T>
bool
operator>(Scalar<N, C, T> const &lhs, Scalar<S, I, T> const &rhs) {
  return rhs < lhs;
}

template <intmax_t N, typename C, intmax_t S, typename I, typename T>
bool
operator>=(Scalar<N, C, T> const &lhs, Scalar<S, I, T> const &rhs) {
  return rhs <= lhs;
}

// Arithmetic operators
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator+=(self_type const &that) -> self_type & {
  _n += that._n;
  return *this;
}

template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
auto
Scalar<N, C, T>::operator+=(Scalar<S, I, T> const &that) -> self_type & {
  using R = std::ratio<S, N>;
  static_assert(R::den == 1, "Addition not permitted - target scale is not an integral multiple of source scale.");
  _n += that.count() * R::num;
  return *this;
}

/// @cond INTERNAL_DETAIL
template <intmax_t N, typename C, typename T>
template <typename I>
auto
Scalar<N, C, T>::operator+=(detail::scalar_unit_round_up_t<I> v) -> self_type & {
  _n += v.template scale<N, C>();
  return *this;
}

template <intmax_t N, typename C, typename T>
template <typename I>
auto
Scalar<N, C, T>::operator+=(detail::scalar_unit_round_down_t<I> v) -> self_type & {
  _n += v.template scale<N, C>();
  return *this;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator+=(detail::scalar_round_up_t<N, C, T> v) -> self_type & {
  _n += v._n;
  return *this;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator+=(detail::scalar_round_down_t<N, C, T> v) -> self_type & {
  _n += v._n;
  return *this;
}

/// @endcond

template <intmax_t N, typename C, intmax_t S, typename I, typename T>
auto
operator+(Scalar<N, C, T> lhs, Scalar<S, I, T> const &rhs) -> typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type {
  return typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type(lhs) += rhs;
}

/** Add a two scalars of the same type.
 * @return A scalar of the same type holding the sum.
 */
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(lhs) += rhs;
}

/// @cond INTERNAL_DETAIL
// These handle adding a wrapper and a scalar.
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator+(detail::scalar_unit_round_up_t<I> lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(rhs) += lhs;
}

template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_up_t<I> rhs) {
  return Scalar<N, C, T>(lhs) += rhs;
}

template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator+(detail::scalar_unit_round_down_t<I> lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(rhs) += lhs;
}

template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_down_t<I> rhs) {
  return Scalar<N, C, T>(lhs) += rhs;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(detail::scalar_round_up_t<N, C, T> lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(rhs) += lhs._n;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, detail::scalar_round_up_t<N, C, T> rhs) {
  return Scalar<N, C, T>(lhs) += rhs._n;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(detail::scalar_round_down_t<N, C, T> lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(rhs) += lhs._n;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs) {
  return Scalar<N, C, T>(lhs) += rhs._n;
}
/// @endcond

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator-=(self_type const &that) -> self_type & {
  _n -= that._n;
  return *this;
}

template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
auto
Scalar<N, C, T>::operator-=(Scalar<S, I, T> const &that) -> self_type & {
  using R = std::ratio<S, N>;
  static_assert(R::den == 1, "Subtraction not permitted - target scale is not an integral multiple of source scale.");
  _n -= that.count() * R::num;
  return *this;
}

/// @cond INTERNAL_DETAIL

template <intmax_t N, typename C, typename T>
template <typename I>
auto
Scalar<N, C, T>::operator-=(detail::scalar_unit_round_up_t<I> v) -> self_type & {
  _n -= v.template scale<N, C>();
  return *this;
}

template <intmax_t N, typename C, typename T>
template <typename I>
auto
Scalar<N, C, T>::operator-=(detail::scalar_unit_round_down_t<I> v) -> self_type & {
  _n -= v.template scale<N, C>();
  return *this;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator-=(detail::scalar_round_up_t<N, C, T> v) -> self_type & {
  _n -= v._n;
  return *this;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator-=(detail::scalar_round_down_t<N, C, T> v) -> self_type & {
  _n -= v._n;
  return *this;
}

/// @endcond

template <intmax_t N, typename C, intmax_t S, typename I, typename T>
auto
operator-(Scalar<N, C, T> lhs, Scalar<S, I, T> const &rhs) -> typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type {
  return typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type(lhs) -= rhs;
}

/** Subtract scalars.
 * @return A scalar of the same type holding the difference @a lhs - @a rhs
 */
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(lhs) -= rhs;
}

/// @cond INTERNAL_DETAIL
// Handle subtraction for intermediate wrappers.
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator-(detail::scalar_unit_round_up_t<I> lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(lhs.template scale<N, C>()) -= rhs;
}

template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_up_t<I> rhs) {
  return Scalar<N, C, T>(lhs) -= rhs;
}

template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator-(detail::scalar_unit_round_down_t<I> lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(lhs.template scale<N, C>()) -= rhs;
}

template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_down_t<I> rhs) {
  return Scalar<N, C, T>(lhs) -= rhs;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(detail::scalar_round_up_t<N, C, T> lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(lhs._n) -= rhs;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, detail::scalar_round_up_t<N, C, T> rhs) {
  return Scalar<N, C, T>(lhs) -= rhs._n;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(detail::scalar_round_down_t<N, C, T> lhs, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(lhs._n) -= rhs;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs) {
  return Scalar<N, C, T>(lhs) -= rhs._n;
}
/// @endcond

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator++() -> self_type & {
  ++_n;
  return *this;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator++(int) -> self_type {
  self_type zret(*this);
  ++_n;
  return zret;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator--() -> self_type & {
  --_n;
  return *this;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator--(int) -> self_type {
  self_type zret(*this);
  --_n;
  return zret;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::inc(Counter n) -> self_type & {
  _n += n;
  return *this;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::dec(Counter n) -> self_type & {
  _n -= n;
  return *this;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator*=(C n) -> self_type & {
  _n *= n;
  return *this;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator*(Scalar<N, C, T> const &lhs, C n) {
  return Scalar<N, C, T>(lhs) *= n;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator*(C n, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(rhs) *= n;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator*(Scalar<N, C, T> const &lhs, int n) {
  return Scalar<N, C, T>(lhs) *= n;
}

template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator*(int n, Scalar<N, C, T> const &rhs) {
  return Scalar<N, C, T>(rhs) *= n;
}

template <intmax_t N>
Scalar<N, int>
operator*(Scalar<N, int> const &lhs, int n) {
  return Scalar<N, int>(lhs) *= n;
}

template <intmax_t N>
Scalar<N, int>
operator*(int n, Scalar<N, int> const &rhs) {
  return Scalar<N, int>(rhs) *= n;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator/=(C n) -> self_type & {
  _n /= n;
  return *this;
}

template <intmax_t N, typename C, intmax_t S, typename I, typename T>
auto
operator/(Scalar<N, C, T> lhs, Scalar<S, I, T> rhs) -> typename std::common_type<C, I>::type {
  using R = std::ratio<N, S>;
  return (lhs.count() * R::num) / (rhs.count() * R::den);
}

template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator/(Scalar<N, C, T> lhs, I n) {
  static_assert(std::is_integral<I>::value, "Scalar divsion only support integral types.");
  return Scalar<N, C, T>(lhs) /= n;
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator()(Counter n) const -> self_type {
  return self_type{n};
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::plus(Counter n) const -> self_type {
  return {_n + n};
}

template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::minus(Counter n) const -> self_type {
  return {_n - n};
}

/** Explicitly round @c value up to a multiple of @a N.
 *
 * @tparam N [explicit] Rounding unit.
 * @tparam C [deduced] Base type of @a value.
 * @param value Value to round.
 * @return The smallest multiple of @a N that is at least as large as @a value.
 *
 * @code
 * int r = swoc::round_up<10>(119); // r becomes 120.
 * int r = swoc::round_up<10>(120); // r becomes 120.
 * int r = swoc::round_up<10>(121); // r becomes 130.
 * @endcode
 */
template <intmax_t N, typename C>
C
round_up(C value) {
  return N * detail::scale_conversion_round_up<N, 1>(value);
}

/** Explicitly round @a value to a multiple of @a N.
 *
 * @tparam N [explicit] Rounding unit.
 * @tparam C [deduced] Base type of @a value.
 * @param value Value to round.
 * @return The largest multiple of @a N that is not greater than @a value.
 *
 * @code
 * int r = swoc::round_down<10>(119); // r becomes 110.
 * int r = swoc::round_down<10>(120); // r becomes 120.
 * int r = swoc::round_down<10>(121); // r becomes 120.
 * @endcode
 */
template <intmax_t N, typename C>
C
round_down(C value) {
  return N * detail::scale_conversion_round_down<N, 1>(value);
}

namespace detail {
template <typename T>
auto
tag_label(std::ostream &, const meta::CaseTag<0> &) -> void {}

template <typename T>
auto
tag_label(std::ostream &w, const meta::CaseTag<1> &) -> decltype(T::label, meta::TypeFunc<void>()) {
  w << T::label;
}

template <typename T>
inline std::ostream &
tag_label(std::ostream &w) {
  tag_label<T>(w, meta::CaseArg);
  return w;
}
} // namespace detail
}} // namespace swoc::SWOC_VERSION_NS

namespace std {
/// Compute common type of two scalars.
/// This is in `std` to overload the base definition. This yields a type that has the common type of
/// the counter type and a scale that is the GCF of the input scales.
template <intmax_t N, typename C, intmax_t S, typename I, typename T>
struct common_type<swoc::Scalar<N, C, T>, swoc::Scalar<S, I, T>> {
  using R    = std::ratio<N, S>;
  using type = swoc::Scalar<N / R::num, typename common_type<C, I>::type, T>;
};

template <intmax_t N, typename C, typename T>
ostream &
operator<<(ostream &s, swoc::Scalar<N, C, T> const &x) {
  s << x.value();
  return swoc::detail::tag_label<T>(s);
}

} // namespace std
