#ifndef INCLUDED_BOBCAT_OFOLDSTREAMBUF_
#define INCLUDED_BOBCAT_OFOLDSTREAMBUF_

#include <iostream>
#include <string>
#include <vector>

#include <bobcat/ofilterstreambuf>

namespace FBB
{

class lm
{
    size_t d_value;
    
    public:
        lm(int value);                                  // 1.f
        std::ostream &modify(std::ostream &out) const;  // 1.f
};

class mlm
{
    int d_value;
    
    public:
        mlm(int value);                                 // 1.f
        std::ostream &modify(std::ostream &out) const;  // 2.f
};

struct OFoldStreambufEnums
{
    enum TrailingBlanks
    {
        IGNORE_TRAILING_BLANKS,
        HANDLE_TRAILING_BLANKS
    };
    enum TabsOrBlanks
    {
        BLANKS,
        TABS
    };
};
    
    // 'virtual public OFoldStreambufBlanks is used to avoid 'base class not
    // accessible' warnings when classes inherit from OFoldStreambuf like
    // OFoldStream. 
class OFoldStreambuf: virtual public OFoldStreambufEnums, 
                      public OFilterStreambuf
{
    friend std::ostream &lm::modify(std::ostream &) const;
    friend std::ostream &mlm::modify(std::ostream &) const;

    enum Mode
    {
        INDENT,
        WS,
        NON_WS
    };

    std::string d_nonWs;
    std::string d_ws;

    size_t d_rightMargin;
    size_t d_indent;
    bool d_reqIndent;

    size_t d_wsLength;
    size_t d_next;

    Mode d_mode;

    char d_indentChar;
    size_t d_indentWidth;
    bool d_handleTrailingBlanks;

    typedef std::vector<OFoldStreambuf const *>::iterator BufIt;
    static std::vector<OFoldStreambuf const *> s_buffers;

    public:
        explicit OFoldStreambuf(
                   size_t leftIndent = 0, size_t rightMargin = 80,
                   TabsOrBlanks tob = BLANKS,
                   TrailingBlanks tb = IGNORE_TRAILING_BLANKS);

        explicit OFoldStreambuf(char const *fname,
                   size_t leftIndent = 0, size_t rightMargin = 80,
                   TabsOrBlanks tob = BLANKS,
                   TrailingBlanks tb = IGNORE_TRAILING_BLANKS);

        explicit OFoldStreambuf(std::ostream &stream,
                   size_t leftIndent = 0, size_t rightMargin = 80,
                    TabsOrBlanks tob = BLANKS,
                   TrailingBlanks tb = IGNORE_TRAILING_BLANKS);

        ~OFoldStreambuf() override;

        void setMargins(size_t leftMargin, size_t rightMargin);
        void setTrailingBlanks(TrailingBlanks tb);                  // .f
        void useBlanks();                                           // .f
        void useTabs(size_t tabWidth = 8);                          // .f

        static size_t leftMargin(std::streambuf const *buffer);
        static size_t rightMargin(std::streambuf const *buffer);

    private:
        int pSync();                    // see the 4.09.00 changelog

        int sync() override;
        int overflow(int c) override;

        void indent(int c);
        void ws(int c);
        void nonWs(int c);

        size_t length() const;

        void iniBlankTabs(TabsOrBlanks tob);
        void newline();
        void addNonWs(int c);                           // .f
        void addWs(int c);
        void indent();
        void flush();
        void clearWs();

        void modifyIndent(int delta);
        void setIndent(int value);                      // .f

        void writeWs() const;                           // .f    
        void writeNonWs() const;                        // .f
        void put(int ch) const;                         // .f

        static BufIt findOFoldStreambuf(std::streambuf const *buffer);
};

inline lm::lm(int value)
:
    d_value(value < 0 ? 0 : value)
{}
inline std::ostream &lm::modify(std::ostream &out) const
{
    dynamic_cast<OFoldStreambuf &>(*out.rdbuf()).setIndent(d_value);
    return out;
}        

inline mlm::mlm(int value)
:
    d_value(value)
{}
inline std::ostream &mlm::modify(std::ostream &out) const
{
    dynamic_cast<OFoldStreambuf &>(*out.rdbuf()).modifyIndent(d_value);
    return out;
}        

inline size_t OFoldStreambuf::leftMargin(std::streambuf const *buffer)
{
    return (*findOFoldStreambuf(buffer))->d_indent;
}
inline size_t OFoldStreambuf::rightMargin(std::streambuf const *buffer)
{
    return (*findOFoldStreambuf(buffer))->d_rightMargin;
}
inline void OFoldStreambuf::setIndent(int value)
{
    d_indent = value;
}
inline void OFoldStreambuf::setTrailingBlanks(TrailingBlanks tb)
{
    d_handleTrailingBlanks =  tb ==  HANDLE_TRAILING_BLANKS;
}
inline void OFoldStreambuf::useBlanks()
{
    d_indentChar = ' ';
    d_indentWidth = 1;
}
inline void OFoldStreambuf::useTabs(size_t tabWidth)
{
    d_indentChar = '\t';
    d_indentWidth = tabWidth;
}

    // Free functions

inline std::ostream &operator<<(std::ostream &out, lm const &idt)
{
    return idt.modify(out);
}
inline std::ostream &operator<<(std::ostream &out, mlm const &idt)
{
    return idt.modify(out);
}

} // FBB

#endif
