// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// StrongInt<T> is a simple template class mechanism for defining "logical"
// integer-like class types that support almost all of the same functionality
// as native integer types, but which prevents assignment, construction, and
// other operations from other integer-like types.  In other words, you cannot
// assign from raw integer types or other StrongInt<> types, nor can you do
// most arithmetic or logical operations.  This provides a simple form of
// dimensionality in that you can add two instances of StrongInt<T>, producing
// a StrongInt<T>, but you can not add a StrongInt<T> and a raw T nor can you
// add a StrongInt<T> and a StrongInt<U>.  Details on supported operations are
// below.
//
// In addition to type strength, StrongInt provides a way to inject (optional)
// validation of the various operations.  This allows you to define StrongInt
// types that check for overflow conditions and react in standard or custom
// ways.
//
// A StrongInt<T> with a NullStrongIntValidator should compile away to a raw T
// in optimized mode.  What this means is that the generated assembly for:
//
//   int64 foo = 123;
//   int64 bar = 456;
//   int64 baz = foo + bar;
//   constexpr int64 fubar = 789;
//
// ...should be identical to the generated assembly for:
//
//    DEFINE_STRONG_INT_TYPE(MyStrongInt, int64);
//    MyStrongInt foo(123);
//    MyStrongInt bar(456);
//    MyStrongInt baz = foo + bar;
//    constexpr MyStrongInt fubar(789);
//
// Since the methods are all inline and non-virtual and the class has just
// one data member, the compiler can erase the StrongInt class entirely in its
// code-generation phase.  This also means that you can pass StrongInt<T>
// around by value just as you would a raw T.
//
// It is important to note that StrongInt does NOT generate compile time
// warnings or errors for overflows on implicit constant conversions.
//
// Usage:
//  StrongInt<TagType, NativeType, ValidatorType = NullStrongIntValidator>
//
//    Creates a new StrongInt instance directly.
//
//     TagType: The unique type which discriminates this StrongInt<T> from
//         other StrongInt<U> types.
//     NativeType: The primitive integral type this StrongInt will hold, as
//         defined by std::is_integral (see <type_traits>).
//     ValidatorType: The type of validation used by this StrongInt type.  A
//         few pre-built validator types are provided here, but the caller can
//         define any custom validator they desire.
//
// Supported operations:
//     StrongInt<T> = StrongInt<T>
//     !StrongInt<T> => bool
//     ~StrongInt<T> => StrongInt<T>
//     -StrongInt<T> => StrongInt<T>
//     +StrongInt<T> => StrongInt<T>
//     ++StrongInt<T> => StrongInt<T>
//     StrongInt<T>++ => StrongInt<T>
//     --StrongInt<T> => StrongInt<T>
//     StrongInt<T>-- => StrongInt<T>
//     StrongInt<T> + StrongInt<T> => StrongInt<T>
//     StrongInt<T> - StrongInt<T> => StrongInt<T>
//     StrongInt<T> * (numeric type) => StrongInt<T>
//     StrongInt<T> / (numeric type) => StrongInt<T>
//     StrongInt<T> % (numeric type) => StrongInt<T>
//     StrongInt<T> << (numeric type) => StrongInt<T>
//     StrongInt<T> >> (numeric type) => StrongInt<T>
//     StrongInt<T> & StrongInt<T> => StrongInt<T>
//     StrongInt<T> | StrongInt<T> => StrongInt<T>
//     StrongInt<T> ^ StrongInt<T> => StrongInt<T>
//
//   For binary operations, the equivalent op-equal (eg += vs. +) operations are
//   also supported.  Other operator combinations should cause compile-time
//   errors.
//
// Validators:
//   NullStrongIntValidator: Do no validation.  This should be entirely
//       optimized away by the compiler.

#ifndef MEDIAPIPE_DEPS_STRONG_INT_H_
#define MEDIAPIPE_DEPS_STRONG_INT_H_

#include <iosfwd>
#include <limits>
#include <ostream>
#include <type_traits>

#include "absl/base/macros.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/port.h"

namespace mediapipe {
namespace intops {

// Define the validators which can be plugged-in to make StrongInt resilient to
// things like overflows. This is a do-nothing implementation of the
// compile-time interface.
//
// NOTE: For all validation functions that operate on an existing StrongInt<T>,
// the type argument 'T' *must* be StrongInt<T>::ValueType (the int type being
// strengthened).
struct NullStrongIntValidator {
  // Verify initialization of StrongInt<T> from arg, type U.
  //
  // Note that this templated default implementation has an arbitrary bool
  // return value for the sole purpose of conforming to c++11 constexpr.
  //
  // Custom validator implementations can choose to return void or use a similar
  // return value constexpr construct if constexpr initialization is desirable.
  //
  // The StrongInt class does not care about or use the returned value. Any
  // returned value is solely there to allow the constexpr declaration; custom
  // validators can only fail / abort when detecting an invalid value.
  //
  // For example, other than the constexpr behavior, the below 2 custom
  // validator implementations are logically equivalent:
  //
  //   template<typename T, typename U>
  //   static void ValidateInit(U arg) {
  //     if (arg < 0) LOG(FATAL) << "arg < 0";
  //   }
  //
  //   template<typename T, typename U>
  //   static constexpr bool ValidateInit(U arg) {
  //     return (arg < 0) ? (LOG(FATAL) << "arg < 0", false) : false;
  //   }
  //
  // A constexpr ValidateInit implementation has the added advantage that the
  // validation can take place (fail) at compile time.
  template <typename T, typename U>
  static constexpr bool ValidateInit(U arg) {
    return true;
  }
  // Verify -value.
  template <typename T>
  static void ValidateNegate(T value) { /* do nothing */
  }
  // Verify ~value;
  template <typename T>
  static void ValidateBitNot(T value) { /* do nothing */
  }
  // Verify lhs + rhs.
  template <typename T>
  static void ValidateAdd(T lhs, T rhs) { /* do nothing */
  }
  // Verify lhs - rhs.
  template <typename T>
  static void ValidateSubtract(T lhs, T rhs) { /* do nothing */
  }
  // Verify lhs * rhs.
  template <typename T, typename U>
  static void ValidateMultiply(T lhs, U rhs) { /* do nothing */
  }
  // Verify lhs / rhs.
  template <typename T, typename U>
  static void ValidateDivide(T lhs, U rhs) { /* do nothing */
  }
  // Verify lhs % rhs.
  template <typename T, typename U>
  static void ValidateModulo(T lhs, U rhs) { /* do nothing */
  }
  // Verify lhs << rhs.
  template <typename T>
  static void ValidateLeftShift(T lhs, int64 rhs) { /* do nothing */
  }
  // Verify lhs >> rhs.
  template <typename T>
  static void ValidateRightShift(T lhs, int64 rhs) { /* do nothing */
  }
  // Verify lhs & rhs.
  template <typename T>
  static void ValidateBitAnd(T lhs, T rhs) { /* do nothing */
  }
  // Verify lhs | rhs.
  template <typename T>
  static void ValidateBitOr(T lhs, T rhs) { /* do nothing */
  }
  // Verify lhs ^ rhs.
  template <typename T>
  static void ValidateBitXor(T lhs, T rhs) { /* do nothing */
  }
};

// Holds an integer value (of type NativeType) and behaves as a NativeType by
// exposing assignment, unary, comparison, and arithmetic operators.
//
// This class is NOT thread-safe.
template <typename TagType, typename NativeType,
          typename ValidatorType = NullStrongIntValidator>
class StrongInt {
 public:
  typedef NativeType ValueType;

  // Default value initialization.
  constexpr StrongInt()
      : value_((ValidatorType::template ValidateInit<ValueType>(NativeType()),
                NativeType())) {}

  // Explicit initialization from another StrongInt type that has an
  // implementation of:
  //
  //    ToType StrongIntConvert(FromType source, ToType*);
  //
  // This uses Argument Dependent Lookup (ADL) to find which function to
  // call.
  //
  // Example: Assume you have two StrongInt types.
  //
  //      DEFINE_STRONG_INT_TYPE(Bytes, int64);
  //      DEFINE_STRONG_INT_TYPE(Megabytes, int64);
  //
  //  If you want to be able to (explicitly) construct an instance of Bytes from
  //  an instance of Megabytes, simply define a converter function in the same
  //  namespace as either Bytes or Megabytes (or both):
  //
  //      Megabytes StrongIntConvert(Bytes arg, Megabytes* /* unused */) {
  //        return Megabytes((arg >> 20).value());
  //      };
  //
  //  The second argument is needed to differentiate conversions, and it always
  //  passed as NULL.
  template <typename ArgTagType, typename ArgNativeType,
            typename ArgValidatorType>
  explicit StrongInt(
      StrongInt<ArgTagType, ArgNativeType, ArgValidatorType> arg) {
    // We have to pass both the "from" type and the "to" type as args for the
    // conversions to be differentiated.  The converter can not be a template
    // because explicit template call syntax defeats ADL.
    StrongInt *dummy = NULL;
    StrongInt converted = StrongIntConvert(arg, dummy);
    value_ = converted.value();
  }

  // Explicit initialization from a numeric primitive.
  template <typename T, typename = typename std::enable_if<
                            std::is_convertible<T, ValueType>::value>::type>
  explicit constexpr StrongInt(T init_value)
      : value_((ValidatorType::template ValidateInit<ValueType>(init_value),
                static_cast<ValueType>(init_value))) {}

  // Use the default copy constructor, assignment, and destructor.

  // Accesses the raw value.
  constexpr ValueType value() const { return value_; }

  // Accesses the raw value, with cast.
  // Primarily for compatibility with int-type.h
  template <typename ValType>
  constexpr ValType value() const {
    return static_cast<ValType>(value_);
  }

  // Metadata functions.
  static ValueType Max() { return std::numeric_limits<ValueType>::max(); }
  static ValueType Min() { return std::numeric_limits<ValueType>::min(); }

  // Unary operators.
  bool operator!() const { return value_ == 0; }
  const StrongInt operator+() const { return StrongInt(value_); }
  const StrongInt operator-() const {
    ValidatorType::template ValidateNegate<ValueType>(value_);
    return StrongInt(-value_);
  }
  const StrongInt operator~() const {
    ValidatorType::template ValidateBitNot<ValueType>(value_);
    return StrongInt(ValueType(~value_));
  }

  // Increment and decrement operators.
  StrongInt &operator++() {  // ++x
    ValidatorType::template ValidateAdd<ValueType>(value_, ValueType(1));
    ++value_;
    return *this;
  }
  const StrongInt operator++(int postfix_flag) {  // x++
    ValidatorType::template ValidateAdd<ValueType>(value_, ValueType(1));
    StrongInt temp(*this);
    ++value_;
    return temp;
  }
  StrongInt &operator--() {  // --x
    ValidatorType::template ValidateSubtract<ValueType>(value_, ValueType(1));
    --value_;
    return *this;
  }
  const StrongInt operator--(int postfix_flag) {  // x--
    ValidatorType::template ValidateSubtract<ValueType>(value_, ValueType(1));
    StrongInt temp(*this);
    --value_;
    return temp;
  }

  // Action-Assignment operators.
  StrongInt &operator+=(StrongInt arg) {
    ValidatorType::template ValidateAdd<ValueType>(value_, arg.value());
    value_ += arg.value();
    return *this;
  }
  StrongInt &operator-=(StrongInt arg) {
    ValidatorType::template ValidateSubtract<ValueType>(value_, arg.value());
    value_ -= arg.value();
    return *this;
  }
  template <typename ArgType>
  StrongInt &operator*=(ArgType arg) {
    ValidatorType::template ValidateMultiply<ValueType, ArgType>(value_, arg);
    value_ *= arg;
    return *this;
  }
  template <typename ArgType>
  StrongInt &operator/=(ArgType arg) {
    ValidatorType::template ValidateDivide<ValueType, ArgType>(value_, arg);
    value_ /= arg;
    return *this;
  }
  template <typename ArgType>
  StrongInt &operator%=(ArgType arg) {
    ValidatorType::template ValidateModulo<ValueType, ArgType>(value_, arg);
    value_ %= arg;
    return *this;
  }
  StrongInt &operator<<=(int64 arg) {  // NOLINT(whitespace/operators)
    ValidatorType::template ValidateLeftShift<ValueType>(value_, arg);
    value_ <<= arg;
    return *this;
  }
  StrongInt &operator>>=(int64 arg) {  // NOLINT(whitespace/operators)
    ValidatorType::template ValidateRightShift<ValueType>(value_, arg);
    value_ >>= arg;
    return *this;
  }
  StrongInt &operator&=(StrongInt arg) {
    ValidatorType::template ValidateBitAnd<ValueType>(value_, arg.value());
    value_ &= arg.value();
    return *this;
  }
  StrongInt &operator|=(StrongInt arg) {
    ValidatorType::template ValidateBitOr<ValueType>(value_, arg.value());
    value_ |= arg.value();
    return *this;
  }
  StrongInt &operator^=(StrongInt arg) {
    ValidatorType::template ValidateBitXor<ValueType>(value_, arg.value());
    value_ ^= arg.value();
    return *this;
  }

 private:
  // The integer value of type ValueType.
  ValueType value_;

  static_assert(std::is_integral<ValueType>::value,
                "invalid integer type for strong int");
};

// Provide the << operator, primarily for logging purposes.
template <typename TagType, typename ValueType, typename ValidatorType>
std::ostream &operator<<(std::ostream &os,
                         StrongInt<TagType, ValueType, ValidatorType> arg) {
  return os << arg.value();
}

// Provide the << operator, primarily for logging purposes. Specialized for int8
// so that an integer and not a character is printed.
template <typename TagType, typename ValidatorType>
std::ostream &operator<<(std::ostream &os,
                         StrongInt<TagType, int8, ValidatorType> arg) {
  return os << static_cast<int>(arg.value());
}

// Provide the << operator, primarily for logging purposes. Specialized for
// uint8 so that an integer and not a character is printed.
template <typename TagType, typename ValidatorType>
std::ostream &operator<<(std::ostream &os,
                         StrongInt<TagType, uint8, ValidatorType> arg) {
  return os << static_cast<unsigned int>(arg.value());
}

// Define operators that take two StrongInt arguments. These operators are
// defined in terms of their op-equal member function cousins.
#define STRONG_INT_VS_STRONG_INT_BINARY_OP(op)                            \
  template <typename TagType, typename ValueType, typename ValidatorType> \
  inline StrongInt<TagType, ValueType, ValidatorType> operator op(        \
      StrongInt<TagType, ValueType, ValidatorType> lhs,                   \
      StrongInt<TagType, ValueType, ValidatorType> rhs) {                 \
    lhs op## = rhs;                                                       \
    return lhs;                                                           \
  }
STRONG_INT_VS_STRONG_INT_BINARY_OP(+);
STRONG_INT_VS_STRONG_INT_BINARY_OP(-);
STRONG_INT_VS_STRONG_INT_BINARY_OP(&);
STRONG_INT_VS_STRONG_INT_BINARY_OP(|);
STRONG_INT_VS_STRONG_INT_BINARY_OP(^);
#undef STRONG_INT_VS_STRONG_INT_BINARY_OP

// Define operators that take one StrongInt and one native integer argument.
// These operators are defined in terms of their op-equal member function
// cousins, mostly.
#define STRONG_INT_VS_NUMERIC_BINARY_OP(op)                               \
  template <typename TagType, typename ValueType, typename ValidatorType, \
            typename NumType>                                             \
  inline StrongInt<TagType, ValueType, ValidatorType> operator op(        \
      StrongInt<TagType, ValueType, ValidatorType> lhs, NumType rhs) {    \
    lhs op## = rhs;                                                       \
    return lhs;                                                           \
  }
// This is used for commutative operators between one StrongInt and one native
// integer argument.  That is a long way of saying "multiplication".
#define NUMERIC_VS_STRONG_INT_BINARY_OP(op)                               \
  template <typename TagType, typename ValueType, typename ValidatorType, \
            typename NumType>                                             \
  inline StrongInt<TagType, ValueType, ValidatorType> operator op(        \
      NumType lhs, StrongInt<TagType, ValueType, ValidatorType> rhs) {    \
    rhs op## = lhs;                                                       \
    return rhs;                                                           \
  }
STRONG_INT_VS_NUMERIC_BINARY_OP(*);
NUMERIC_VS_STRONG_INT_BINARY_OP(*);
STRONG_INT_VS_NUMERIC_BINARY_OP(/);
STRONG_INT_VS_NUMERIC_BINARY_OP(%);
STRONG_INT_VS_NUMERIC_BINARY_OP(<<);  // NOLINT(whitespace/operators)
STRONG_INT_VS_NUMERIC_BINARY_OP(>>);  // NOLINT(whitespace/operators)
#undef STRONG_INT_VS_NUMERIC_BINARY_OP
#undef NUMERIC_VS_STRONG_INT_BINARY_OP

// Define comparison operators.  We allow all comparison operators.
#define STRONG_INT_COMPARISON_OP(op)                                          \
  template <typename TagType, typename ValueType, typename ValidatorType>     \
  inline bool operator op(StrongInt<TagType, ValueType, ValidatorType> lhs,   \
                          StrongInt<TagType, ValueType, ValidatorType> rhs) { \
    return lhs.value() op rhs.value();                                        \
  }
STRONG_INT_COMPARISON_OP(==);  // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(!=);  // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(<);   // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(<=);  // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(>);   // NOLINT(whitespace/operators)
STRONG_INT_COMPARISON_OP(>=);  // NOLINT(whitespace/operators)
#undef STRONG_INT_COMPARISON_OP

}  // namespace intops
}  // namespace mediapipe

#endif  // MEDIAPIPE_DEPS_STRONG_INT_H_
