// SPDX-License-Identifier: BSD-3-Clause
// Ceres Solver - A fast non-linear least squares minimizer
// Copyright 2022 Google Inc. All rights reserved.
// http://ceres-solver.org/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name of Google Inc. nor the names of its contributors may be
//   used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Author: sergiu.deitsch@gmail.com (Sergiu Deitsch)
//

#ifndef CERES_PUBLIC_INTERNAL_JET_TRAITS_H_
#define CERES_PUBLIC_INTERNAL_JET_TRAITS_H_

#include <tuple>
#include <type_traits>
#include <utility>

#include "tinyceres/internal/integer_sequence_algorithm.hpp"
#include "tinyceres/jet_fwd.hpp"

namespace ceres {
namespace internal {

// Predicate that determines whether any of the Types is a Jet.
template <typename... Types>
struct AreAnyJet : std::false_type {};

template <typename T, typename... Types>
struct AreAnyJet<T, Types...> : AreAnyJet<Types...> {};

template <typename T, int N, typename... Types>
struct AreAnyJet<Jet<T, N>, Types...> : std::true_type {};

// Convenience variable template for AreAnyJet.
template <typename... Types>
inline constexpr bool AreAnyJet_v = AreAnyJet<Types...>::value;

// Extracts the underlying floating-point from a type T.
template <typename T, typename E = void>
struct UnderlyingScalar {
  using type = T;
};

template <typename T, int N>
struct UnderlyingScalar<Jet<T, N>> : UnderlyingScalar<T> {};

// Convenience template alias for UnderlyingScalar type trait.
template <typename T>
using UnderlyingScalar_t = typename UnderlyingScalar<T>::type;

// Predicate determining whether all Types in the pack are the same.
//
// Specifically, the predicate applies std::is_same recursively to pairs of
// Types in the pack.
template <typename T1, typename... Types>
inline constexpr bool AreAllSame_v = (std::is_same<T1, Types>::value && ...);

// Determines the rank of a type. This allows to ensure that types passed as
// arguments are compatible to each other. The rank of Jet is determined by the
// dimensions of the dual part. The rank of a scalar is always 0.
// Non-specialized types default to a rank of -1.
template <typename T, typename E = void>
struct Rank : std::integral_constant<int, -1> {};

// The rank of a scalar is 0.
template <typename T>
struct Rank<T, std::enable_if_t<std::is_scalar<T>::value>>
    : std::integral_constant<int, 0> {};

// The rank of a Jet is given by its dimensionality.
template <typename T, int N>
struct Rank<Jet<T, N>> : std::integral_constant<int, N> {};

// Convenience variable template for Rank.
template <typename T>
inline constexpr int Rank_v = Rank<T>::value;

// Constructs an integer sequence of ranks for each of the Types in the pack.
template <typename... Types>
using Ranks_t = std::integer_sequence<int, Rank_v<Types>...>;

// Returns the scalar part of a type. This overload acts as an identity.
template <typename T>
constexpr decltype(auto) AsScalar(T&& value) noexcept {
  return std::forward<T>(value);
}

// Recursively unwraps the scalar part of a Jet until a non-Jet scalar type is
// encountered.
template <typename T, int N>
constexpr decltype(auto) AsScalar(const Jet<T, N>& value) noexcept(
    noexcept(AsScalar(value.a))) {
  return AsScalar(value.a);
}

}  // namespace internal

// Type trait ensuring at least one of the types is a Jet,
// the underlying scalar types are the same and Jet dimensions match.
//
// The type trait can be further specialized if necessary.
//
// This trait is a candidate for a concept definition once C++20 features can
// be used.
template <typename... Types>
// clang-format off
struct CompatibleJetOperands : std::integral_constant
<
    bool,
    // At least one of the types is a Jet
    internal::AreAnyJet_v<Types...> &&
    // The underlying floating-point types are exactly the same
    internal::AreAllSame_v<internal::UnderlyingScalar_t<Types>...> &&
    // Non-zero ranks of types are equal
    internal::IsEmptyOrAreAllEqual_v<internal::RemoveValue_t<internal::Ranks_t<Types...>, 0>>
>
// clang-format on
{};

// Single Jet operand is always compatible.
template <typename T, int N>
struct CompatibleJetOperands<Jet<T, N>> : std::true_type {};

// Single non-Jet operand is always incompatible.
template <typename T>
struct CompatibleJetOperands<T> : std::false_type {};

// Empty operands are always incompatible.
template <>
struct CompatibleJetOperands<> : std::false_type {};

// Convenience variable template ensuring at least one of the types is a Jet,
// the underlying scalar types are the same and Jet dimensions match.
//
// This trait is a candidate for a concept definition once C++20 features can
// be used.
template <typename... Types>
inline constexpr bool CompatibleJetOperands_v =
    CompatibleJetOperands<Types...>::value;

// Type trait ensuring at least one of the types is a Jet,
// the underlying scalar types are compatible among each other and Jet
// dimensions match.
//
// The type trait can be further specialized if necessary.
//
// This trait is a candidate for a concept definition once C++20 features can
// be used.
template <typename... Types>
// clang-format off
struct PromotableJetOperands : std::integral_constant
<
    bool,
    // Types can be compatible among each other
    internal::AreAnyJet_v<Types...> &&
    // Non-zero ranks of types are equal
    internal::IsEmptyOrAreAllEqual_v<internal::RemoveValue_t<internal::Ranks_t<Types...>, 0>>
>
// clang-format on
{};

// Convenience variable template ensuring at least one of the types is a Jet,
// the underlying scalar types are compatible among each other and Jet
// dimensions match.
//
// This trait is a candidate for a concept definition once C++20 features can
// be used.
template <typename... Types>
inline constexpr bool PromotableJetOperands_v =
    PromotableJetOperands<Types...>::value;

}  // namespace ceres

#endif  // CERES_PUBLIC_INTERNAL_JET_TRAITS_H_
