// $Id:$

#ifndef INDENT_HPP
#define INDENT_HPP

#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/operations.hpp>

#include <ios>

// a small helper class to get global variable
// behaviour for the stream pword index
// for header only implementation
// the expression xdent<int>()() will always get
// the same unique index
template <typename T>
struct xdent
{

    int operator() (){
        if (!initialized) {
            index = std::ios::xalloc();
            initialized = true;
        }
        return index;
    }
private:
    static T index;
    static bool initialized;
};

template <typename T> T xdent<T>::index;    
template <typename T> bool xdent<T>::initialized;

// The indent_filter.
// The ctor is private, so the filter can only be
// created from its static push function.
// This is to assert registration to the stream.
class indent_filter
    : public boost::iostreams::output_filter 
{
public:

    template<typename Sink>
    bool put(Sink& dest, int c)
    {
        if (c == '\n')
            linestart_ = true;
        else
            if (linestart_) {
                for(int n=0; n<indent_; ++n)
                    for(int m=0; m<width_; ++m)
                        boost::iostreams::put(dest, ' ');
                linestart_ = false;
            }
        return boost::iostreams::put(dest, c);
    }
    
    template<typename Sink>
    void close(Sink&)
    {
        indent_ = 0;
        linestart_ = true;
    }
    
    void indent_in()
    {
        ++indent_;
    }
    
    void indent_out()
    {
        if (indent_>0) --indent_;
    }
    
    // Of course it would be more elegant to
    // modify the filtering_ostream push
    // function instead, ...
    static void push(boost::iostreams::filtering_ostream& os, int width = 4)
    {
        os.push(indent_filter(width));
        indent_filter* pi =
            os.component<indent_filter>(os.size()-1);
        os.pword(xdent<int>()()) = pi;
        pi->pos_ = &os;
    }
    
    // When the filter is destroyed, it must be deregistered.
    ~indent_filter()
    {
        if (pos_)
            pos_->pword(xdent<int>()()) = 0;
    }
    
private:
    explicit indent_filter(int width)
        : indent_(0)
        , linestart_(true)
        , width_(width)
        , pos_(0)
    { }

    int  indent_;
    bool  linestart_;
    int width_;
    std::ostream* pos_;
};

// This are the manipulators that change indentation.
// Note that this will even work when the filter_stream
// is accessed through its basic_ostream.
// Uniqueness of xdent<int>()() guarantees correct cast
// from void* to indent_filter* .
template<class charT, class traits>
inline std::basic_ostream<charT, traits>&
indent_in(std::basic_ostream<charT, traits>& os)
{
    indent_filter*p = (indent_filter*)os.pword(xdent<int>()());
    if (p) { 
        os.flush(); 
        p->indent_in(); 
    }
    return os;
}

template<class charT, class traits>
inline std::basic_ostream<charT, traits>&
indent_out(std::basic_ostream<charT, traits>& os)
{
    indent_filter*p = (indent_filter*)os.pword(xdent<int>()());
    if (p) { 
        os.flush(); 
        p->indent_out(); 
    }
    return os;
}

#endif // INDENT
