#pragma once

// printf-like routines that return std::string.

#include <cstdio>
#include <memory>
#include <string>
#include <sstream>
#include <system_error>
#include <utility>
#include <vector>

#include <arbor/util/optional.hpp>

namespace pyarb {
namespace util {

// Use ADL to_string or std::to_string, falling back to ostream formatting:

namespace impl_to_string {
    using std::to_string;

    template <typename T, typename = void>
    struct select {
        static std::string str(const T& value) {
            std::ostringstream o;
            o << value;
            return o.str();
        }
    };

    // Can be eplaced with std::void_t in c++17.
    template <typename ...Args>
    using void_t = void;

    template <typename T>
    struct select<T, void_t<decltype(to_string(std::declval<T>()))>> {
        static std::string str(const T& v) {
            return to_string(v);
        }
    };
}

template <typename T>
std::string to_string(const T& value) {
    return impl_to_string::select<T>::str(value);
}

// Use snprintf to format a string, with special handling for standard
// smart pointer types and strings.

namespace impl {
    template <typename X>
    X sprintf_arg_translate(const X& x) { return x; }

    inline const char* sprintf_arg_translate(const std::string& x) { return x.c_str(); }

    template <typename T, typename Deleter>
    T* sprintf_arg_translate(const std::unique_ptr<T, Deleter>& x) { return x.get(); }

    template <typename T>
    T* sprintf_arg_translate(const std::shared_ptr<T>& x) { return x.get(); }
}

template <typename... Args>
std::string strprintf(const char* fmt, Args&&... args) {
    thread_local static std::vector<char> buffer(1024);

    for (;;) {
        int n = std::snprintf(buffer.data(), buffer.size(), fmt, impl::sprintf_arg_translate(std::forward<Args>(args))...);
        if (n<0) {
            throw std::system_error(errno, std::generic_category());
        }
        else if ((unsigned)n<buffer.size()) {
            return std::string(buffer.data(), n);
        }
        buffer.resize(2*n);
    }
}

template <typename... Args>
std::string strprintf(const std::string& fmt, Args&&... args) {
    return strprintf(fmt.c_str(), std::forward<Args>(args)...);
}

// Substitute instances of '{}' in the format string with the following parameters,
// using default std::ostream formatting.

namespace impl {
    inline void pprintf_(std::ostringstream& o, const char* s) {
        o << s;
    }

    template <typename T, typename... Tail>
    void pprintf_(std::ostringstream& o, const char* s, T&& value, Tail&&... tail) {
        const char* t = s;
        while (*t && !(t[0]=='{' && t[1]=='}')) {
            ++t;
        }
        o.write(s, t-s);
        if (*t) {
            o << std::forward<T>(value);
            pprintf_(o, t+2, std::forward<Tail>(tail)...);
        }
    }
}

template <typename... Args>
std::string pprintf(const char *s, Args&&... args) {
    std::ostringstream o;
    impl::pprintf_(o, s, std::forward<Args>(args)...);
    return o.str();
}

namespace impl {

    template <typename Seq>
    struct sepval {
        const Seq& seq_;
        const char* sep_;

        sepval(const Seq& seq, const char* sep): seq_(seq), sep_(sep) {}

        friend std::ostream& operator<<(std::ostream& o, const sepval& s) {
            bool first = true;
            for (auto& x: s.seq_) {
                if (!first) o << s.sep_;
                first = false;
                o << x;
            }
            return o;
        }
    };

    template <typename Seq>
    struct sepval_lim {
        const Seq& seq_;
        const char* sep_;
        unsigned count_;

        sepval_lim(const Seq& seq, const char* sep, unsigned count): seq_(seq), sep_(sep), count_(count) {}

        friend std::ostream& operator<<(std::ostream& o, const sepval_lim& s) {
            bool first = true;
            unsigned n = s.count_;
            for (auto& x: s.seq_) {
                if (!first) {
                    o << s.sep_;
                }
                first = false;
                if (!n) {
                    return o << "...";
                }
                --n;
                o << x;
            }
            return o;
        }
    };
}

template <typename Seq>
impl::sepval<Seq> sepval(const char* sep, const Seq& seq) {
    return impl::sepval<Seq>(seq, sep);
}

template <typename Seq>
impl::sepval_lim<Seq> sepval(const char* sep, const Seq& seq, unsigned n) {
    return impl::sepval_lim<Seq>(seq, sep, n);
}

template <typename Seq>
impl::sepval<Seq> csv(const Seq& seq) {
    return impl::sepval<Seq>(seq, ", ");
}

template <typename Seq>
impl::sepval_lim<Seq> csv(const Seq& seq, unsigned n) {
    return impl::sepval_lim<Seq>(seq, ", ", n);
}

} // namespace util

template <typename T>
std::ostream& operator<<(std::ostream& o, const arb::util::optional<T>& x) {
    return o << (x? util::to_string(*x): "None");
}

} // namespace pyarb