// ****************************************************************************
//
//          Aevol - An in silico experimental evolution platform
//
// ****************************************************************************
//
// Copyright: See the AUTHORS file provided with the package or <www.aevol.fr>
// Web: http://www.aevol.fr/
// E-mail: See <http://www.aevol.fr/contact/>
// Original Authors : Guillaume Beslon, Carole Knibbe, David Parsons
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// ****************************************************************************

#ifndef AEVOL_UTILITY_H_
#define AEVOL_UTILITY_H_

#include <optional>
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <utility>

#include "checkpointing/input_format_error.h"

#ifndef __cpp_lib_to_underlying
namespace std {

template<class Enum>
constexpr underlying_type_t<Enum> to_underlying(Enum e) noexcept {
  return static_cast<underlying_type_t<Enum>>(e);
}

}
#endif

#ifndef HAS_FROMCHARS_DOUBLE
namespace std {
// Minimal and optimistic version of from_chars for doubles
inline auto from_chars(const char* first, const char*, double& value) {
  char* str_end;
  value = std::strtod(first, &str_end);
  return std::make_tuple(const_cast<const char*>(str_end), std::errc{});
}
}
#endif

namespace aevol {

// Pretty-print a tuple (code adapted from https://en.cppreference.com/w/cpp/utility/apply)
template <class Ch, class Tr, class... Ts>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, const std::tuple<Ts...>& tuple) {
  std::apply(
      [&os](Ts const&... tupleArgs) {
        std::size_t n{0};
        ((os << tupleArgs << (++n != sizeof...(Ts) ? "," : "")), ...);
      }, tuple
  );
  return os;
}

// Stream insertion operator overload for optionals
template <class T>
auto& operator<<(std::ostream& os, const std::optional<T>& ov) {
  if (ov) {
    os << "ON " << ov.value();
  } else {
    os << "OFF";
  }
  return os;
}

// Stream extraction operator overload for optionals
template <class T>
std::istream& operator>>(std::istream& is, std::optional<T>& ov) {
  auto str = std::string {};
  is >> str;
  if (str == "ON") {
    auto v = T{};
    is >> v;
    ov = v;
  } else {
    ov = std::nullopt;
  }
  return is;
}

template<typename InputStream, typename... Ts>
void get_expected_or_throw(InputStream& is, const char* keyword, Ts& ... args) {
  std::string str;
  is >> str;
  if (str != keyword) {
    throw input_format_error(keyword, str);
  }
  if constexpr (sizeof...(args)) {
    (is >> ... >> args);
  }
}

}  // namespace aevol

#endif  // AEVOL_UTILITY_H_
