// (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.



// Contains machinery for performing code conversion.



#ifndef BOOST_IOSTREAMS_CODE_CONVERTER_HPP_INCLUDED

#define BOOST_IOSTREAMS_CODE_CONVERTER_HPP_INCLUDED



#if defined(_MSC_VER) && (_MSC_VER >= 1020)

# pragma once

#endif



#include <boost/iostreams/detail/config/wide_streams.hpp>

#if defined(BOOST_IOSTREAMS_NO_WIDE_STREAMS) || \

    defined(BOOST_IOSTREAMS_NO_LOCALE) \

    /**/

# error code conversion not supported on this platform

#endif



#include <algorithm>                       // max.

#include <cstring>                         // memcpy.

#include <exception>

#include <boost/config.hpp>                // DEDUCED_TYPENAME, 

#include <boost/iostreams/char_traits.hpp>

#include <boost/iostreams/constants.hpp>   // default_filter_buffer_size.

#include <boost/iostreams/detail/adapter/concept_adapter.hpp>

#include <boost/iostreams/detail/adapter/direct_adapter.hpp>

#include <boost/iostreams/detail/buffer.hpp>

#include <boost/iostreams/detail/call_traits.hpp>

#include <boost/iostreams/detail/codecvt_holder.hpp>

#include <boost/iostreams/detail/codecvt_helper.hpp>

#include <boost/iostreams/detail/double_object.hpp>

#include <boost/iostreams/detail/execute.hpp>

#include <boost/iostreams/detail/forward.hpp>

#include <boost/iostreams/detail/functional.hpp>

#include <boost/iostreams/detail/ios.hpp> // failure, openmode, int types.

#include <boost/iostreams/detail/optional.hpp>

#include <boost/iostreams/detail/select.hpp>

#include <boost/iostreams/traits.hpp>

#include <boost/iostreams/operations.hpp>

#include <boost/shared_ptr.hpp>

#include <boost/static_assert.hpp>

#include <boost/type_traits/is_convertible.hpp>

#include <boost/type_traits/is_same.hpp>



// Must come last.

#include <boost/iostreams/detail/config/disable_warnings.hpp> // Borland 5.x



namespace boost { namespace iostreams {



struct code_conversion_error : BOOST_IOSTREAMS_FAILURE {

    code_conversion_error() 

        : BOOST_IOSTREAMS_FAILURE("code conversion error")

        { }

};



namespace detail {



//--------------Definition of strncpy_if_same---------------------------------//



// Helper template for strncpy_if_same, below.

template<bool B>

struct strncpy_if_same_impl;



template<>

struct strncpy_if_same_impl<true> {

    template<typename Ch>

    static Ch* copy(Ch* tgt, const Ch* src, std::streamsize n)

    { return BOOST_IOSTREAMS_CHAR_TRAITS(Ch)::copy(tgt, src, n); }

};



template<>

struct strncpy_if_same_impl<false> {

    template<typename Src, typename Tgt>

    static Tgt* copy(Tgt* tgt, const Src*, std::streamsize) { return tgt; }

};



template<typename Src, typename Tgt>

Tgt* strncpy_if_same(Tgt* tgt, const Src* src, std::streamsize n)

{

    typedef strncpy_if_same_impl<is_same<Src, Tgt>::value> impl;

    return impl::copy(tgt, src, n);

}



//--------------Definition of conversion_buffer-------------------------------//



// Buffer and conversion state for reading.

template<typename Codecvt, typename Alloc>

class conversion_buffer 

    : public buffer<

                 BOOST_DEDUCED_TYPENAME detail::codecvt_extern<Codecvt>::type,

                 Alloc

             > 

{

public:

    typedef typename Codecvt::state_type state_type;

    conversion_buffer() 

        : buffer<

              BOOST_DEDUCED_TYPENAME detail::codecvt_extern<Codecvt>::type,

              Alloc

          >(0) 

    { 

        reset(); 

    }

    state_type& state() { return state_; }

    void reset() 

    { 

        if (this->size()) 

            this->set(0, 0);

        state_ = state_type(); 

    }

private:

    state_type state_;

};



//--------------Definition of converter_impl----------------------------------//



// Contains member data, open/is_open/close and buffer management functions.

template<typename Device, typename Codecvt, typename Alloc>

struct code_converter_impl {

    typedef typename codecvt_extern<Codecvt>::type          extern_type;

    typedef typename category_of<Device>::type              device_category;

    typedef is_convertible<device_category, input>          can_read;

    typedef is_convertible<device_category, output>         can_write;

    typedef is_convertible<device_category, bidirectional>  is_bidir;

    typedef typename 

            iostreams::select<  // Disambiguation for Tru64.

                is_bidir, bidirectional,

                can_read, input,

                can_write, output

            >::type                                         mode;      

    typedef typename

            mpl::if_<

                is_direct<Device>,

                direct_adapter<Device>,

                Device

            >::type                                         policy_type;

    typedef optional< concept_adapter<policy_type> >        storage_type;

    typedef is_convertible<device_category, two_sequence>   is_double;

    typedef conversion_buffer<Codecvt, Alloc>               buffer_type;



    code_converter_impl() : cvt_(), flags_(0) { }



    ~code_converter_impl()

    { 

        try { 

            if (flags_ & f_open) close(); 

        } catch (...) { /* */ } 

    }



    void open(const Device& dev, int buffer_size)

    {

        if (flags_ & f_open)

            throw BOOST_IOSTREAMS_FAILURE("already open");

        if (buffer_size == -1)

            buffer_size = default_filter_buffer_size;

        int max_length = cvt_.get().max_length();

        buffer_size = (std::max)(buffer_size, 2 * max_length);

        if (can_read::value) {

            buf_.first().resize(buffer_size);

            buf_.first().set(0, 0);

        }

        if (can_write::value && !is_double::value) {

            buf_.second().resize(buffer_size);

            buf_.second().set(0, 0);

        }

        dev_.reset(concept_adapter<policy_type>(dev));

        flags_ = f_open;

    }



    void close()

    {

        detail::execute_all(

            detail::call_member_close(*this, BOOST_IOS::in),

            detail::call_member_close(*this, BOOST_IOS::out)

        );

    }



    void close(BOOST_IOS::openmode which)

    {

        if (which == BOOST_IOS::in && (flags_ & f_input_closed) == 0) {

            flags_ |= f_input_closed;

            iostreams::close(dev(), BOOST_IOS::in);

        }

        if (which == BOOST_IOS::out && (flags_ & f_output_closed) == 0) {

            flags_ |= f_output_closed;

            detail::execute_all(

                detail::flush_buffer(buf_.second(), dev(), can_write::value),

                detail::call_close(dev(), BOOST_IOS::out),

                detail::call_reset(dev_),

                detail::call_reset(buf_.first()),

                detail::call_reset(buf_.second())

            );

        }

    }



    bool is_open() const { return (flags_ & f_open) != 0;}



    policy_type& dev() { return **dev_; }



    enum flag_type {

        f_open             = 1,

        f_input_closed     = f_open << 1,

        f_output_closed    = f_input_closed << 1

    };



    codecvt_holder<Codecvt>  cvt_;

    storage_type             dev_;

    double_object<

        buffer_type, 

        is_double

    >                        buf_;

    int                      flags_;

};



} // End namespace detail.



//--------------Definition of converter---------------------------------------//



#define BOOST_IOSTREAMS_CONVERTER_PARAMS() , int buffer_size = -1

#define BOOST_IOSTREAMS_CONVERTER_ARGS() , buffer_size



template<typename Device, typename Codecvt, typename Alloc>

struct code_converter_base {

    typedef detail::code_converter_impl<

                Device, Codecvt, Alloc

            > impl_type;

    code_converter_base() : pimpl_(new impl_type) { }

    shared_ptr<impl_type> pimpl_;

};



template< typename Device, 

          typename Codecvt = detail::default_codecvt, 

          typename Alloc = std::allocator<char> >

class code_converter 

    : protected code_converter_base<Device, Codecvt, Alloc>

{

private:

    typedef detail::code_converter_impl<

                Device, Codecvt, Alloc

            >                                                       impl_type;

    typedef typename impl_type::policy_type                         policy_type;

    typedef typename impl_type::buffer_type                         buffer_type;

    typedef typename detail::codecvt_holder<Codecvt>::codecvt_type  codecvt_type;

    typedef typename detail::codecvt_intern<Codecvt>::type          intern_type;

    typedef typename detail::codecvt_extern<Codecvt>::type          extern_type;

    typedef typename detail::codecvt_state<Codecvt>::type           state_type;

public:

    typedef intern_type                                             char_type;    

    struct category 

        : impl_type::mode, device_tag, closable_tag, localizable_tag

        { };

    BOOST_STATIC_ASSERT((

        is_same<

            extern_type, 

            BOOST_DEDUCED_TYPENAME char_type_of<Device>::type

        >::value

    ));

public:

    code_converter() { }

#if BOOST_WORKAROUND(__GNUC__, < 3)

    code_converter(code_converter& rhs) 

        : code_converter_base<Device, Codecvt, Alloc>(rhs)

        { }

    code_converter(const code_converter& rhs) 

        : code_converter_base<Device, Codecvt, Alloc>(rhs)

        { }

#endif

    BOOST_IOSTREAMS_FORWARD( code_converter, open_impl, Device,

                             BOOST_IOSTREAMS_CONVERTER_PARAMS, 

                             BOOST_IOSTREAMS_CONVERTER_ARGS )



        // fstream-like interface.



    bool is_open() const { return this->pimpl_->is_open(); }

    void close(BOOST_IOS::openmode which = BOOST_IOS::in | BOOST_IOS::out )

    { impl().close(which); }



        // Device interface.



    std::streamsize read(char_type*, std::streamsize);

    std::streamsize write(const char_type*, std::streamsize);

    void imbue(const std::locale& loc) { impl().cvt_.imbue(loc); }



        // Direct device access.



    Device& operator*() { return detail::unwrap_direct(dev()); }

    Device* operator->() { return &detail::unwrap_direct(dev()); }

private:

    template<typename T> // Used for forwarding.

    void open_impl(const T& t BOOST_IOSTREAMS_CONVERTER_PARAMS()) 

    { 

        impl().open(t BOOST_IOSTREAMS_CONVERTER_ARGS()); 

    }



    const codecvt_type& cvt() { return impl().cvt_.get(); }

    policy_type& dev() { return impl().dev(); }

    buffer_type& in() { return impl().buf_.first(); }

    buffer_type& out() { return impl().buf_.second(); }

    impl_type& impl() { return *this->pimpl_; }

};



//--------------Implementation of converter-----------------------------------//



// Implementation note: if end of stream contains a partial character,

// it is ignored.

template<typename Device, typename Codevt, typename Alloc>

std::streamsize code_converter<Device, Codevt, Alloc>::read

    (char_type* s, std::streamsize n)

{

    const extern_type*   next;        // Next external char.

    intern_type*         nint;        // Next internal char.

    std::streamsize      total = 0;   // Characters read.

    int                  status = iostreams::char_traits<char>::good();

    bool                 partial = false;

    buffer_type&         buf = in();



    do {



        // Fill buffer.

        if (buf.ptr() == buf.eptr() || partial) {

            status = buf.fill(dev());

            if (buf.ptr() == buf.eptr())

                break;

            partial = false;

        }



        // Convert.

        std::codecvt_base::result result =

            cvt().in( buf.state(),

                      buf.ptr(), buf.eptr(), next,

                      s + total, s + n, nint );

        buf.ptr() += next - buf.ptr();

        total = static_cast<std::streamsize>(nint - s);



        switch (result) {

        case std::codecvt_base::partial:

            partial = true;

            break;

        case std::codecvt_base::ok:

            break;

        case std::codecvt_base::noconv:

            {

                std::streamsize amt = 

                    std::min<std::streamsize>(next - buf.ptr(), n - total);

                detail::strncpy_if_same(s + total, buf.ptr(), amt);

                total += amt;

            }

            break;

        case std::codecvt_base::error:

        default:

            buf.state() = state_type();

            throw code_conversion_error();

        }



    } while (total < n && status != EOF && status != WOULD_BLOCK);



    return total == 0 && status == EOF ? -1 : total;

}



template<typename Device, typename Codevt, typename Alloc>

std::streamsize code_converter<Device, Codevt, Alloc>::write

    (const char_type* s, std::streamsize n)

{

    buffer_type&        buf = out();

    extern_type*        next;              // Next external char.

    const intern_type*  nint;              // Next internal char.

    std::streamsize     total = 0;         // Characters written.

    bool                partial = false;



    while (total < n) {



        // Empty buffer.

        if (buf.eptr() == buf.end() || partial) {

            if (!buf.flush(dev()))

                break;

            partial = false;

        }

       

        // Convert.

        std::codecvt_base::result result =

            cvt().out( buf.state(),

                       s + total, s + n, nint,

                       buf.eptr(), buf.end(), next );

        int progress = (int) (next - buf.eptr());

        buf.eptr() += progress;



        switch (result) {

        case std::codecvt_base::partial:

            partial = true; // Fall through.

        case std::codecvt_base::ok:

            total = static_cast<std::streamsize>(nint - s);

            break;

        case std::codecvt_base::noconv:

            {

                std::streamsize amt = 

                    std::min<std::streamsize>( nint - total - s, 

                                               buf.end() - buf.eptr() );

                detail::strncpy_if_same(buf.eptr(), s + total, amt);

                total += amt;

            }

            break;

        case std::codecvt_base::error:

        default:

            buf.state() = state_type();

            throw code_conversion_error();

        }

    }

    return total;

}



//----------------------------------------------------------------------------//



} } // End namespaces iostreams, boost.



#include <boost/iostreams/detail/config/enable_warnings.hpp> // Borland 5.x



#endif // #ifndef BOOST_IOSTREAMS_CODE_CONVERTER_HPP_INCLUDED

