/*
 * Copyright 2022 The Android Open Source Project
 *
 * 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.
 */

#pragma once

#include <ftl/details/mixins.h>

namespace android::ftl {

// CRTP mixins for defining type-safe wrappers that are distinct from their underlying type. Common
// uses are IDs, opaque handles, and physical quantities. The constructor is provided by (and must
// be inherited from) the `Constructible` mixin, whereas operators (equality, ordering, arithmetic,
// etc.) are enabled through inheritance:
//
//   struct Id : ftl::Constructible<Id, std::int32_t>, ftl::Equatable<Id> {
//     using Constructible::Constructible;
//   };
//
//   static_assert(!std::is_default_constructible_v<Id>);
//
// Unlike `Constructible`, `DefaultConstructible` allows default construction. The default value is
// zero-initialized unless specified:
//
//   struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
//                  ftl::Equatable<Color>,
//                  ftl::Orderable<Color> {
//     using DefaultConstructible::DefaultConstructible;
//   };
//
//   static_assert(Color() == Color(0u));
//   static_assert(ftl::to_underlying(Color(-1)) == 255u);
//   static_assert(Color(1u) < Color(2u));
//
//   struct Sequence : ftl::DefaultConstructible<Sequence, std::int8_t, -1>,
//                     ftl::Equatable<Sequence>,
//                     ftl::Orderable<Sequence>,
//                     ftl::Incrementable<Sequence> {
//     using DefaultConstructible::DefaultConstructible;
//   };
//
//   static_assert(Sequence() == Sequence(-1));
//
// The underlying type need not be a fundamental type:
//
//   struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
//                    ftl::Equatable<Timeout>,
//                    ftl::Addable<Timeout> {
//     using DefaultConstructible::DefaultConstructible;
//   };
//
//   using namespace std::chrono_literals;
//   static_assert(Timeout() + Timeout(5s) == Timeout(15s));
//
template <typename Self, typename T>
struct Constructible {
  explicit constexpr Constructible(T value) : value_(value) {}

  explicit constexpr operator const T&() const { return value_; }

 private:
  template <typename, template <typename> class>
  friend class details::Mixin;

  T value_;
};

template <typename Self, typename T, auto kDefault = T{}>
struct DefaultConstructible : Constructible<Self, T> {
  using Constructible<Self, T>::Constructible;
  constexpr DefaultConstructible() : DefaultConstructible(T{kDefault}) {}
};

// Shorthand for casting a type-safe wrapper to its underlying value.
template <typename Self, typename T>
constexpr const T& to_underlying(const Constructible<Self, T>& c) {
  return static_cast<const T&>(c);
}

// Comparison operators for equality.
template <typename Self>
struct Equatable : details::Mixin<Self, Equatable> {
  constexpr bool operator==(const Self& other) const {
    return to_underlying(this->self()) == to_underlying(other);
  }

  constexpr bool operator!=(const Self& other) const { return !(*this == other); }
};

// Comparison operators for ordering.
template <typename Self>
struct Orderable : details::Mixin<Self, Orderable> {
  constexpr bool operator<(const Self& other) const {
    return to_underlying(this->self()) < to_underlying(other);
  }

  constexpr bool operator>(const Self& other) const { return other < this->self(); }
  constexpr bool operator>=(const Self& other) const { return !(*this < other); }
  constexpr bool operator<=(const Self& other) const { return !(*this > other); }
};

// Pre-increment and post-increment operators.
template <typename Self>
struct Incrementable : details::Mixin<Self, Incrementable> {
  constexpr Self& operator++() {
    ++this->mut();
    return this->self();
  }

  constexpr Self operator++(int) {
    const Self tmp = this->self();
    operator++();
    return tmp;
  }
};

// Additive operators, including incrementing.
template <typename Self>
struct Addable : details::Mixin<Self, Addable>, Incrementable<Self> {
  constexpr Self& operator+=(const Self& other) {
    this->mut() += to_underlying(other);
    return this->self();
  }

  constexpr Self operator+(const Self& other) const {
    Self tmp = this->self();
    return tmp += other;
  }

 private:
  using Base = details::Mixin<Self, Addable>;
  using Base::mut;
  using Base::self;
};

}  // namespace android::ftl
