//===-- rosa/support/writer/split_tuple_writer.hpp ---------------*- C++ -*-===/
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===/
///
/// \file rosa/support/writer/split_tuple_writer.hpp
///
/// \authors David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Facilities to split writers of \c std::tuple into writers of
/// their elements.
///
//===----------------------------------------------------------------------===/

#ifndef ROSA_SUPPORT_WRITER_SPLIT_TUPLE_WRITER_HPP
#define ROSA_SUPPORT_WRITER_SPLIT_TUPLE_WRITER_HPP

#include "rosa/support/debug.hpp"
#include "rosa/support/log.h"
#include "rosa/support/sequence.hpp"

#include <memory>
#include <queue>
#include <tuple>

namespace rosa {
namespace writer {

/// What to do when splitted element writers got destructed so that some
/// incomplete tuples remain in the buffer.
enum IncompleteTuplePolicy {
  Ignore, ///< ignore incomplete tuples, data discarded
  Flush,  ///< flush incomplete tuples, use default value for missing elements
#ifndef NDEBUG
  DbgFail ///< fail with an assertion, available only in debug build
#endif    // !defined NDEBUG
};

/// Anonymous namespace providing implementation for splitting tuple writers;
/// consider it private.
namespace {

/// Writes values into a buffer based on an index.
///
/// The values are first buffered and flushed out later once a corresponding
/// tuple gets complete (i.e., other elementwise writers put the corresponding
/// values into the buffer). Since the underlying buffer waits for tuples to
/// become complete, the elementwise writers are expected to provide the same
/// number of values for the buffer (i.e., completing all tuples that have an
/// element). That is, however, not enforced in any ways.
///
/// Once all elementwise writers are destructed, the underlying buffer gets
/// destructed as well. Should the buffer contain some values, which are
/// elements of incomplete tuples, the destructor of the buffer handles the
/// situation according to the \c rosa::writer::IncompleteTuplePolicy set when
/// the elementwise writers were created by \c rosa::writer::splitTupleWriter().
///
/// \tparam TB buffer to write into
/// \tparam I index of the values to write to \p TB
///
/// \note \p TB is expected to implemnet an interface matching that of \c
/// TupleWriterBuffer.
///
/// \note the
template <typename TB, size_t I> class SplittedElementWriter {
public:
  /// Type alias for the values the writer writes.
  using T = typename TB::template element_type<I>;

  /// \defgroup SplittedElemenWriterTypedefs Typedefs of
  /// \c SplittedElementWriter
  ///
  /// Useful `typedef`s for writers.
  ///
  ///@{
  typedef T value_type; ///< Type of values written.
  typedef T &reference; ///< Reference to the type written
  ///@}

  /// Creates a new instance.
  ///
  /// \param Buffer buffer \p this object writes values into
  ///
  /// \note \p Buffer is captured as a \c std::shared_ptr because it is shared
  /// among writers for its element positions.
  SplittedElementWriter(std::shared_ptr<TB> Buffer) : Buffer(Buffer) {}

  /// Tells how many values are still in the buffer.
  ///
  /// Values are buffered as long as all elements of their corresponding tuples
  /// are written into the buffer by other elementwise writers and in turn
  /// complete tuples written by the buffer itself. This function tells how many
  /// values are still in the buffer waiting for their containing tuples to be
  /// complete and written out from the buffer.
  ///
  /// \note The function simply calles the corresponding function of \p TB.
  ///
  /// \return how many values are still in the buffer.
  size_t buffered(void) const noexcept {
    return Buffer->template buffered<I>();
  }

  /// Tells if the last write operation of the buffer was successful.
  ///
  /// Values are buffered until their corresponding tuples gets complete. The
  /// buffer writes complete tuples by the underlying writer. This function
  /// tells if the buffer successfully has written the latest complete tuple by
  /// the underlying writer.
  ///
  /// Once this function returns \c false, further \c write() calls has no
  /// effect and the last \c buffered() values will not be written to the
  /// underlying writer of \c std::tuple.
  ///
  /// \note The function simply calles the corresponding function of \p TB.
  ///
  /// \return if the last write operation was successful
  bool good(void) const noexcept { return Buffer->good(); }

  /// Writes an entry to the buffer.
  ///
  /// The function has no effect if an earlier write operation of the buffer has
  /// failed, that is \c good() returns \c false.
  ///
  /// \note The function simply calles the corresponding function of \p TB.
  ///
  /// \param V value to write
  void write(const T &V) { Buffer->template write<I>(V); }

private:
  std::shared_ptr<TB> Buffer; ///< Buffer \p this object writes into
};

/// Writes an element of a tuple.
///
/// \see SplittedElementWriter
///
/// \tparam TB type of the buffer \p W writes into
/// \tparam I index of the values \p W write to \p TB
///
/// \param [in,out] W object to write with
/// \param V value to write
///
/// \return \p W after writing \p V with it
template <typename TB, size_t I>
SplittedElementWriter<TB, I> &
operator<<(SplittedElementWriter<TB, I> &W,
           const typename SplittedElementWriter<TB, I>::value_type &V) {
  W.write(V);
  return W;
}

///\defgroup TupleBufferContainer Type converter turning a \c std::tuple of
/// types into a \c std::tuple of container of those types.
///
/// The new type is used for buffering elements of tuples separately.
///
///@{

/// Template declaration.
///
/// \tparam T type to convert
///
/// \note The template is defined only when \p T is a \c std::tuple.
///
/// Usage for a type \c Tuple as:\code
/// typename TupleBufferContainer<Tuple>::Type
/// \endcode
template <typename T> struct TupleBufferContainer;

/// Template definition for \c std::tuple.
template <typename... Ts> struct TupleBufferContainer<std::tuple<Ts...>> {
  /// The converted type.
  using Type = std::tuple<std::queue<Ts>...>;
};

///@}

/// Convenience template type alias for easy use of \c TupleBufferContainer.
///
/// Converts a \c std::tuple of types into a \c std::tuple of container of those
/// types.
///
/// \tparam Tuple type to convert
template <typename Tuple>
using tuple_buffer_container_t = typename TupleBufferContainer<Tuple>::Type;

/// Buffer for element values for writer of \c std::tuple.
///
/// The class can be instantiated with a writer of \c std::tuple and provides
/// an interface for writing elements into tuples one by one.
///
/// \note The class is utilized by \c SplittedElementWriter.
///
/// \tparam TupleWriter the writer type that handles values of \c std::tuple
///
/// \note The elements of the written \c std::tuple need to be default
/// constructible because of \c rosa::writer::IncompleteTuplePolicy::Flush.
///
/// \todo Consider thread safety of the class.
template <typename TupleWriter> class TupleWriterBuffer {
public:
  /// Type alias for the value type of \p TupleWriter.
  /// \note The type is expected to be \c std::tuple.
  using writer_value_type = typename TupleWriter::value_type;

  /// The number of elements of \c writer_value_type.
  static constexpr size_t writer_value_size =
      std::tuple_size_v<writer_value_type>;

  /// Template type alias to get element types of \c writer_value_type by
  /// index.
  /// \tparam I the index of the element
  template <size_t I>
  using element_type = typename std::tuple_element<I, writer_value_type>::type;

  /// Type alias for index sequence for accessing elements of \c
  /// writer_value_size.
  using element_idx_seq_t = seq_t<writer_value_size>;

  /// Creates a new instance.
  ///
  /// \param Writer the writer \p this object uses to write tuples
  /// \param Policy what to do with incomplete tuples when destructing \p
  /// this object
  TupleWriterBuffer(TupleWriter &&Writer,
                    const IncompleteTuplePolicy Policy) noexcept
      : Writer(Writer), Policy(Policy), Buffer() {}

  /// Destructor.
  ///
  /// Should \p this object has some values in \c Buffer (i.e., incomplete
  /// tuples), the destructor takes care of them according to \c Policy.
  ///
  /// \note The destructor may flush the buffered values if the last write
  /// operation was successful, that is \c good() returns \c true.
  ~TupleWriterBuffer(void) noexcept {
    if (good()) {
      switch (Policy) {
      default:
        LOG_ERROR("Unexpected Policy");
      case Ignore:
        // Do not care about incomplete tuples in the buffer.
        break;
      case Flush:
        // Whatever we have in the buffer, flush it out.
        flush();
        break;
#ifndef NDEBUG
      case DbgFail:
        ASSERT(empty(element_idx_seq_t()) && "Non-empty buffer");
        break;
#endif // !defined NDEBUG
      }
    }
  }

  /// Tells how many values are in the buffer for index \p I.
  ///
  /// \tparam I the index of the element to check
  ///
  /// \return the number of values in buffer for index \p I
  template <size_t I> size_t buffered(void) const noexcept {
    return std::get<I>(Buffer).size();
  }

  /// Tells if the last write operation was successful.
  ///
  /// Values are buffered until their corresponding tuples gets complete.
  /// Once a tuple becomes complete, it is written by \c Writer. This function
  /// tells if writing the latest complete tuple was successful.
  ///
  /// Once this function returns \c false, further \c write<I>() calls has no
  /// effect and the last \c buffered<I>() values will not be written to the
  /// underlying writer of \c std::tuple.
  ///
  /// \return if the last write operation was successful
  bool good(void) const noexcept { return Writer.good(); }

  /// Writes an entry to the buffer for index \p I.
  ///
  /// The function has no effect if an earlier write operation of the buffer has
  /// failed, that is \c good() returns \c false.
  ///
  /// The value is buffered first and written by \p Writer once its
  /// corresponding tuple becomes complete (i.e., all other elements of the
  /// tuple are written into \p this object by corresponding elementwise
  /// writers).
  ///
  /// \tparam I the index of the element to write
  ///
  /// \param V value to write
  template <size_t I> void write(const element_type<I> &V) {
    ASSERT(!hasCompleteTuple(element_idx_seq_t()));
    if (good()) {
      std::get<I>(Buffer).push(V);
      if (hasCompleteTuple(element_idx_seq_t())) {
        writeTuple(false);
      }
    }
    ASSERT(!hasCompleteTuple(element_idx_seq_t()));
  }

private:
  /// Tells whether \c Buffer is completely empty, all elements of it are empty.
  ///
  /// \tparam S0 indices for accessing elements of \c std::tuple
  ///
  /// \note The parameter provides indices as template parameter \p S0 and its
  /// actual value is ignored.
  ///
  /// \return whether all elements of \c Buffer are empty
  template <size_t... S0> bool empty(Seq<S0...>) const noexcept {
    return (true && ... && std::get<S0>(Buffer).empty());
  }

  /// Tells whether \c Buffer contains at least one complete tuple.
  ///
  /// \tparam S0 indices for accessing elements of \c std::tuple
  ///
  /// \note The parameter provides indices as template parameter \p S0 and its
  /// actual value is ignored.
  ///
  /// \return whether there is at least one complete tuple in \c Buffer
  template <size_t... S0> bool hasCompleteTuple(Seq<S0...>) const noexcept {
    return (true && ... && !std::get<S0>(Buffer).empty());
  }

  /// Makes sure \c Buffer has one complete tuple in front.
  ///
  /// If the tuple in front of \c Buffer is not complete, missing elements are
  /// default constructed in \c Buffer to complete the tuple.
  ///
  /// \tparam S0 indices for accessing elements of \c std::tuple
  ///
  /// \note The parameter provides indices as template parameter \p S0 and its
  /// actual value is ignored.
  template <size_t... S0> void makeCompleteTuple(Seq<S0...>) noexcept {
    auto addDefaultIfEmpty = [](auto &Queue) {
      if (Queue.empty()) {
        Queue.emplace(); // default construct a value in the empty queue.
      }
    };
    (addDefaultIfEmpty(std::get<S0>(Buffer)), ...);
  }

  /// Gives the first complete tuple from \c Buffer.
  ///
  /// \tparam S0 indices for accessing elements of \c std::tuple
  ///
  /// \note The parameter provides indices as template parameter \p S0 and its
  /// actual value is ignored.
  ///
  /// \return the first complete tuple from \c Buffer
  ///
  /// \pre There is at least one complete tuple in \c Buffer:\code
  /// hasCompleteTuple(element_idx_seq_t())
  /// \endcode
  template <size_t... S0> writer_value_type front(Seq<S0...>) const noexcept {
    ASSERT(hasCompleteTuple(element_idx_seq_t()));
    return {std::get<S0>(Buffer).front()...};
  }

  /// Removes the first complete tuple from \c Buffer.
  ///
  /// \tparam S0 indices for accessing elements of \c std::tuple
  ///
  /// \note The parameter provides indices as template parameter \p S0 and its
  /// actual value is ignored.
  ///
  /// \pre There is at least one complete tuple in \c Buffer:\code
  /// hasCompleteTuple(element_idx_seq_t())
  /// \endcode
  template <size_t... S0> void pop(Seq<S0...>) noexcept {
    ASSERT(hasCompleteTuple(element_idx_seq_t()));
    (std::get<S0>(Buffer).pop(), ...);
  }

  /// Takes the first tuple from \c Buffer and writes it with \c Writer.
  ///
  /// \c Buffer need to contain a complete tuple to write it with \c Writer. The
  /// function makes sure that the tuple in front of \c Buffer is complete if \p
  /// MakeComplete is true. The tuple in fron of \c Buffer are expected to be
  /// complete otherwise.
  ///
  /// \param MakeComplete whether to make the first tuple complete
  ///
  /// \pre There is at least one complete tuple in \c Buffer if not \p
  /// MakeComplete: \code
  /// MakeComplete || hasCompleteTuple(element_idx_seq_t())
  /// \endcode
  void writeTuple(const bool MakeComplete) noexcept {
    if (MakeComplete) {
      makeCompleteTuple(element_idx_seq_t());
    }
    ASSERT(hasCompleteTuple(element_idx_seq_t()));
    Writer.write(front(element_idx_seq_t()));
    pop(element_idx_seq_t());
  }

  /// Empties \c Buffer by writing out all tuples from it so that missing
  /// elements of incomplete tuples are extended with default constructed
  /// values.
  void flush(void) noexcept {
    while (!empty(element_idx_seq_t())) {
      ASSERT(!hasCompleteTuple(element_idx_seq_t())); // Sanity check.
      writeTuple(true);
    }
  }

  TupleWriter Writer;                 ///< The splitted writer
  const IncompleteTuplePolicy Policy; ///< what to do with incomplete tuples
                                      ///< when destructing \p this object
  tuple_buffer_container_t<writer_value_type>
      Buffer; ///< Container for elementwise buffering
};

/// Template type alias for writer of an element based on buffered writer of
/// \c std::tuple.
///
/// The alias utilizes \c SplittedElementWriter for writing element values by
/// \p TB.
///
/// \tparam TB buffer to write into
/// \tparam I index of the values to write to \p TB
template <typename TB, size_t I>
using element_writer_t = SplittedElementWriter<TB, I>;

///\defgroup ElementWriters Type converter turning a buffer of \c std::tuple
/// into a \c std::tuple of corresponding \c element_writer_t.
///
///@{

/// Template declaration.
///
/// \tparam TB buffer to write into
/// \tparam S type providing indices for accessing elements of \c std::tuple
///
/// \note \p TB is expected to implement an interface matching that of \c
/// TupleWriterBuffer.
///
/// \note The template is defined only when \p S is a \c rosa::Seq.
///
/// Usage for a proper buffer type \c TB:\code
/// typename ElementIteratorWriters<TB, typename TB::element_idx_seq_t>::Type
/// \endcode
template <typename TB, typename S> struct ElementWriters;

/// Template definition.
template <typename TB, size_t... S0> struct ElementWriters<TB, Seq<S0...>> {
  /// The converted type.
  using Type = std::tuple<element_writer_t<TB, S0>...>;
};

///@}

/// Convenience template type alias for easy use of \c ElementIteratorWriters.
///
/// Converts a buffer of \c std::tuple into a \c std::tuple of corresponding \c
/// element_writer_t.
///
/// \tparam TB buffer to write into
///
/// \note \p TB is expected to implement an interface matching that of \c
/// TupleWriterBuffer.
template <typename TB>
using element_writers_t =
    typename ElementWriters<TB, typename TB::element_idx_seq_t>::Type;

/// Template type alias for turning a writer of \c std::tuple into
/// corresponding \c element_writers_t.
///
/// The alias utilizes \c TupleWriterBuffer for buffering values before writing
/// them by \p TupleWriter. The elementwise writers are \c
/// SplittedElementWriter.
///
/// \tparam TupleWriter writer of \c std::tuple
template <typename TupleWriter>
using splitted_tuple_writers_t =
    element_writers_t<TupleWriterBuffer<TupleWriter>>;

/// Creates elementwise writers for a writer of \c std::tuple.
///
/// \note Implementation for \c rosa::iterator::splitTupleWriter.
///
/// \tparam TupleWriter writer of \c std::tuple
/// \tparam S0 indices for accessing elements of \c std::tuple
///
/// \param Writer the writer to write tuples with
/// \param Policy how to handle incomplete tuples when destructing the
/// underlying buffer
///
/// \note The last parameter provides indices as template parameter \p S0 and
/// its actual value is ignored.
///
/// \return \c std::tuple of elementwise writers corresponding to \p Writer
template <typename TupleWriter, size_t... S0>
splitted_tuple_writers_t<TupleWriter>
splitTupleWriterImpl(TupleWriter &&Writer, const IncompleteTuplePolicy Policy,
                     Seq<S0...>) noexcept {
  using TB = TupleWriterBuffer<TupleWriter>;
  // Create a buffer in shared pointer. The buffer will be destructed once
  // all corresponding element writers (created below) have been destructed.
  auto Buffer = std::make_shared<TB>(std::move(Writer), Policy);
  return {element_writer_t<TB, S0>(Buffer)...};
}

} // End namespace

/// Creates elementwise writers for a writer of \c std::tuple.
///
/// \note The implementation utilizes \c splitTupleWriterImpl.
///
/// Obtain elementwise writers for a writer \p Writer of type \c TupleWriter of
/// \c std::tuple<T1, T2, T3> as \code
/// auto [T1Writer, T2Writer, T3Writer] = splitTupleWriter(std::move(Writer),
///                                                        Ignore);
/// \endcode
///
/// The function returns a tuple of writers, which can be assigned to variables
/// using structured binding.
///
/// The function moves its first argument (i.e., \p Writer cannot be used
/// directly after splitting it). If the first argument is a variable, it needs
/// to be moved explicitly by \c std::move() as in the example.
///
/// While a tuple could be written with \p Writer directly as \code
/// Writer << std::make_tuple(T1(), T2(), T3());
/// \endcode The same tuple is written with splitted writers as \code
/// T1Writer << T1();
/// T2Writer << T2();
/// T3Writer << T3();
/// \endcode
///
/// \tparam TupleWriter writer of \c std::tuple
///
/// \note The elements of the written \c std::tuple need to be default
/// constructible because of \c rosa::writer::IncompleteTuplePolicy::Flush.
///
/// \param Writer the writer to write tuples with
/// \param Policy what to do with incomplete tuples when destructing the
/// underlying buffer
///
/// \return \c std::tuple of elementwise writers corresponding to \p Writer
template <typename TupleWriter>
splitted_tuple_writers_t<TupleWriter>
splitTupleWriter(TupleWriter &&Writer,
                 const IncompleteTuplePolicy Policy) noexcept {
  return splitTupleWriterImpl(
      std::move(Writer), Policy,
      seq_t<std::tuple_size_v<typename TupleWriter::value_type>>());
}

} // End namespace writer
} // End namespace rosa

#endif // ROSA_SUPPORT_WRITER_SPLIT_TUPLE_WRITER_HPP
