#ifndef INCLUDED_BOBCAT_MSTREAM_
#define INCLUDED_BOBCAT_MSTREAM_

#include <climits>
#include <ostream>
#include <fstream>
#include <unistd.h>

#include <bobcat/mbuf>
#include <bobcat/exception>

namespace FBB
{
    std::ostream &endl(std::ostream &os);
    std::ostream &flush(std::ostream &os);
    std::ostream &noid(std::ostream &os);
    std::ostream &noidl(std::ostream &os);

    // http://www.drdobbs.com/184401357:
    //      A slightly more serious concern is that the virtual functions in a
    //      class that's derived from streambuf might throw exceptions;
    //      perhaps, for example, a user-defined class for network I/O might
    //      throw an exception when the underlying connection has been
    //      lost. High-level istream and ostream functions will catch those
    //      exceptions and translate them into an error state within the
    //      stream (that's one of the reasons that seemingly innocent
    //      functions like istream::get are so complicated), but if you're
    //      working with stream buffers directly there's nobody to catch
    //      exceptions for you. If you work with stream buffers, you should
    //      make sure that your code is exception safe.

class Mstream: private Mbuf, public std::ostream
{
    public:
        Mstream();                                  // 1.f initializes to cout

        explicit Mstream(std::ostream &ostr,        // 2.f writes to ostr
                            size_t maxCount = UINT_MAX,
                            std::string const &tag = "",
                            bool throwing = false);

        explicit Mstream(std::streambuf *buf,       // 3.f writes to buf
                            size_t maxCount = UINT_MAX,
                            std::string const &tag = "",
                            bool throwing = false);

                                                // uses a named ofstream,
        explicit Mstream(std::string const &name,   // 4.f
                            size_t maxCount = UINT_MAX,
                            std::string const &tag = "",
                            bool throwing = false);

        static void echo(bool trueIsOn, int fd = STDIN_FILENO);

        // rdbuf members are still available but will turn a Mstream into
        // the kind of stream the streambuf handles. Except for one if the
        // reset members below Mstream assumes an Mbuf

        long id() const;                                        // .f

        void reset(std::ostream &ostr);                         // 1.f
        void reset(std::string const &name);                    // 2.f
        void reset(std::streambuf *mbuf);                       // 3.f

        void reset(Mstream const &mstream);

        void reset(std::ostream &ostr, size_t maxCount,         // 4.f
                     std::string const &tag, bool throwing);

        void reset(std::string const &name, size_t maxCount,    // 5.f
                     std::string const &tag,
                     bool throwing);            // opens the ofstream
                                                // and uses that stream for
                                                // Mstream's output

        void reset(std::streambuf *mbuf, size_t maxCount,       // 6.f
                        std::string const &tag, bool throwing);

        void on();                                              // .f
        void off();                                             // .f
        bool isActive() const;                                  // .f
        bool setActive(bool ifTrue);

        using Mbuf::count;          // size_t count() const;
        using Mbuf::lineExcess;     // bool lineExcess() const;
        using Mbuf::lineTag;        // string const &lineTag() const;
        using Mbuf::maxCount;       // size_t maxCount() const;
        using Mbuf::setCount;       // void setCount(size_t count);
        using Mbuf::setLineNr;      // void setLineNr(size_t lineNr);
        using Mbuf::setLineTag;     // void setLineTag(string const &lineTag);
        using Mbuf::setMaxCount;    // void setMaxCount(size_t maxCount);
        using Mbuf::setTag;         // void setTag(string const &tag);
        using Mbuf::tag;            // string const &tag() const;
        using Mbuf::noLineNr;       // void noLineNr();
        using Mbuf::throws;         // bool throws() const;
        using Mbuf::throwing;       // bool throwing(bool ifTrue);
};

inline Mstream::Mstream()
:
    std::ostream(this)
{}
inline Mstream::Mstream(std::ostream &ostr, size_t maxCount,
                             std::string const &tag, bool throwing)
:
    Mbuf(ostr.rdbuf(), maxCount, tag, throwing),
    std::ostream(this)
{}
inline Mstream::Mstream(std::streambuf *buf, size_t maxCount,
                             std::string const &tag, bool throwing)
:
    Mbuf(buf, maxCount, tag, throwing),
    std::ostream(this)
{}
inline Mstream::Mstream(std::string const &name, size_t maxCount,
                             std::string const &tag, bool throwing)
:
    Mbuf(name, maxCount, tag, throwing),
    std::ostream(this)
{}

inline long Mstream::id() const
{
    return reinterpret_cast<long>(this);
}
inline bool Mstream::isActive() const
{
    return good();
}
inline void Mstream::off()
{
    setstate(std::ios::badbit);
}
inline void Mstream::on()
{
    clear();
}
inline void Mstream::reset(std::ostream &ostr)
{
    Mbuf::reset(ostr.rdbuf(), maxCount(), tag(), throws());
}
inline void Mstream::reset(std::string const &name)
{
    Mbuf::reset(name, maxCount(), tag(), throws());
}
inline void Mstream::reset(std::streambuf *buf)
{
    Mbuf::reset(buf, maxCount(), tag(), throws());
}
inline void Mstream::reset(std::ostream &ostr, size_t maxCount,
                           std::string const &tag, bool throwing)
{
    Mbuf::reset(ostr.rdbuf(), maxCount, tag, throwing);
}
inline void Mstream::reset(std::string const &name, size_t maxCount,
                           std::string const &tag, bool throwing)
{
    Mbuf::reset(name, maxCount, tag, throwing);
}
inline void Mstream::reset(std::streambuf *buf, size_t maxCount,
                           std::string const &tag, bool throwing)
{
    Mbuf::reset(buf, maxCount, tag, throwing);
}

extern Mstream emsg;
extern Mstream fmsg;
extern Mstream imsg;
extern Mstream wmsg;

} // FBB
#endif
