//          Copyright Louis Delacroix 2010 - 2014.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)
//
// A pretty printing library for C++
//
// Usage:
// Include this header, and operator<< will "just work".

#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT

#include <cstddef>
#include <iterator>
#include <memory>
#include <ostream>
#include <set>
#include <tuple>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <valarray>

namespace pretty_print {
namespace detail {
// SFINAE type trait to detect whether T::const_iterator exists.

struct sfinae_base {
  using yes = char;
  using no = yes[2];
};

template <typename T>
struct has_const_iterator : private sfinae_base {
 private:
  template <typename C>
  static yes &test(typename C::const_iterator *);
  template <typename C>
  static no &test(...);

 public:
  static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
  using type = T;
};

template <typename T>
struct has_begin_end : private sfinae_base {
 private:
  template <typename C>
  static yes &
  f(typename std::enable_if<
      std::is_same<decltype(static_cast<typename C::const_iterator (C::*)()
                                            const>(&C::begin)),
                   typename C::const_iterator (C::*)() const>::value>::type *);

  template <typename C>
  static no &f(...);

  template <typename C>
  static yes &g(typename std::enable_if<
                std::is_same<decltype(static_cast<typename C::const_iterator (
                                          C::*)() const>(&C::end)),
                             typename C::const_iterator (C::*)() const>::value,
                void>::type *);

  template <typename C>
  static no &g(...);

 public:
  static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
  static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);
};

}  // namespace detail

// Holds the delimiter values for a specific character type

template <typename TChar>
struct delimiters_values {
  using char_type = TChar;
  const char_type *prefix;
  const char_type *delimiter;
  const char_type *postfix;
};

// Defines the delimiter values for a specific container and character type

template <typename T, typename TChar>
struct delimiters {
  using type = delimiters_values<TChar>;
  static const type values;
};

// Functor to print containers. You can use this directly if you want
// to specify a non-default delimiters type. The printing logic can
// be customized by specializing the nested template.

template <typename T, typename TChar = char,
          typename TCharTraits = ::std::char_traits<TChar>,
          typename TDelimiters = delimiters<T, TChar>>
struct print_container_helper {
  using delimiters_type = TDelimiters;
  using ostream_type = std::basic_ostream<TChar, TCharTraits>;

  template <typename U>
  struct printer {
    static void print_body(const U &c, ostream_type &stream) {
      using std::begin;
      using std::end;

      auto it = begin(c);
      const auto the_end = end(c);

      if (it != the_end) {
        for (;;) {
          stream << *it;

          if (++it == the_end) break;

          if (delimiters_type::values.delimiter != NULL)
            stream << delimiters_type::values.delimiter;
        }
      }
    }
  };

  print_container_helper(const T &container) : container_(container) {}

  inline void operator()(ostream_type &stream) const {
    if (delimiters_type::values.prefix != NULL)
      stream << delimiters_type::values.prefix;

    printer<T>::print_body(container_, stream);

    if (delimiters_type::values.postfix != NULL)
      stream << delimiters_type::values.postfix;
  }

 private:
  const T &container_;
};

// Specialization for pairs

template <typename T, typename TChar, typename TCharTraits,
          typename TDelimiters>
template <typename T1, typename T2>
struct print_container_helper<T, TChar, TCharTraits,
                              TDelimiters>::printer<std::pair<T1, T2>> {
  using ostream_type =
      typename print_container_helper<T, TChar, TCharTraits,
                                      TDelimiters>::ostream_type;

  static void print_body(const std::pair<T1, T2> &c, ostream_type &stream) {
    stream << c.first;
    if (print_container_helper<T, TChar, TCharTraits,
                               TDelimiters>::delimiters_type::values
            .delimiter != NULL)
      stream << print_container_helper<T, TChar, TCharTraits,
                                       TDelimiters>::delimiters_type::values
                    .delimiter;
    stream << c.second;
  }
};

// Specialization for tuples

template <typename T, typename TChar, typename TCharTraits,
          typename TDelimiters>
template <typename... Args>
struct print_container_helper<T, TChar, TCharTraits,
                              TDelimiters>::printer<std::tuple<Args...>> {
  using ostream_type =
      typename print_container_helper<T, TChar, TCharTraits,
                                      TDelimiters>::ostream_type;
  using element_type = std::tuple<Args...>;

  template <std::size_t I>
  struct Int {};

  static void print_body(const element_type &c, ostream_type &stream) {
    tuple_print(c, stream, Int<0>());
  }

  static void tuple_print(const element_type &, ostream_type &,
                          Int<sizeof...(Args)>) {}

  static void tuple_print(
      const element_type &c, ostream_type &stream,
      typename std::conditional<sizeof...(Args) != 0, Int<0>,
                                std::nullptr_t>::type) {
    stream << std::get<0>(c);
    tuple_print(c, stream, Int<1>());
  }

  template <std::size_t N>
  static void tuple_print(const element_type &c, ostream_type &stream, Int<N>) {
    if (print_container_helper<T, TChar, TCharTraits,
                               TDelimiters>::delimiters_type::values
            .delimiter != NULL)
      stream << print_container_helper<T, TChar, TCharTraits,
                                       TDelimiters>::delimiters_type::values
                    .delimiter;

    stream << std::get<N>(c);

    tuple_print(c, stream, Int<N + 1>());
  }
};

// Prints a print_container_helper to the specified stream.

template <typename T, typename TChar, typename TCharTraits,
          typename TDelimiters>
inline std::basic_ostream<TChar, TCharTraits> &operator<<(
    std::basic_ostream<TChar, TCharTraits> &stream,
    const print_container_helper<T, TChar, TCharTraits, TDelimiters> &helper) {
  helper(stream);
  return stream;
}

// Basic is_container template; specialize to derive from std::true_type for all
// desired container types

template <typename T>
struct is_container
    : public std::integral_constant<bool,
                                    detail::has_const_iterator<T>::value &&
                                        detail::has_begin_end<T>::beg_value &&
                                        detail::has_begin_end<T>::end_value> {};

template <typename T, std::size_t N>
struct is_container<T[N]> : std::true_type {};

template <std::size_t N>
struct is_container<char[N]> : std::false_type {};

template <typename T>
struct is_container<std::valarray<T>> : std::true_type {};

template <typename T1, typename T2>
struct is_container<std::pair<T1, T2>> : std::true_type {};

template <typename... Args>
struct is_container<std::tuple<Args...>> : std::true_type {};

// Default delimiters

template <typename T>
struct delimiters<T, char> {
  static const delimiters_values<char> values;
};
template <typename T>
const delimiters_values<char> delimiters<T, char>::values = {"[", ", ", "]"};
template <typename T>
struct delimiters<T, wchar_t> {
  static const delimiters_values<wchar_t> values;
};
template <typename T>
const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = {L"[", L", ",
                                                                   L"]"};

// Delimiters for (multi)set and unordered_(multi)set

template <typename T, typename TComp, typename TAllocator>
struct delimiters<::std::set<T, TComp, TAllocator>, char> {
  static const delimiters_values<char> values;
};

template <typename T, typename TComp, typename TAllocator>
const delimiters_values<char>
    delimiters<::std::set<T, TComp, TAllocator>, char>::values = {"{", ", ",
                                                                  "}"};

template <typename T, typename TComp, typename TAllocator>
struct delimiters<::std::set<T, TComp, TAllocator>, wchar_t> {
  static const delimiters_values<wchar_t> values;
};

template <typename T, typename TComp, typename TAllocator>
const delimiters_values<wchar_t>
    delimiters<::std::set<T, TComp, TAllocator>, wchar_t>::values = {
        L"{", L", ", L"}"};

template <typename T, typename TComp, typename TAllocator>
struct delimiters<::std::multiset<T, TComp, TAllocator>, char> {
  static const delimiters_values<char> values;
};

template <typename T, typename TComp, typename TAllocator>
const delimiters_values<char>
    delimiters<::std::multiset<T, TComp, TAllocator>, char>::values = {
        "{", ", ", "}"};

template <typename T, typename TComp, typename TAllocator>
struct delimiters<::std::multiset<T, TComp, TAllocator>, wchar_t> {
  static const delimiters_values<wchar_t> values;
};

template <typename T, typename TComp, typename TAllocator>
const delimiters_values<wchar_t>
    delimiters<::std::multiset<T, TComp, TAllocator>, wchar_t>::values = {
        L"{", L", ", L"}"};

template <typename T, typename THash, typename TEqual, typename TAllocator>
struct delimiters<::std::unordered_set<T, THash, TEqual, TAllocator>, char> {
  static const delimiters_values<char> values;
};

template <typename T, typename THash, typename TEqual, typename TAllocator>
const delimiters_values<char> delimiters<
    ::std::unordered_set<T, THash, TEqual, TAllocator>, char>::values = {
    "{", ", ", "}"};

template <typename T, typename THash, typename TEqual, typename TAllocator>
struct delimiters<::std::unordered_set<T, THash, TEqual, TAllocator>, wchar_t> {
  static const delimiters_values<wchar_t> values;
};

template <typename T, typename THash, typename TEqual, typename TAllocator>
const delimiters_values<wchar_t> delimiters<
    ::std::unordered_set<T, THash, TEqual, TAllocator>, wchar_t>::values = {
    L"{", L", ", L"}"};

template <typename T, typename THash, typename TEqual, typename TAllocator>
struct delimiters<::std::unordered_multiset<T, THash, TEqual, TAllocator>,
                  char> {
  static const delimiters_values<char> values;
};

template <typename T, typename THash, typename TEqual, typename TAllocator>
const delimiters_values<char> delimiters<
    ::std::unordered_multiset<T, THash, TEqual, TAllocator>, char>::values = {
    "{", ", ", "}"};

template <typename T, typename THash, typename TEqual, typename TAllocator>
struct delimiters<::std::unordered_multiset<T, THash, TEqual, TAllocator>,
                  wchar_t> {
  static const delimiters_values<wchar_t> values;
};

template <typename T, typename THash, typename TEqual, typename TAllocator>
const delimiters_values<wchar_t>
    delimiters<::std::unordered_multiset<T, THash, TEqual, TAllocator>,
               wchar_t>::values = {L"{", L", ", L"}"};

// Delimiters for pair and tuple

template <typename T1, typename T2>
struct delimiters<std::pair<T1, T2>, char> {
  static const delimiters_values<char> values;
};
template <typename T1, typename T2>
const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = {
    "(", ", ", ")"};
template <typename T1, typename T2>
struct delimiters<::std::pair<T1, T2>, wchar_t> {
  static const delimiters_values<wchar_t> values;
};
template <typename T1, typename T2>
const delimiters_values<wchar_t>
    delimiters<::std::pair<T1, T2>, wchar_t>::values = {L"(", L", ", L")"};

template <typename... Args>
struct delimiters<std::tuple<Args...>, char> {
  static const delimiters_values<char> values;
};
template <typename... Args>
const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = {
    "(", ", ", ")"};
template <typename... Args>
struct delimiters<::std::tuple<Args...>, wchar_t> {
  static const delimiters_values<wchar_t> values;
};
template <typename... Args>
const delimiters_values<wchar_t>
    delimiters<::std::tuple<Args...>, wchar_t>::values = {L"(", L", ", L")"};

// Type-erasing helper class for easy use of custom delimiters.
// Requires TCharTraits = std::char_traits<TChar> and TChar = char or wchar_t,
// and MyDelims needs to be defined for TChar. Usage: "cout <<
// pretty_print::custom_delims<MyDelims>(x)".

struct custom_delims_base {
  virtual ~custom_delims_base() {}
  virtual std::ostream &stream(::std::ostream &) = 0;
  virtual std::wostream &stream(::std::wostream &) = 0;
};

template <typename T, typename Delims>
struct custom_delims_wrapper : custom_delims_base {
  custom_delims_wrapper(const T &t_) : t(t_) {}

  std::ostream &stream(std::ostream &s) {
    return s << print_container_helper<T, char, std::char_traits<char>, Delims>(
               t);
  }

  std::wostream &stream(std::wostream &s) {
    return s << print_container_helper<T, wchar_t, std::char_traits<wchar_t>,
                                       Delims>(t);
  }

 private:
  const T &t;
};

template <typename Delims>
struct custom_delims {
  template <typename Container>
  custom_delims(const Container &c)
      : base(new custom_delims_wrapper<Container, Delims>(c)) {}

  std::unique_ptr<custom_delims_base> base;
};

template <typename TChar, typename TCharTraits, typename Delims>
inline std::basic_ostream<TChar, TCharTraits> &operator<<(
    std::basic_ostream<TChar, TCharTraits> &s, const custom_delims<Delims> &p) {
  return p.base->stream(s);
}

// A wrapper for a C-style array given as pointer-plus-size.
// Usage: std::cout << pretty_print_array(arr, n) << std::endl;

template <typename T>
struct array_wrapper_n {
  typedef const T *const_iterator;
  typedef T value_type;

  array_wrapper_n(const T *const a, size_t n) : _array(a), _n(n) {}
  inline const_iterator begin() const { return _array; }
  inline const_iterator end() const { return _array + _n; }

 private:
  const T *const _array;
  size_t _n;
};

// A wrapper for hash-table based containers that offer local iterators to each
// bucket. Usage: std::cout << bucket_print(m, 4) << std::endl;  (Prints bucket
// 5 of container m.)

template <typename T>
struct bucket_print_wrapper {
  typedef typename T::const_local_iterator const_iterator;
  typedef typename T::size_type size_type;

  const_iterator begin() const { return m_map.cbegin(n); }

  const_iterator end() const { return m_map.cend(n); }

  bucket_print_wrapper(const T &m, size_type bucket) : m_map(m), n(bucket) {}

 private:
  const T &m_map;
  const size_type n;
};

}  // namespace pretty_print

// Global accessor functions for the convenience wrappers

template <typename T>
inline pretty_print::array_wrapper_n<T> pretty_print_array(const T *const a,
                                                           size_t n) {
  return pretty_print::array_wrapper_n<T>(a, n);
}

template <typename T>
pretty_print::bucket_print_wrapper<T> bucket_print(const T &m,
                                                   typename T::size_type n) {
  return pretty_print::bucket_print_wrapper<T>(m, n);
}

// Main magic entry point: An overload snuck into namespace std.
// Can we do better?

namespace std {
// Prints a container to the stream using default delimiters

template <typename T, typename TChar, typename TCharTraits>
inline typename enable_if<::pretty_print::is_container<T>::value,
                          basic_ostream<TChar, TCharTraits> &>::type
operator<<(basic_ostream<TChar, TCharTraits> &stream, const T &container) {
  return stream
         << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(
                container);
}
}  // namespace std

#endif  // H_PRETTY_PRINT
