// (C) Copyright 2008 CodeRage, LLC (turkanis at coderage dot com)
// (C) Copyright 2003-2007 Jonathan Turkanis
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)

// See http://www.boost.org/libs/iostreams for documentation.

#ifndef CPPCMS_BOOST_IOSTREAMS_DETAIL_CHAIN_HPP_INCLUDED
#define CPPCMS_BOOST_IOSTREAMS_DETAIL_CHAIN_HPP_INCLUDED

#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif

#include <cassert>
#include <exception>
#include <functional>                           // unary_function.
#include <iterator>                             // advance.
#include <list>
#include <memory>                               // allocator, auto_ptr.
#include <typeinfo>
#include <stdexcept>                            // logic_error, out_of_range.
#include <cppcms_boost/checked_delete.hpp>
#include <cppcms_boost/config.hpp>                     // BOOST_MSVC, template friends,
#include <cppcms_boost/detail/workaround.hpp>          // BOOST_NESTED_TEMPLATE 
#include <cppcms_boost/iostreams/constants.hpp>
#include <cppcms_boost/iostreams/detail/access_control.hpp>
#include <cppcms_boost/iostreams/detail/char_traits.hpp>
#include <cppcms_boost/iostreams/detail/push.hpp>
#include <cppcms_boost/iostreams/detail/streambuf.hpp> // pubsync.
#include <cppcms_boost/iostreams/detail/wrap_unwrap.hpp>
#include <cppcms_boost/iostreams/device/null.hpp>
#include <cppcms_boost/iostreams/positioning.hpp>
#include <cppcms_boost/iostreams/traits.hpp>           // is_filter.
#include <cppcms_boost/iostreams/stream_buffer.hpp>
#include <cppcms_boost/next_prior.hpp>
#include <cppcms_boost/shared_ptr.hpp>
#include <cppcms_boost/static_assert.hpp>
#include <cppcms_boost/type_traits/is_convertible.hpp>
#include <cppcms_boost/type.hpp>
#include <cppcms_boost/iostreams/detail/execute.hpp>   // VC6.5 requires this
#if CPPCMS_BOOST_WORKAROUND(CPPCMS_BOOST_MSVC, < 1310)        // #include order
# include <cppcms_boost/mpl/int.hpp>
#endif

// Sometimes type_info objects must be compared by name. Borrowed from
// Boost.Python and Boost.Function.
#if (defined(__GNUC__) && __GNUC__ >= 3) || \
     defined(_AIX) || \
    (defined(__sgi) && defined(__host_mips)) || \
    (defined(linux) && defined(__INTEL_COMPILER) && defined(__ICC)) \
    /**/
# include <cstring>
# define CPPCMS_BOOST_IOSTREAMS_COMPARE_TYPE_ID(X,Y) \
     (std::strcmp((X).name(),(Y).name()) == 0)
#else
# define CPPCMS_BOOST_IOSTREAMS_COMPARE_TYPE_ID(X,Y) ((X)==(Y))
#endif

// Deprecated
#define CPPCMS_BOOST_IOSTREAMS_COMPONENT_TYPE(chain, index) \
    chain.component_type( index ) \
    /**/

#if !CPPCMS_BOOST_WORKAROUND(CPPCMS_BOOST_MSVC, < 1310)
# define CPPCMS_BOOST_IOSTREAMS_COMPONENT(chain, index, target) \
    chain.component< target >( index ) \
    /**/
#else
# define CPPCMS_BOOST_IOSTREAMS_COMPONENT(chain, index, target) \
    chain.component( index, ::cppcms_boost::type< target >() ) \
    /**/
#endif

namespace cppcms_boost { namespace iostreams {

//--------------Definition of chain and wchain--------------------------------//

namespace detail {

template<typename Chain> class chain_client;

//
// Concept name: Chain.
// Description: Represents a chain of stream buffers which provides access
//     to the first buffer in the chain and send notifications when the
//     streambufs are added to or removed from chain.
// Refines: Closable device with mode equal to typename Chain::mode.
// Models: chain, converting_chain.
// Example:
//
//    class chain {
//    public:
//        typedef xxx chain_type;
//        typedef xxx client_type;
//        typedef xxx mode;
//        bool is_complete() const;                  // Ready for i/o.
//        template<typename T>
//        void push( const T& t,                     // Adds a stream buffer to
//                   streamsize,                     // chain, based on t, with
//                   streamsize );                   // given buffer and putback
//                                                   // buffer sizes. Pass -1 to
//                                                   // request default size.
//    protected:
//        void register_client(client_type* client); // Associate client.
//        void notify();                             // Notify client.
//    };
//

//
// Description: Represents a chain of filters with an optional device at the
//      end.
// Template parameters:
//      Self - A class deriving from the current instantiation of this template.
//          This is an example of the Curiously Recurring Template Pattern.
//      Ch - The character type.
//      Tr - The character traits type.
//      Alloc - The allocator type.
//      Mode - A mode tag.
//
template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
class chain_base {
public:
    typedef Ch                                     char_type;
    CPPCMS_BOOST_IOSTREAMS_STREAMBUF_TYPEDEFS(Tr)
    typedef Alloc                                  allocator_type;
    typedef Mode                                   mode;
    struct category
        : Mode,
          device_tag
        { };
    typedef chain_client<Self>                     client_type;
    friend class chain_client<Self>;
private:
    typedef linked_streambuf<Ch>                   streambuf_type;
    typedef std::list<streambuf_type*>             list_type;
    typedef chain_base<Self, Ch, Tr, Alloc, Mode>  my_type;
protected:
    chain_base() : pimpl_(new chain_impl) { }
    chain_base(const chain_base& rhs): pimpl_(rhs.pimpl_) { }
public:

    // dual_use is a pseudo-mode to facilitate filter writing, 
    // not a genuine mode.
    CPPCMS_BOOST_STATIC_ASSERT((!is_convertible<mode, dual_use>::value));

    //----------Buffer sizing-------------------------------------------------//

    // Sets the size of the buffer created for the devices to be added to this
    // chain. Does not affect the size of the buffer for devices already
    // added.
    void set_device_buffer_size(int n) { pimpl_->device_buffer_size_ = n; }

    // Sets the size of the buffer created for the filters to be added
    // to this chain. Does not affect the size of the buffer for filters already
    // added.
    void set_filter_buffer_size(int n) { pimpl_->filter_buffer_size_ = n; }

    // Sets the size of the putback buffer for filters and devices to be added
    // to this chain. Does not affect the size of the buffer for filters or
    // devices already added.
    void set_pback_size(int n) { pimpl_->pback_size_ = n; }

    //----------Device interface----------------------------------------------//

    std::streamsize read(char_type* s, std::streamsize n);
    std::streamsize write(const char_type* s, std::streamsize n);
    std::streampos seek(stream_offset off, CPPCMS_BOOST_IOS::seekdir way);

    //----------Direct component access---------------------------------------//

    const std::type_info& component_type(int n) const
    {
        if (static_cast<size_type>(n) >= size())
            throw std::out_of_range("bad chain offset");
        return (*cppcms_boost::next(list().begin(), n))->component_type();
    }

#if !CPPCMS_BOOST_WORKAROUND(CPPCMS_BOOST_MSVC, < 1310)
    // Deprecated.
    template<int N>
    const std::type_info& component_type() const { return component_type(N); }

    template<typename T>
    T* component(int n) const { return component(n, cppcms_boost::type<T>()); }

    // Deprecated.
    template<int N, typename T> 
    T* component() const { return component<T>(N); }
#endif

#if !CPPCMS_BOOST_WORKAROUND(CPPCMS_BOOST_MSVC, < 1310)
    private:
#endif
    template<typename T>
    T* component(int n, cppcms_boost::type<T>) const
    {
        if (static_cast<size_type>(n) >= size())
            throw std::out_of_range("bad chain offset");
        streambuf_type* link = *cppcms_boost::next(list().begin(), n);
        if (CPPCMS_BOOST_IOSTREAMS_COMPARE_TYPE_ID(link->component_type(), typeid(T)))
            return static_cast<T*>(link->component_impl());
        else
            return 0;
    }
public:

    //----------Container-like interface--------------------------------------//

    typedef typename list_type::size_type size_type;
    streambuf_type& front() { return *list().front(); }
    CPPCMS_BOOST_IOSTREAMS_DEFINE_PUSH(push, mode, char_type, push_impl)
    void pop();
    bool empty() const { return list().empty(); }
    size_type size() const { return list().size(); }
    void reset();

    //----------Additional i/o functions--------------------------------------//

    // Returns true if this chain is non-empty and its final link
    // is a source or sink, i.e., if it is ready to perform i/o.
    bool is_complete() const;
    bool auto_close() const;
    void set_auto_close(bool close);
    bool sync() { return front().CPPCMS_BOOST_IOSTREAMS_PUBSYNC() != -1; }
    bool strict_sync();
private:
    template<typename T>
    void push_impl(const T& t, int buffer_size = -1, int pback_size = -1)
    {
        typedef typename iostreams::category_of<T>::type  category;
        typedef typename unwrap_ios<T>::type              component_type;
        typedef stream_buffer<
                    component_type,
                    CPPCMS_BOOST_IOSTREAMS_CHAR_TRAITS(char_type),
                    Alloc, Mode
                >                                         streambuf_t;
        typedef typename list_type::iterator              iterator;
        CPPCMS_BOOST_STATIC_ASSERT((is_convertible<category, Mode>::value));
        if (is_complete())
            throw std::logic_error("chain complete");
        streambuf_type* prev = !empty() ? list().back() : 0;
        buffer_size =
            buffer_size != -1 ?
                buffer_size :
                iostreams::optimal_buffer_size(t);
        pback_size =
            pback_size != -1 ?
                pback_size :
                pimpl_->pback_size_;
        std::auto_ptr<streambuf_t>
            buf(new streambuf_t(t, buffer_size, pback_size));
        list().push_back(buf.get());
        buf.release();
        if (is_device<component_type>::value) {
            pimpl_->flags_ |= f_complete | f_open;
            for ( iterator first = list().begin(),
                           last = list().end();
                  first != last;
                  ++first )
            {
                (*first)->set_needs_close();
            }
        }
        if (prev) prev->set_next(list().back());
        notify();
    }

    list_type& list() { return pimpl_->links_; }
    const list_type& list() const { return pimpl_->links_; }
    void register_client(client_type* client) { pimpl_->client_ = client; }
    void notify() { if (pimpl_->client_) pimpl_->client_->notify(); }

    //----------Nested classes------------------------------------------------//

    static void close(streambuf_type* b, CPPCMS_BOOST_IOS::openmode m)
    {
        if (m == CPPCMS_BOOST_IOS::out && is_convertible<Mode, output>::value)
            b->CPPCMS_BOOST_IOSTREAMS_PUBSYNC();
        b->close(m);
    }

    static void set_next(streambuf_type* b, streambuf_type* next)
    { b->set_next(next); }

    static void set_auto_close(streambuf_type* b, bool close)
    { b->set_auto_close(close); }

    struct closer  : public std::unary_function<streambuf_type*, void>  {
        closer(CPPCMS_BOOST_IOS::openmode m) : mode_(m) { }
        void operator() (streambuf_type* b)
        {
            close(b, mode_);
        }
        CPPCMS_BOOST_IOS::openmode mode_;
    };
    friend struct closer;

    enum flags {
        f_complete = 1,
        f_open = 2,
        f_auto_close = 4
    };

    struct chain_impl {
        chain_impl()
            : client_(0), device_buffer_size_(default_device_buffer_size),
              filter_buffer_size_(default_filter_buffer_size),
              pback_size_(default_pback_buffer_size),
              flags_(f_auto_close)
            { }
        ~chain_impl() { try { close(); reset(); } catch (...) { } }
        void close()
            {
                if ((flags_ & f_open) != 0) {
                    flags_ &= ~f_open;
                    stream_buffer< basic_null_device<Ch, Mode> > null;
                    if ((flags_ & f_complete) == 0) {
                        null.open(basic_null_device<Ch, Mode>());
                        set_next(links_.back(), &null);
                    }
                    links_.front()->CPPCMS_BOOST_IOSTREAMS_PUBSYNC();
                    try {
                        cppcms_boost::iostreams::detail::execute_foreach(
                            links_.rbegin(), links_.rend(), 
                            closer(CPPCMS_BOOST_IOS::in)
                        );
                    } catch (...) {
                        try {
                            cppcms_boost::iostreams::detail::execute_foreach(
                                links_.begin(), links_.end(), 
                                closer(CPPCMS_BOOST_IOS::out)
                            );
                        } catch (...) { }
                        throw;
                    }
                    cppcms_boost::iostreams::detail::execute_foreach(
                        links_.begin(), links_.end(), 
                        closer(CPPCMS_BOOST_IOS::out)
                    );
                }
            }
        void reset()
            {
                typedef typename list_type::iterator iterator;
                for ( iterator first = links_.begin(),
                               last = links_.end();
                      first != last;
                      ++first )
                {
                    if ( (flags_ & f_complete) == 0 ||
                         (flags_ & f_auto_close) == 0 )
                    {
                        set_auto_close(*first, false);
                    }
                    streambuf_type* buf = 0;
                    std::swap(buf, *first);
                    delete buf;
                }
                links_.clear();
                flags_ &= ~f_complete;
                flags_ &= ~f_open;
            }
        list_type     links_;
        client_type*  client_;
        int           device_buffer_size_,
                      filter_buffer_size_,
                      pback_size_;
        int           flags_;
    };
    friend struct chain_impl;

    //----------Member data---------------------------------------------------//

private:
    shared_ptr<chain_impl> pimpl_;
};

} // End namespace detail.

//
// Macro: BOOST_IOSTREAMS_DECL_CHAIN(name, category)
// Description: Defines a template derived from chain_base appropriate for a
//      particular i/o category. The template has the following parameters:
//      Ch - The character type.
//      Tr - The character traits type.
//      Alloc - The allocator type.
// Macro parameters:
//      name_ - The name of the template to be defined.
//      category_ - The i/o category of the template to be defined.
//
#define CPPCMS_BOOST_IOSTREAMS_DECL_CHAIN(name_, default_char_) \
    template< typename Mode, typename Ch = default_char_, \
              typename Tr = CPPCMS_BOOST_IOSTREAMS_CHAR_TRAITS(Ch), \
              typename Alloc = std::allocator<Ch> > \
    class name_ : public cppcms_boost::iostreams::detail::chain_base< \
                            name_<Mode, Ch, Tr, Alloc>, \
                            Ch, Tr, Alloc, Mode \
                         > \
    { \
    public: \
        struct category : device_tag, Mode { }; \
        typedef Mode                                   mode; \
    private: \
        typedef cppcms_boost::iostreams::detail::chain_base< \
                    name_<Mode, Ch, Tr, Alloc>, \
                    Ch, Tr, Alloc, Mode \
                >                                      base_type; \
    public: \
        typedef Ch                                     char_type; \
        typedef Tr                                     traits_type; \
        typedef typename traits_type::int_type         int_type; \
        typedef typename traits_type::off_type         off_type; \
        name_() { } \
        name_(const name_& rhs) : base_type(rhs) { } \
        name_& operator=(const name_& rhs) \
        { base_type::operator=(rhs); return *this; } \
    }; \
    /**/
CPPCMS_BOOST_IOSTREAMS_DECL_CHAIN(chain, char)
CPPCMS_BOOST_IOSTREAMS_DECL_CHAIN(wchain, wchar_t)
#undef CPPCMS_BOOST_IOSTREAMS_DECL_CHAIN

//--------------Definition of chain_client------------------------------------//

namespace detail {

//
// Template name: chain_client
// Description: Class whose instances provide access to an underlying chain
//      using an interface similar to the chains.
// Subclasses: the various stream and stream buffer templates.
//
template<typename Chain>
class chain_client {
public:
    typedef Chain                             chain_type;
    typedef typename chain_type::char_type    char_type;
    typedef typename chain_type::traits_type  traits_type;
    typedef typename chain_type::size_type    size_type;
    typedef typename chain_type::mode         mode;

    chain_client(chain_type* chn = 0) : chain_(chn ) { }
    chain_client(chain_client* client) : chain_(client->chain_) { }
    virtual ~chain_client() { }

    const std::type_info& component_type(int n) const
    { return chain_->component_type(n); }

#if !CPPCMS_BOOST_WORKAROUND(CPPCMS_BOOST_MSVC, < 1310)
    // Deprecated.
    template<int N>
    const std::type_info& component_type() const
    { return chain_->CPPCMS_BOOST_NESTED_TEMPLATE component_type<N>(); }

    template<typename T>
    T* component(int n) const
    { return chain_->CPPCMS_BOOST_NESTED_TEMPLATE component<T>(n); }

    // Deprecated.
    template<int N, typename T>
    T* component() const
    { return chain_->CPPCMS_BOOST_NESTED_TEMPLATE component<N, T>(); }
#else
    template<typename T>
    T* component(int n, cppcms_boost::type<T> t) const
    { return chain_->component(n, t); }
#endif

    bool is_complete() const { return chain_->is_complete(); }
    bool auto_close() const { return chain_->auto_close(); }
    void set_auto_close(bool close) { chain_->set_auto_close(close); }
    bool strict_sync() { return chain_->strict_sync(); }
    void set_device_buffer_size(std::streamsize n)
        { chain_->set_device_buffer_size(n); }
    void set_filter_buffer_size(std::streamsize n)
        { chain_->set_filter_buffer_size(n); }
    void set_pback_size(std::streamsize n) { chain_->set_pback_size(n); }
    CPPCMS_BOOST_IOSTREAMS_DEFINE_PUSH(push, mode, char_type, push_impl)
    void pop() { chain_->pop(); }
    bool empty() const { return chain_->empty(); }
    size_type size() { return chain_->size(); }
    void reset() { chain_->reset(); }

    // Returns a copy of the underlying chain.
    chain_type filters() { return *chain_; }
    chain_type filters() const { return *chain_; }
protected:
    template<typename T>
    void push_impl(const T& t CPPCMS_BOOST_IOSTREAMS_PUSH_PARAMS())
    { chain_->push(t CPPCMS_BOOST_IOSTREAMS_PUSH_ARGS()); }
    chain_type& ref() { return *chain_; }
    void set_chain(chain_type* c)
    { chain_ = c; chain_->register_client(this); }
#if !defined(CPPCMS_BOOST_NO_MEMBER_TEMPLATE_FRIENDS) && \
    (!CPPCMS_BOOST_WORKAROUND(__BORLANDC__, < 0x600))
    template<typename S, typename C, typename T, typename A, typename M>
    friend class chain_base;
#else
    public:
#endif
    virtual void notify() { }
private:
    chain_type* chain_;
};

//--------------Implementation of chain_base----------------------------------//

template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
inline std::streamsize chain_base<Self, Ch, Tr, Alloc, Mode>::read
    (char_type* s, std::streamsize n)
{ return iostreams::read(*list().front(), s, n); }

template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
inline std::streamsize chain_base<Self, Ch, Tr, Alloc, Mode>::write
    (const char_type* s, std::streamsize n)
{ return iostreams::write(*list().front(), s, n); }

template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
inline std::streampos chain_base<Self, Ch, Tr, Alloc, Mode>::seek
    (stream_offset off, CPPCMS_BOOST_IOS::seekdir way)
{ return iostreams::seek(*list().front(), off, way); }

template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
void chain_base<Self, Ch, Tr, Alloc, Mode>::reset()
{
    using namespace std;
    pimpl_->close();
    pimpl_->reset();
}

template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
bool chain_base<Self, Ch, Tr, Alloc, Mode>::is_complete() const
{
    return (pimpl_->flags_ & f_complete) != 0;
}

template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
bool chain_base<Self, Ch, Tr, Alloc, Mode>::auto_close() const
{
    return (pimpl_->flags_ & f_auto_close) != 0;
}

template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
void chain_base<Self, Ch, Tr, Alloc, Mode>::set_auto_close(bool close)
{
    pimpl_->flags_ =
        (pimpl_->flags_ & ~f_auto_close) |
        (close ? f_auto_close : 0);
}

template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
bool chain_base<Self, Ch, Tr, Alloc, Mode>::strict_sync()
{
    typedef typename list_type::iterator iterator;
    bool result = true;
    for ( iterator first = list().begin(),
                   last = list().end();
          first != last;
          ++first )
    {
        bool s = (*first)->strict_sync();
        result = result && s;
    }
    return result;
}

template<typename Self, typename Ch, typename Tr, typename Alloc, typename Mode>
void chain_base<Self, Ch, Tr, Alloc, Mode>::pop()
{
    assert(!empty());
    if (auto_close())
        pimpl_->close();
    streambuf_type* buf = 0;
    std::swap(buf, list().back());
    buf->set_auto_close(false);
    buf->set_next(0);
    delete buf;
    list().pop_back();
    pimpl_->flags_ &= ~f_complete;
    if (auto_close() || list().empty())
        pimpl_->flags_ &= ~f_open;
}

} // End namespace detail.

} } // End namespaces iostreams, boost.

#endif // #ifndef BOOST_IOSTREAMS_DETAIL_CHAIN_HPP_INCLUDED
