#pragma once
#include <algorithm>
#include <iomanip>
#include <iosfwd>
#include <cxx20ranges>
#include <string>

namespace output {
template <class C>
concept RawArray = std::is_array_v<C>;
template <class C>
concept ArrayClass = requires(C c)
{
    { c[1ul] };
    { c.size() };
};

template <class C>
concept StringLike = std::convertible_to<C, std::string_view>;

namespace {
    //auto size(const Container auto& C) { return C.size(); }
    auto size(const RawArray auto& C) { return std::extent_v<std::remove_cvref_t<decltype(C)>>; }
}

template <class C>
concept ArrayLike = (not StringLike<C>)and(ArrayClass<C> or RawArray<C>);

template <class T>
void write_possibly_quoted(std::ostream& os, T&& t)
{
    if constexpr (StringLike<T>) {
        os << std::quoted(t);
    } else {
        os << t;
    }
}

std::ostream& operator<<(std::ostream& os, const ArrayLike auto& c)
{
    os << "[";
    auto sz = size(c);
    for (size_t i {}; i != sz; ++i) {
        write_possibly_quoted(os, c[i]);
        if (i + 1 < sz)
            os << ", ";
    }
    return os << "]";
}

template <class T>
requires(not StringLike<T> and sr::range<T>)
    std::ostream&
    operator<<(std::ostream& os, T&& r)
{
    os << "[";
    for (auto el : r) {
        write_possibly_quoted(os, el);
        os << ", ";
    }
    return os << "]";
}
template <class SepType>
class rprinter {
    std::ostream* strmptr = nullptr;
    SepType sep;

public:
    rprinter(SepType s)
        : sep { s }
    {
    }
    rprinter(const rprinter&) noexcept = default;
    std::ostream& stream() { return *strmptr; }
    void stream(std::ostream& os) { strmptr = &os; }
    const auto& separator() const noexcept { return sep; }
};
template <>
class rprinter<void> {
    std::ostream* strmptr = nullptr;

public:
    std::ostream& stream() { return *strmptr; }
    void stream(std::ostream& os) { strmptr = &os; }
};

template <class T>
rprinter<T> operator<<(std::ostream& os, rprinter<T> r)
{
    r.stream(os);
    return { r };
}
template <class T>
std::ostream& operator<<(rprinter<T> vp, sr::range auto&& t)
{
    if constexpr (std::is_same_v<T, void>) {
        for (auto&& el : t)
            vp.stream() << el;
    } else {
        if (not sr::empty(t)) {
            vp.stream() << *sr::begin(t);
            sr::for_each(t | sv::drop(1), [&vp](auto&& el) {
                vp.stream() << vp.separator() << el;
            });
        }
    }
    return vp.stream();
}

inline rprinter<void> unseparated;
inline rprinter<const char*> comma_separated { ", " };

}