#ifndef INCLUDED_BOBCAT_MULTIBUF_
#define INCLUDED_BOBCAT_MULTIBUF_

#include <cstdio>
#include <streambuf>
#include <vector>
#include <ostream>
#include <string>

#include <bobcat/exception>

namespace FBB
{

struct MultiBuf: public std::streambuf
{
    enum Mode
    {
        OFF,                // stream not used
        ON,                 // stream always used
        ONCE,               // stream used until flushed
        RESET,              // stream once used. Set to ONCE to re-use
        ALL,                // with remove: remove all ostreams matching os
    };

    class stream            // holds a pointer to a stream and a indicator
    {                       // telling us whether or not to use the stream
        friend MultiBuf;

        std::ostream *d_os;
        Mode          d_mode;

        public:
            stream(std::ostream &os, Mode mode = ON);   // 1.f
            void setMode(Mode mode);                    // .f
            Mode mode() const;                          // .f
            std::ostream &ostream();                    // .f
        private:
            static void setOnce(stream &os);            // .f
    };

    using iterator =  std::vector<stream>::iterator;
    using const_iterator =  std::vector<stream>::const_iterator;

    private:
        std::string d_buffer;
        std::vector<stream> d_os;

    public:
        MultiBuf()    = default;
        explicit MultiBuf(std::ostream &os, Mode mode = ON);      // 1.f
        explicit MultiBuf(std::vector<stream> const &osvector);   // 2.f

        void insert(std::ostream &os, Mode mode = ON);                  // 1.f
        void insert(std::vector<stream> const &os);                     // 2.f

        bool remove(std::ostream &os, Mode mode = ONCE);

        iterator begin();                                               // 1.f
        iterator end();                                                 // 1.f
        const_iterator begin() const;                                   // 2.f
        const_iterator end() const;                                     // 2.f
        size_t size() const;                                            //  .f
        void setOnce();             // reset all `RESET' modes to `ONCE'

    private:
        int overflow(int c) override;
        std::streamsize xsputn(char const *buffer, std::streamsize n)
                                                                override;
        int sync() override;

        struct Insert;

        static void insertStruct(stream &os, Insert &insert);
};

inline MultiBuf::Mode MultiBuf::stream::mode() const
{
    return d_mode;
}
inline std::ostream &MultiBuf::stream::ostream()
{
    return *d_os;
}
inline void MultiBuf::stream::setMode(Mode mode)
{
    d_mode = mode;
}
inline void MultiBuf::stream::setOnce(stream &os)
{
    if (os.d_mode == RESET)
        os.d_mode = ONCE;
}
inline MultiBuf::stream::stream(std::ostream &os, Mode mode)
:
    d_os(&os),
    d_mode(mode)
{}

inline MultiBuf::MultiBuf(std::ostream &os, Mode mode)
{
    insert(os, mode);
}
inline MultiBuf::MultiBuf(std::vector<stream> const &osvector)
{
    insert(osvector);
}

inline MultiBuf::iterator MultiBuf::begin()
{
    return d_os.begin();
}
inline MultiBuf::const_iterator MultiBuf::begin() const
{
    return d_os.begin();
}
inline MultiBuf::iterator MultiBuf::end()
{
    return d_os.end();
}
inline MultiBuf::const_iterator MultiBuf::end() const
{
    return d_os.end();
}
inline void MultiBuf::insert(std::ostream &os, Mode mode)
{
    d_os.push_back(stream(os, mode));
}
inline void MultiBuf::insert(std::vector<stream> const &os)
{
    d_os.insert(d_os.end(), os.begin(), os.end());
}

} // namespace FBB

#endif
