//===-- rosa/support/csv/CSVWriter.hpp --------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/support/csv/CSVWriter.hpp
///
/// \authors David Juhasz    (david.juhasz@tuwien.ac.at)
///          Edwin Willegger (edwin.willegger@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \brief Facitilities to write CSV files.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_SUPPORT_CSV_CSVWRITER_HPP
#define ROSA_SUPPORT_CSV_CSVWRITER_HPP

#include <iostream>
#include <ostream>

#include <tuple>
#include <vector>
#include <array>

#include "rosa/support/log.h"

namespace rosa {
namespace csv {

/// Provides facilities to write values into a CSV file.
///
/// The writer emits a comma, the character `,`, between each written values.
/// The resulted stream is a flat CSV file as it consists of onlyone row, no new
/// line is emitted.
///
/// \tparam T type of values to write
template <typename T>
class CSVWriter {
public:
  /// Creates a new instance.
  ///
  /// \param [in,out] S output stream to write to
  ///
  /// \note The writer operates on non-binary outputs as long as \p S is in
  /// good state.
  CSVWriter(std::ostream &S)
      : Str(S.good() && !(S.flags() & std::ios::binary) ? &S : nullptr),
        IsFirst(true) {}

  /// Tells if the last operation was successful.
  ///
  /// \return if the last operation was successful
  bool good(void) const noexcept {
    return Str != nullptr;
  }

  /// Writes an entry to the output stream.
  ///
  /// The implementation does anything only if the last operation was
  /// successful. If so, \p V is written to \c rosa::csv::CSVWriter::Str.
  /// The emitted value is preceded with a comma if the actual call is not the
  /// first one for \p this object. Success of the operation is checked at the
  /// end.
  ///
  /// \param V value to write
  void write(const T &V) {
    if (Str) {
      if (!IsFirst) {
        *Str << ',';
      } else {
        IsFirst = false;
      }
      *Str << V;
      if (!Str->good()) {
        Str = nullptr;
      }
    }
  }

private:
  std::ostream *Str; ///< Output stream to write to.
  bool IsFirst;      ///< Denotes if the next write would be the first one.
};

/// Writes a tuple of values into a CSV file
///
/// \tparam Ts types of values to write
template <typename... Ts> class CSVTupleWriter {

public:

//    typedef <Ts...> value_type ;  ///< Type of values written.
    typedef std::tuple<Ts...> value_type;

    /// Creates a new instance.
    ///
    /// \param [in,out] S output stream to write to
    ///
    /// \note The writer operates on non-binary outputs as long as \p S is in
    /// good state.
    CSVTupleWriter(std::ostream &S)
        : Str(S.good() && !(S.flags() & std::ios::binary) ? &S : nullptr), 
          IsHeaderWritten(false), IsDataWritten(false) {}

    /// Tells if the last operation was successful.
    ///
    /// \return if the last operation was successful
    bool good(void) const noexcept {
      return Str != nullptr;
    }


    /// Write the values of a tuple to a CSV file with \c rosa::csv::CSVTupleWriter.
    ///
    /// \see rosa::csv::CSVTupleWriter
    ///
    /// 
    /// \param [in,out] values tuple, which values are written in a recusive fashion into a stream.
    template<size_t i = 0>
    void write(const std::tuple<Ts...> &values) {
        constexpr size_t size = sizeof...(Ts);

        LOG_TRACE_STREAM << "Writing tuple values into file \n"; 
        LOG_TRACE_STREAM << " Tuple has " << std::to_string(size) << " elements. \n";

        LOG_TRACE_STREAM << " Value is " << std::get<i>(values);

        if(Str){
            /// Write the current element of the tuple into the stream and add a separtor after it, 
            /// and call the function for the next element in the tuple.
            if constexpr(i+1 != sizeof...(Ts)){
                *Str << std::get<i>(values) << ", ";
                write<i+1>(values);
            /// If the last element is written into the stream than begin a new line. 
            }else if constexpr(i + 1 == sizeof...(Ts)){
                *Str << std::get<i>(values) << '\n';
                /// every time the last data value of a line is written, the flag indicates that data was already written into the file.
                IsDataWritten = true; 
            }
        }
    }

    /// Write the header values to a CSV file with \c rosa::csv::CSVTupleWriter.
    ///
    /// \note The function has no effect if anything has already been written
    /// to the output stream either by \c
    /// rosa::csv::CSVTupleWriter::writeHeader() or \c
    /// rosa::csv::CSVTupleWriter::write().
    ///
    /// \see rosa::csv::CSVTupleWriter
    ///
    /// \param header the content of the header line.
    void writeHeader(const std::array<std::string, sizeof...(Ts)> &header){
        size_t index = 0;
        /// write into the stream only, if it is not a nullptr, and if no data and no header was already written into it. 
        if(Str && IsDataWritten == false && IsHeaderWritten == false){
            index = 0;
            for (auto i = header.begin(); i != header.end(); ++i){
                index = index + 1;
                /// write into the stream every entry with a delimiter, in this case ", " until 
                /// the last entry
                if(index != header.size()){
                    *Str << *i << ", ";
                /// write the last entry into the stream, without any delimiter
                }else {
                    *Str << *i;
                }
            }
            /// finish the header line and start a new line.
            *Str << '\n';
            /// now it is not possible to write additional header lines. 
            IsHeaderWritten = true;
        }
    }

private:
    std::ostream *Str;              ///< Output stream to write to.
    bool          IsHeaderWritten;  ///< If an header line was already written into the stream. If set than no additional header could be written.
    bool          IsDataWritten;    ///< If one line of data has already been written into the stream, than no headerline could be added. 
};

/// Writes all values of a tuple to a CSV file with \c rosa::csv::CSVTupleWriter.
///
/// \see rosa::csv::CSVTupleWriter
///
/// \tparam Ts types of values to write
///
/// \param [in,out] W object to write with
/// \param V values to write
///
/// \return \p W after writing \p V with it
template <typename... Ts>
CSVTupleWriter<Ts...> &operator<<(CSVTupleWriter<Ts...> &W, const std::tuple<Ts...> &V)
{
    W.write(V);
    return W;
}

/// Writes a value to a CSV file with \c rosa::csv::CSVWriter.
///
/// \see rosa::csv::CSVWriter
///
/// \tparam T type of value to write
///
/// \param [in,out] W object to write with
/// \param V value to write
///
/// \return \p W after writing \p V with it
template <typename T>
CSVWriter<T> &operator<<(CSVWriter<T> &W, const T& V) {
  W.write(V);
  return W;
}

} // End namespace csv
} // End namespace rosa

#endif // ROSA_SUPPORT_CSV_CSVWRITER_HPP
