// Copyright 2019 Google LLC
//
// 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
//
//     https://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.

// This file contains various C++ type aliases for use in Emboss.
#ifndef EMBOSS_RUNTIME_CPP_EMBOSS_CPP_TYPES_H_
#define EMBOSS_RUNTIME_CPP_EMBOSS_CPP_TYPES_H_

#include <climits>
#include <cstddef>
#include <cstdint>
#include <type_traits>

namespace emboss {
namespace support {

static_assert(sizeof(long long) * CHAR_BIT >= 64,  // NOLINT
              "Emboss requires that long long is at least 64 bits.");

// FloatType<n_bits>::Type is the C++ floating-point type of the appropriate
// size.
template <int kBits>
struct FloatType final {
  static_assert(kBits == 32 || kBits == 64, "Unknown floating-point size.");
};
template <>
struct FloatType<64> final {
  static_assert(sizeof(double) * CHAR_BIT == 64,
                "C++ double type must be 64 bits!");
  using Type = double;
  using UIntType = ::std::uint64_t;
};
template <>
struct FloatType<32> final {
  static_assert(sizeof(float) * CHAR_BIT == 32,
                "C++ float type must be 32 bits!");
  using Type = float;
  using UIntType = ::std::uint32_t;
};

// LeastWidthInteger<n_bits>::Unsigned is the smallest uintNN_t type that can
// hold n_bits or more.  LeastWidthInteger<n_bits>::Signed is the corresponding
// signed type.
template <int kBits>
struct LeastWidthInteger final {
  static_assert(kBits <= 64, "Only bit sizes up to 64 are supported.");
  using Unsigned = typename LeastWidthInteger<kBits + 1>::Unsigned;
  using Signed = typename LeastWidthInteger<kBits + 1>::Signed;
};
template <>
struct LeastWidthInteger<64> final {
  using Unsigned = ::std::uint64_t;
  using Signed = ::std::int64_t;
};
template <>
struct LeastWidthInteger<32> final {
  using Unsigned = ::std::uint32_t;
  using Signed = ::std::int32_t;
};
template <>
struct LeastWidthInteger<16> final {
  using Unsigned = ::std::uint16_t;
  using Signed = ::std::int16_t;
};
template <>
struct LeastWidthInteger<8> final {
  using Unsigned = ::std::uint8_t;
  using Signed = ::std::int8_t;
};

// IsAliasSafe<T>::value is true if T is an alias safe type; i.e. const?
// volatile? (unsigned)? char | std::byte.
template <typename T>
struct IsAliasSafe {
  // Note that 'char' is a distinct type from 'signed char' and 'unsigned char'.
  static constexpr bool value =
      ::std::is_same<char, typename ::std::remove_cv<T>::type>::value ||
      ::std::is_same<unsigned char, typename ::std::remove_cv<T>::type>::value
#if __cplusplus >= 201703
      || ::std::is_same<::std::byte, typename ::std::remove_cv<T>::type>::value
#endif
      ;
};

// The static member variable requires a definition.
template <typename T>
constexpr bool IsAliasSafe<T>::value;

// AddSourceConst<SourceT, DestT>::Type is DestT's base type with const added if
// SourceT is const.
template <typename SourceT, typename DestT>
struct AddSourceConst {
  using Type = typename ::std::conditional<
      /**/ ::std::is_const<SourceT>::value,
      typename ::std::add_const<DestT>::type, DestT>::type;
};

// AddSourceVolatile<SourceT, DestT>::Type is DestT's base type with volatile
// added if SourceT is volatile.
template <typename SourceT, typename DestT>
struct AddSourceVolatile {
  using Type = typename ::std::conditional<
      /**/ ::std::is_volatile<SourceT>::value,
      typename ::std::add_volatile<DestT>::type, DestT>::type;
};

// AddCV<SourceT, DestT>::Type is DestT's base type with SourceT's const and
// volatile qualifiers added, if any.
template <typename SourceT, typename DestT>
struct AddSourceCV {
  using Type = typename AddSourceConst<
      SourceT, typename AddSourceVolatile<SourceT, DestT>::Type>::Type;
};

}  // namespace support
}  // namespace emboss

#endif  // EMBOSS_RUNTIME_CPP_EMBOSS_CPP_TYPES_H_
