// 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: jodebo_beck@gmx.de (Johannes Beck)
//         sergiu.deitsch@gmail.com (Sergiu Deitsch)
//
// Algorithms to be used together with integer_sequence, like computing the sum
// or the exclusive scan (sometimes called exclusive prefix sum) at compile
// time.

#ifndef CERES_PUBLIC_INTERNAL_INTEGER_SEQUENCE_ALGORITHM_H_
#define CERES_PUBLIC_INTERNAL_INTEGER_SEQUENCE_ALGORITHM_H_

#include <utility>

#include "tinyceres/jet_fwd.hpp"

namespace ceres::internal {

// Implementation of calculating an exclusive scan (exclusive prefix sum) of an
// integer sequence. Exclusive means that the i-th input element is not included
// in the i-th sum. Calculating the exclusive scan for an input array I results
// in the following output R:
//
// R[0] = 0
// R[1] = I[0];
// R[2] = I[0] + I[1];
// R[3] = I[0] + I[1] + I[2];
// ...
//
// In C++17 std::exclusive_scan does the same operation at runtime (but
// cannot be used to calculate the prefix sum at compile time). See
// https://en.cppreference.com/w/cpp/algorithm/exclusive_scan for a more
// detailed description.
//
// Example for integer_sequence<int, 1, 4, 3> (seq := integer_sequence):
//                   T  , Sum,          Ns...   ,          Rs...
// ExclusiveScanImpl<int,   0, seq<int, 1, 4, 3>, seq<int         >>
// ExclusiveScanImpl<int,   1, seq<int,    4, 3>, seq<int, 0      >>
// ExclusiveScanImpl<int,   5, seq<int,       3>, seq<int, 0, 1   >>
// ExclusiveScanImpl<int,   8, seq<int         >, seq<int, 0, 1, 5>>
//                                                ^^^^^^^^^^^^^^^^^
//                                                resulting sequence
template <typename T, T Sum, typename SeqIn, typename SeqOut>
struct ExclusiveScanImpl;

template <typename T, T Sum, T N, T... Ns, T... Rs>
struct ExclusiveScanImpl<T,
                         Sum,
                         std::integer_sequence<T, N, Ns...>,
                         std::integer_sequence<T, Rs...>> {
  using Type =
      typename ExclusiveScanImpl<T,
                                 Sum + N,
                                 std::integer_sequence<T, Ns...>,
                                 std::integer_sequence<T, Rs..., Sum>>::Type;
};

// End of 'recursion'. The resulting type is SeqOut.
template <typename T, T Sum, typename SeqOut>
struct ExclusiveScanImpl<T, Sum, std::integer_sequence<T>, SeqOut> {
  using Type = SeqOut;
};

// Calculates the exclusive scan of the specified integer sequence. The last
// element (the total) is not included in the resulting sequence so they have
// same length. This means the exclusive scan of integer_sequence<int, 1, 2, 3>
// will be integer_sequence<int, 0, 1, 3>.
template <typename Seq>
class ExclusiveScanT {
  using T = typename Seq::value_type;

 public:
  using Type =
      typename ExclusiveScanImpl<T, T(0), Seq, std::integer_sequence<T>>::Type;
};

// Helper to use exclusive scan without typename.
template <typename Seq>
using ExclusiveScan = typename ExclusiveScanT<Seq>::Type;

// Removes all elements from a integer sequence corresponding to specified
// ValueToRemove.
//
// This type should not be used directly but instead RemoveValue.
template <typename T, T ValueToRemove, typename... Sequence>
struct RemoveValueImpl;

// Final filtered sequence
template <typename T, T ValueToRemove, T... Values>
struct RemoveValueImpl<T,
                       ValueToRemove,
                       std::integer_sequence<T, Values...>,
                       std::integer_sequence<T>> {
  using type = std::integer_sequence<T, Values...>;
};

// Found a matching value
template <typename T, T ValueToRemove, T... Head, T... Tail>
struct RemoveValueImpl<T,
                       ValueToRemove,
                       std::integer_sequence<T, Head...>,
                       std::integer_sequence<T, ValueToRemove, Tail...>>
    : RemoveValueImpl<T,
                      ValueToRemove,
                      std::integer_sequence<T, Head...>,
                      std::integer_sequence<T, Tail...>> {};

// Move one element from the tail to the head
template <typename T, T ValueToRemove, T... Head, T MiddleValue, T... Tail>
struct RemoveValueImpl<T,
                       ValueToRemove,
                       std::integer_sequence<T, Head...>,
                       std::integer_sequence<T, MiddleValue, Tail...>>
    : RemoveValueImpl<T,
                      ValueToRemove,
                      std::integer_sequence<T, Head..., MiddleValue>,
                      std::integer_sequence<T, Tail...>> {};

// Start recursion by splitting the integer sequence into two separate ones
template <typename T, T ValueToRemove, T... Tail>
struct RemoveValueImpl<T, ValueToRemove, std::integer_sequence<T, Tail...>>
    : RemoveValueImpl<T,
                      ValueToRemove,
                      std::integer_sequence<T>,
                      std::integer_sequence<T, Tail...>> {};

// RemoveValue takes an integer Sequence of arbitrary type and removes all
// elements matching ValueToRemove.
//
// In contrast to RemoveValueImpl, this implementation deduces the value type
// eliminating the need to specify it explicitly.
//
// As an example, RemoveValue<std::integer_sequence<int, 1, 2, 3>, 4>::type will
// not transform the type of the original sequence. However,
// RemoveValue<std::integer_sequence<int, 0, 0, 2>, 2>::type will generate a new
// sequence of type std::integer_sequence<int, 0, 0> by removing the value 2.
template <typename Sequence, typename Sequence::value_type ValueToRemove>
struct RemoveValue
    : RemoveValueImpl<typename Sequence::value_type, ValueToRemove, Sequence> {
};

// Convenience template alias for RemoveValue.
template <typename Sequence, typename Sequence::value_type ValueToRemove>
using RemoveValue_t = typename RemoveValue<Sequence, ValueToRemove>::type;

// Returns true if all elements of Values are equal to HeadValue.
//
// Returns true if Values is empty.
template <typename T, T HeadValue, T... Values>
inline constexpr bool AreAllEqual_v = ((HeadValue == Values) && ...);

// Predicate determining whether an integer sequence is either empty or all
// values are equal.
template <typename Sequence>
struct IsEmptyOrAreAllEqual;

// Empty case.
template <typename T>
struct IsEmptyOrAreAllEqual<std::integer_sequence<T>> : std::true_type {};

// General case for sequences containing at least one value.
template <typename T, T HeadValue, T... Values>
struct IsEmptyOrAreAllEqual<std::integer_sequence<T, HeadValue, Values...>>
    : std::integral_constant<bool, AreAllEqual_v<T, HeadValue, Values...>> {};

// Convenience variable template for IsEmptyOrAreAllEqual.
template <class Sequence>
inline constexpr bool IsEmptyOrAreAllEqual_v =
    IsEmptyOrAreAllEqual<Sequence>::value;

}  // namespace ceres::internal

#endif  // CERES_PUBLIC_INTERNAL_INTEGER_SEQUENCE_ALGORITHM_H_
