/** \file
  * IStream adapters.
  */
/*
 * Copyright (C) 2013 Alexander Lamaison <alexander.lamaison@gmail.com>
 *
 * This material is provided "as is", with absolutely no warranty
 * expressed or implied. Any use is at your own risk. Permission to
 * use or copy this software for any purpose is hereby granted without
 * fee, provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is
 * granted, provided the above notices are retained, and a notice that
 * the code was modified is included with the above copyright notice.
 *
 * This header is part of Comet version 2.
 * https://github.com/alamaison/comet
 */

#ifndef COMET_STREAM_H
#define COMET_STREAM_H

#include <comet/config.h>

#include <comet/bstr.h> // bstr_t
#include <comet/error.h> // com_error
#include <comet/handle_except.h> // COMET_CATCH_CLASS_INTERFACE_BOUNDARY
#include <comet/server.h> // simple_object

#include <cassert> // assert
#include <istream>
#include <limits> // numeric_limits
#include <ostream>

#include <strsafe.h> // StringCbCopyW

namespace comet {

namespace impl {

    static const size_t COPY_CHUNK_SIZE = 512;

    /**
     * Used to clear stream failure on exiting a scope.
     *
     * Useful when trying an operation and converting any failure to an
     * exception.  In that case there is no need for the stream object to
     * also retain evidence of the failure.
     *
     * Note, only clears `failbit`.  `eofbit` is left alone as it is a
     * valid consequence of operations performed by the wrapper.  `badbit` is
     * also left as these errors are unrecoverable and therefore the
     * stream is as good as dead; a caller of the unwrapped stream needs
     * to know that.
     */
    template<typename Stream>
    class stream_failure_cleanser
    {
    public:
        explicit stream_failure_cleanser(Stream& stream)
            : m_stream(stream)
        {}

        ~stream_failure_cleanser() throw()
        {
            try
            {
                m_stream.clear(m_stream.rdstate() & ~std::ios_base::failbit);
            }
            catch (const std::exception&)
            {}
        }

    private:
        stream_failure_cleanser(const stream_failure_cleanser&);
        stream_failure_cleanser& operator=(const stream_failure_cleanser&);

        Stream& m_stream;
    };

    // These do_read/write helper functions dispatch the stream-type specific code
    // so that we only need one class to implement all the stream adapters

    inline void do_istream_read(
        std::istream& stream, void* buffer, ULONG buffer_size_in_bytes,
        ULONG& bytes_read_out)
    {
        typedef std::istream stream_type;

        //
        // IMPORTANT: The bytes_read_out count must be  correct even in the
        // error case, EVEN if that error is an exception, because the caller
        // can treat an error as EOF (see Read method docs).
        //
        // However, in the error case it is acceptable to make the read count
        // smaller than the actual number of bytes read (e.g. 0) because the
        // caller is losing the unread part of the stream anyway.  Losing a few
        // extra bytes that are already in the buffer but not declared valid
        // by the byte count will not make the situation worse.

        bytes_read_out = 0U;

        stream.read(
            reinterpret_cast<char*>(buffer),
            buffer_size_in_bytes / sizeof(std::istream::char_type));

        // Any failure not caused by eof is a failure.
        // Badbit is always a failure because, even if we are at eof, the stream
        // encountered a problem that needs reporting.
        if (stream.bad() || (stream.fail() && !stream.eof()))
        {
            throw std::runtime_error("Reading from stream failed");
        }
        else
        {
            if (stream.gcount() >= 0 &&
                // temp conversion to unsigned larger than ULONG to catch
                // values larger than ULONG
                static_cast<ULONGLONG>(stream.gcount()) <
                    (std::numeric_limits<ULONG>::max)() /
                    sizeof(std::istream::char_type))
            {
                bytes_read_out = static_cast<ULONG>(
                    stream.gcount() * sizeof(std::istream::char_type));
            }

            assert(stream.eof() || bytes_read_out == buffer_size_in_bytes);
        }
    }

    inline void do_ostream_write(
        std::ostream& stream, const void* buffer, ULONG buffer_size_in_bytes,
        ULONG& bytes_written_out)
    {
        // If there is an error we cannot get a reliable write count, even
        // if we were to use stream.rdbuf()->sputn, because the failure
        // might well happen during the flush operation which doesn't let
        // us find out how much was flushed before failing.

        typedef std::ostream stream_type;

        bytes_written_out = 0U;

        stream.write(
            reinterpret_cast<const char*>(buffer),
            buffer_size_in_bytes / sizeof(std::ostream::char_type));

        try
        {
            stream.flush();
        }
        catch (const std::exception& e)
        {
            throw com_error(e.what(), STG_E_MEDIUMFULL);
        }

        if (stream.good())
        {
            bytes_written_out = buffer_size_in_bytes;
        }
        else
        {
            throw com_error("Writing to stream failed", STG_E_MEDIUMFULL);
        }
    }

    /** Ensure read position updated even in case of exception */
    template<typename Stream>
    class read_position_finaliser
    {
    public:
        read_position_finaliser(
            Stream& stream, std::streampos& new_position_out)
            : m_stream(stream), m_new_position_out(new_position_out)
        {}

        ~read_position_finaliser() throw()
        {
            try
            {
                // we still want the read position if previous op failed
                m_stream.clear();

                std::streampos new_position = m_stream.tellg();
                if (m_stream)
                {
                    m_new_position_out = new_position;
                }
                else
                {
                    m_new_position_out = std::streampos();
                }
            }
            catch (...)
            {
                // We tried.  Nothing else we can do
                //
                // We likely ended up here in the exception unwinding of a
                // failed seek because a non-seekable stream chose to let us
                // know by throwing an exception from the streambuf
                // (e.g. Boost.IOStream).  Non-seekable is also non-tellable so
                // telgp does the same thing.
                //
                // We have prevent this exception propagating else we get an
                // thrown while unwinding another exception causing terminate()
                m_new_position_out = std::streampos(-1);
            }
        }

    private:

        read_position_finaliser(const read_position_finaliser&);
        read_position_finaliser& operator=(const read_position_finaliser&);

        Stream& m_stream;
        std::streampos& m_new_position_out;
    };

    /** Ensure write position updated even in case of exception */
    template<typename Stream>
    class write_position_finaliser
    {
    public:
        write_position_finaliser(
            Stream& stream, std::streampos& new_position_out)
            : m_stream(stream), m_new_position_out(new_position_out)
        {}

        ~write_position_finaliser() throw()
        {
            try
            {
                // we still want the write position if previous op failed
                m_stream.clear();

                std::streampos new_position = m_stream.tellp();
                if (m_stream)
                {
                    m_new_position_out = new_position;
                }
                else
                {
                    m_new_position_out = std::streampos();
                }
            }
            catch (...)
            {
                // We tried.  Nothing else we can do
                //
                // We likely ended up here in the exception unwinding of a
                // failed seek because a non-seekable stream chose to let us
                // know by throwing an exception from the streambuf
                // (e.g. Boost.IOStream).  Non-seekable is also non-tellable so
                // tellp does the same thing.
                //
                // We have prevent this exception propagating else we get an
                // thrown while unwinding another exception causing terminate()
                m_new_position_out = std::streampos(-1);
            }
        }

    private:
        write_position_finaliser(const write_position_finaliser&);
        write_position_finaliser& operator=(const write_position_finaliser&);

        Stream& m_stream;
        std::streampos& m_new_position_out;
    };

    /**
     * Copies `streampos` out-parameter to ULARGE_INTEGER out parameter
     * in the face of exceptions.
     *
     * Using this class means you can concentrate on keeping the `streampos`
     * variable updated, confident in the knowledge the the COM-interface
     * ULARGE_INTEGER parameter will eventually be updated to match it,
     * no matter what.
     *
     * Assumes that the source out parameter is guaranteed to be valid at
     * all times.  Valid doesn't necessarily mean correct but that it always
     * contains the value we want to set the out parameter to, even if that is
     * wrong (for instance failure while calculating correct position).
     */
    class position_out_converter
    {
    public:

        position_out_converter(
            std::streampos& source_out_parameter,
            ULARGE_INTEGER* destination_out_parameter)
            :
        m_source(source_out_parameter),
        m_destination(destination_out_parameter) {}

        ~position_out_converter()
        {
            if (m_destination)
            {
                try
                {
                    // Convert to streamoff because streampos may not
                    // be an integer.  streamoff is guaranteed to be.
                    std::streamoff offset_from_beginning = m_source;
                    if (offset_from_beginning < 0)
                    {
                        // Invalid offset, for example seeking not supported
                        // The error itself is dealt with right after seeking
                        // so this class just has to convert it to something
                        // reasonable for an unsigned value
                        m_destination->QuadPart = 0U;
                    }
                    else
                    {
                        m_destination->QuadPart = offset_from_beginning;
                    }
                }
                catch(const std::exception&)
                {
                    // Only way this can happen is if streampos refuses to
                    // convert to streamoff in which case we really are screwed.
                    m_destination->QuadPart = 0U;
                }
            }
        }

    private:
        position_out_converter(const position_out_converter&);
        position_out_converter& operator=(const position_out_converter&);

        std::streampos& m_source;
        ULARGE_INTEGER* m_destination;
    };

    /**
     * Increments a total counter with an increment are end of scope,
     * regardless of how that scope is ended.
     *
     * Assumes the increment parameter is valid at all times.
     */
    class byte_count_incrementer
    {
    public:
        byte_count_incrementer(
            ULARGE_INTEGER* total, ULONG& increment)
            : m_total(total), m_increment(increment) {}

        ~byte_count_incrementer()
        {
            if (m_total)
            {
                m_total->QuadPart += m_increment;
            }
        }

    private:

        byte_count_incrementer(const byte_count_incrementer&);
        byte_count_incrementer& operator=(const byte_count_incrementer&);

        ULARGE_INTEGER* m_total;
        ULONG& m_increment;
    };

    template<typename StreamTraits>
    class position_resetter
    {
    public:
        position_resetter(StreamTraits& traits, std::streampos position)
            :
        m_traits(traits), m_position(position) {}

        ~position_resetter()
        {
            try
            {
                std::streampos new_position;
                m_traits.do_seek(
                    m_position, std::ios_base::beg, new_position);

                assert(new_position == m_position);
            }
            catch (const std::exception&)
            {}
        }

    private:
        position_resetter(const position_resetter&);
        position_resetter& operator=(const position_resetter&);

        StreamTraits& m_traits;
        std::streampos m_position;
    };

    /**
     * Calculate offset of end of stream from start.
     */
    template<typename StreamTraits>
    inline std::streamoff stream_size(StreamTraits& traits)
    {
        position_resetter<StreamTraits>(traits, traits.do_tell());

        std::streampos new_position;
        traits.do_seek(0, std::ios_base::end, new_position);

        return new_position;
    }

    template<typename Stream, bool is_istream, bool is_ostream>
    class stream_traits;

    template<typename Stream>
    class stream_traits<Stream, true, false>
    {
    public:
        explicit stream_traits(Stream& stream) : m_stream(stream) {}

        void do_read(
            void* buffer, ULONG buffer_size_in_bytes,
            ULONG& bytes_read_out)
        {
            do_istream_read(
                m_stream, buffer, buffer_size_in_bytes, bytes_read_out);
        }

        void do_write(
            const void* /*buffer*/,
            ULONG /*buffer_size_in_bytes*/, ULONG& bytes_written_out)
        {
            bytes_written_out = 0U;
            throw com_error(
                "std::istream does not support writing", STG_E_ACCESSDENIED);
        }

        void do_seek(
            std::streamoff offset, std::ios_base::seekdir way,
            std::streampos& new_position_out)
        {
            read_position_finaliser<Stream> position_out_updater(
                m_stream, new_position_out);

            if (!m_stream.seekg(offset, way))
            {
                throw std::runtime_error("Unable to change read position");
            }
        }

        std::streampos do_tell()
        {
            std::streampos pos = m_stream.tellg();
            if (!m_stream)
            {
                throw std::runtime_error("Stream position unavailable");
            }

            return pos;
        }

        void do_flush()
        {
            throw com_error(
                "std::istream does not support committing data",
                STG_E_ACCESSDENIED);
        }

    private:
        stream_traits(const stream_traits&);
        stream_traits& operator=(const stream_traits&);

        Stream& m_stream;
    };

    template<typename Stream>
    class stream_traits<Stream, false, true>
    {
    public:
        explicit stream_traits(Stream& stream) : m_stream(stream) {}

        void do_read(
            void* /*buffer*/,
            ULONG /*buffer_size_in_bytes*/, ULONG& bytes_read_out)
        {
            bytes_read_out = 0U;
            throw com_error(
                "std::ostream does not support reading", STG_E_ACCESSDENIED);
        }

        void do_write(
            const void* buffer,
            ULONG buffer_size_in_bytes, ULONG& bytes_written_out)
        {
            do_ostream_write(
                m_stream, buffer, buffer_size_in_bytes, bytes_written_out);
        }

        void do_seek(
            std::streamoff offset, std::ios_base::seekdir way,
            std::streampos& new_position_out)
        {
            write_position_finaliser<Stream> position_out_updater(
                m_stream, new_position_out);

            if (!m_stream.seekp(offset, way))
            {
                throw std::runtime_error("Unable to change write position");
            }
        }

        std::streampos do_tell()
        {
            std::streampos pos = m_stream.tellp();
            if (!m_stream)
            {
                throw std::runtime_error("Stream position unavailable");
            }

            return pos;
        }

        void do_flush()
        {
            if (!m_stream.flush())
            {
                throw std::runtime_error(
                    "Unable to flush buffer to output sequence");
            }
        }

    private:
        stream_traits(const stream_traits&);
        stream_traits& operator=(const stream_traits&);

        Stream& m_stream;
    };

    template<typename Stream>
    class stream_traits<Stream, true, true>
    {
    private:

        enum last_stream_operation
        {
            read,
            write
        };

    public:
        explicit stream_traits(Stream& stream)
            : m_stream(stream), m_last_op(read) {}

        void do_read(
            void* buffer, ULONG buffer_size_in_bytes, ULONG& bytes_read_out)
        {
            bytes_read_out = 0U;

            // sync reading position with writing position, which was the last
            // one used and is allowed to be different in C++ streams but
            // not COM IStreams
            if (m_last_op == write)
            {
                m_stream.seekg(m_stream.tellp());
                // We ignore errors syncing the positions as even iostreams may
                // not be seekable at all

                m_last_op = read;
            }
            assert(m_last_op == read);

            do_istream_read(
                m_stream, buffer, buffer_size_in_bytes, bytes_read_out);
        }

        void do_write(
            const void* buffer, ULONG buffer_size_in_bytes,
            ULONG& bytes_written_out)
        {
            bytes_written_out = 0U;

            // sync writing position with reading position, which was the last
            // one used and is allowed to be different in C++ streams but
            // not COM IStreams
            if (m_last_op == read)
            {
                m_stream.seekp(m_stream.tellg());
                // We ignore errors syncing the positions as even iostreams may
                // not be seekable at all

                m_last_op = write;
            }
            assert(m_last_op == write);

            do_ostream_write(
                m_stream, buffer, buffer_size_in_bytes, bytes_written_out);
        }

        void do_seek(
            std::streamoff offset, std::ios_base::seekdir way,
            std::streampos& new_position_out)
        {

            // Unlike with do_read/do_write, we do not ignore errors when
            // trying to sync the read/write positions as, if the
            // first seek succeeded, we know the stream supports
            // seeking.  Therefore a later error is really an error.

            if (m_last_op == read)
            {
                read_position_finaliser<Stream> position_out_updater(
                    m_stream, new_position_out);

                if (!m_stream.seekg(offset, way))
                {
                    throw std::runtime_error("Unable to change read position");
                }
                else
                {
                    if (!m_stream.seekp(m_stream.tellg()))
                    {
                        throw std::runtime_error(
                            "Unable to synchronise write position");
                    }
                }
            }
            else
            {
                write_position_finaliser<Stream> position_out_updater(
                    m_stream, new_position_out);

                if (!m_stream.seekp(offset, way))
                {
                    throw std::runtime_error("Unable to change write position");
                }
                else
                {
                    if (!m_stream.seekg(m_stream.tellp()))
                    {
                        throw std::runtime_error(
                            "Unable to synchronise read position");
                    }
                }
            }
        }

        std::streampos do_tell()
        {
            std::streampos pos;
            if (m_last_op == read)
            {
                pos = m_stream.tellg();
            }
            else
            {
                pos = m_stream.tellp();
            }

            if (!m_stream)
            {
                throw std::runtime_error("Stream position unavailable");
            }

            return pos;
        }

        void do_flush()
        {
            if (!m_stream.flush())
            {
                throw std::runtime_error(
                    "Unable to flush buffer to output sequence");
            }
        }

    private:
        stream_traits(const stream_traits&);
        stream_traits& operator=(const stream_traits&);

        Stream& m_stream;
        last_stream_operation m_last_op;
    };

    /**
     * Wrap COM IStream interface around C++ IOStream.
     *
     * Unlike C++ streams which may have separate read and write positions that
     * move independently, COM IStreams assume a single combined read/write head.
     * Therefore this wrapper always starts the next read or write operation
     * from the where the last operation finished, regardless of whether that
     * operation was a call to `Read` or `Write`.
     *
     * @note This only applies for as long as the read/write positions are
     *       modified only via this wrapper.  If the positions are modified by
     *       directly on the underlying IOStream, it is undefined whether the
     *       starting point for the next call to `Read`/`Write` is syncronised
     *       with the end of the previous operation.
     *
     * If operations on the inner stream results in failure (the `failbit`
     * is set), this is communicated via the COM-interface return code.  The
     * `failbit` is cleared before the call returns.  This allows further
     * wrapper methods to be called without having the clear the bit directly
     * on the underlying stream.  Fatal errors (`badbit`) and end-of-file
     * (`eofbit`) are left unchanged and remain visible in the underlying
     * stream.
     */
    template<typename Stream>
    class adapted_stream : public simple_object<IStream>
    {
    private:

        typedef impl::stream_traits<
            Stream,
            type_traits::super_sub_class<std::istream, Stream>::result,
            type_traits::super_sub_class<std::ostream, Stream>::result
        > stream_traits_type;

    public:

        adapted_stream(Stream& stream, const bstr_t& optional_name)
            : m_stream(stream), m_traits(stream),
            m_optional_name(optional_name) {}

        /**
         * Fill the given buffer with data read from the wrapped C++ stream.
         *
         * @param [in] buffer
         *     Destination of bytes to be read.
         *
         * @param [in] buffer_size
         *     Size of `buffer` and, therefore, the maximum number of bytes
         *     this method will read.  The method may read fewer than this
         *     number of bytes if the request cannot be fulfilled
         *     (e.g. end-of-file, error), in which case only the portion of
         *     `buffer` up to `*read_count_out` bytes from the start
         *     may be considered valid.
         *
         * @param [out] read_count_out
         *     Number of bytes actually read into `buffer`.  In other words,
         *     the extent of the valid bytes in `buffer` once this method
         *     returns.  This value is correct whether the method returns
         *     success or an error code.
         * 
         * @returns
         *     Error code indicating the success or failure of the method.
         * @retval `S_OK`
         *     If `buffer` was completely filled.
         * @retval `S_FALSE` if EOF reached before `buffer` was filled.
         *     `*read_count_out` gives the number of bytes that were read.
         * @retval a COM error code
         *     If there was a read error.
         *
         * Error behaviour rationale
         * -------------------------
         *
         * MSDN says that, if `read_count_out` < `buffer_size`, callers 
         * should treat both error codes and success codes as meaning
         * the end-of-file was reached.  However, doing so means they will be
         * unable to distinguish EOF from a genuine read error.  Therefore,
         * we make the stronger guarantee that only `S_FALSE` means EOF
         * while an error code always indicates a read error.
         * To accommodate callers who follow the documentation on MSDN,
         * we ensure that the read-count out-variable is correct even if the
         * wrapped stream encountered an error (`failbit`, `badbit`) or
         * threw an exception, because the caller may use the byte-count to
         * decide which bytes of the partial read are valid.
         *
         * In the error case, `*read_count_out` may be less than the number
         * of bytes actually read from the wrapped stream.  This is for
         * performance reasons: unless we read from the wrapped stream one
         * byte at a time, there is no way to calculate the the exact number
         * of bytes read if an exception is thrown part way through reading.
         * Byte-by-byte reading is hopelessly slow for some applications, such
         * as an unbuffered stream over a network, so our behaviour is a
         * pragmatic compromise. We believe it is within the (vaguely)
         * documented behaviour of `IStream` to treat the out-count in this way
         * for error cases as the count still delimits a valid region of bytes.
         */
        virtual HRESULT STDMETHODCALLTYPE Read( 
            void* buffer, ULONG buffer_size, ULONG* read_count_out)
        {
            impl::stream_failure_cleanser<Stream> state_resetter(m_stream);

            if (read_count_out)
            {
                *read_count_out = 0U;
            }

            try
            {
                if (!buffer)
                {
                    throw com_error("No buffer given", STG_E_INVALIDPOINTER);
                }

                // We use a dummy read count location if one is not given so
                // that do_read can keep it updated correctly even if
                // do_read throws an exception.
                ULONG dummy_read_count = 0U;
                if (!read_count_out)
                {
                    read_count_out = &dummy_read_count;
                }

                m_traits.do_read(buffer, buffer_size, *read_count_out);

                if (*read_count_out < buffer_size)
                {
                    assert(m_stream.eof());

                    return S_FALSE;
                }
                else
                {
                    assert(*read_count_out == buffer_size);
                    assert(m_stream.good());
                    return S_OK;
                }
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("Read", "adapted_stream");
        }

        /**
         * Write the given data to the wrapped C++ stream's controlled sequence.
         *
         * The wrapped stream is flushed before this method returns to ensure
         * the data is written to the controlled sequence (and any associated
         * errors are detected) rather than just to the stream's buffer.
         * Therefore, best performance is obtained if this method is called with
         * as much data as possible for the fewest number of flushes.
         *
         * @param [in] data
         *     Bytes to write to the controlled sequence.
         *
         * @param [in] data_size
         *     Size of `data` and the number of bytes to write.
         *
         * @param [out] written_count_out
         *     Number of bytes guaranteed to be written to the controlled
         *     sequence.  This will be `data_size` if writing succeeded.  If
         *     an error occurred, this may be fewer than the actual number of
         *     bytes written to the sequence.
         * 
         * @returns
         *     Error code indicating the success or failure of the method.
         * @retval `S_OK`
         *     If `data` was completely written to the wrapped stream's
         *     controlled sequence.
         * @retval a COM error code
         *     If there was a write error.
         *
         * Error behaviour rationale
         * -------------------------
         *
         * If writing succeeds, `*written_count_out` equals `data_size`.  If
         * writing encountered an error `*written_count_out` will be set to zero,
         * even if some bytes were written out.  We do this for performance
         * reasons: unless we write the data one byte at a time to the wrapped
         * stream _and flush the stream after each byte_, the C++ streams don't
         * provide a way to count the exact number of bytes written to the
         * stream's controlled sequence.  Byte-by-byte writing is hopelessly
         * slow for some applications, such as a stream over a network, so our
         * behaviour is a pragmatic compromise.
         * We believe it is within the (vaguely) documented behaviour of
         * `IStream` to treat the out-count in this way for error cases.
         * It may seem strange that this method even has an out-count if it
         * was no intended to be meaningful in the error case (the count would
         * be equal to `data_size` in the success case), however we believe
         * it was only intended to be meaningful for non-blocking writes to
         * asynchronous stream (`E_PENDING` would be returned) which this
         * wrapper is not (see http://bit.ly/1hZGy1L).
         */
        virtual HRESULT STDMETHODCALLTYPE Write(
            const void* data, ULONG data_size, ULONG* written_count_out)
        {
            stream_failure_cleanser<Stream> state_resetter(m_stream);

            if (written_count_out)
            {
                *written_count_out = 0U;
            }

            try
            {
                if (!data)
                {
                    throw com_error("Buffer not given", STG_E_INVALIDPOINTER);
                }

                // We use a dummy written count out location if one is not
                // given so that do_write can keep it updated correctly even if
                // do_write throws an exception.
                ULONG dummy_written_count = 0U;
                if (!written_count_out)
                {
                    written_count_out = &dummy_written_count;
                }

                m_traits.do_write(data, data_size, *written_count_out);

                if (*written_count_out < data_size)
                {
                    throw com_error(
                        "Unable to complete write operation", STG_E_MEDIUMFULL);
                }
                else
                {
                    assert(*written_count_out == data_size);
                    return S_OK;
                }
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("Write", "adapted_stream");
        }

        virtual HRESULT STDMETHODCALLTYPE Seek(
            LARGE_INTEGER offset, DWORD origin,
            ULARGE_INTEGER* new_position_out)
        {
            stream_failure_cleanser<Stream> state_resetter(m_stream);

            std::streampos new_stream_position_out = std::streampos();
            impl::position_out_converter out_param_guard(
                new_stream_position_out, new_position_out);

            try
            {
                std::ios_base::seekdir way;
                if (origin == STREAM_SEEK_CUR)
                {
                    way = std::ios_base::cur;
                }
                else if (origin == STREAM_SEEK_SET)
                {
                    way = std::ios_base::beg;
                }
                else if (origin == STREAM_SEEK_END)
                {
                    way = std::ios_base::end;
                }
                else
                {
                    throw com_error(
                        "Unrecognised stream seek origin",
                        STG_E_INVALIDFUNCTION);
                }

                if (offset.QuadPart > 
                    (std::numeric_limits<std::streamoff>::max)())
                {
                    throw com_error(
                        "Seek offset too large", STG_E_INVALIDFUNCTION);
                }
                else if (offset.QuadPart <
                    (std::numeric_limits<std::streamoff>::min)())
                {
                    throw com_error(
                        "Seek offset too small", STG_E_INVALIDFUNCTION);
                }
                else
                {
                    try
                    {
                        m_traits.do_seek(
                            static_cast<std::streamoff>(offset.QuadPart),
                            way, new_stream_position_out);
                    }
                    // Translate logic_errors (and subtypes) as they
                    // correspond (very roughly) to the kinds of errors
                    // for which IStream::Seek is documented to return
                    // STG_E_INVALIDFUNCTION
                    catch(const std::logic_error& e)
                    {
                        throw com_error(e.what(), STG_E_INVALIDFUNCTION);
                    }

                    return S_OK;
                }
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("Seek", "adapted_stream");
        }

        /**
         * Expand stream to given size.
         *
         * The will only increase a stream's size if it is smaller than the
         * given size.  If the stream size is already equal or bigger, it
         * remains unchanged.
         *
         * Not all streams support changing size.  In particular, `istream`s
         * do not support this method.
         */
        virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER new_size)
        {
            try
            {
                stream_failure_cleanser<Stream> state_resetter(m_stream);

                position_resetter<stream_traits_type> resetter(
                    m_traits, m_traits.do_tell());

                if (new_size.QuadPart > 0)
                {
                    ULARGE_INTEGER new_end_position;
                    new_end_position.QuadPart = new_size.QuadPart - 1;

                    std::streamoff new_offset;
                    if ((std::numeric_limits<std::streamoff>::max)() < 0)
                    {
                        assert(!"Purely negative number!");
                        throw com_error(
                            "Seek offset too large", STG_E_INVALIDFUNCTION);
                    }
                    else if (new_end_position.QuadPart > 
                        static_cast<ULONGLONG>(
                            (std::numeric_limits<std::streamoff>::max)()))
                    {
                        throw com_error(
                            "Seek offset too large", STG_E_INVALIDFUNCTION);
                    }
                    else
                    {
                        new_offset = static_cast<std::streamoff>(
                            new_end_position.QuadPart);
                    }

                    std::streamoff existing_end = stream_size(m_traits);

                    if (new_offset > existing_end)
                    {
                        std::streampos new_position;
                        m_traits.do_seek(
                            new_offset, std::ios_base::beg, new_position);

                        assert(std::streamoff(new_position) == new_offset);

                        // Force the stream to expand by writing NUL at
                        // new extent
                        ULONG bytes_written;
                        m_traits.do_write("\0", 1, bytes_written);
                        assert(bytes_written == 1);
                    }
                }

                return S_OK;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("SetSize", "adapted_stream");
        }

        virtual HRESULT STDMETHODCALLTYPE CopyTo( 
            IStream* destination, ULARGE_INTEGER amount,
            ULARGE_INTEGER* bytes_read_out, ULARGE_INTEGER* bytes_written_out)
        {
            stream_failure_cleanser<Stream> state_resetter(m_stream);

            ULARGE_INTEGER dummy_read_out;
            if (!bytes_read_out)
            {
                bytes_read_out = &dummy_read_out;
            }
            bytes_read_out->QuadPart = 0U;

            ULARGE_INTEGER dummy_written_out;
            if (!bytes_written_out)
            {
                bytes_written_out = &dummy_written_out;
            }
            bytes_written_out->QuadPart = 0U;

            try
            {

                if (!destination)
                {
                    throw com_error(
                        "Destination stream not given", STG_E_INVALIDPOINTER);
                }

                std::vector<unsigned char> buffer(COPY_CHUNK_SIZE);

                // Perform copy operation in chunks COPY_CHUNK bytes big
                // The chunk must be less than the biggest ULONG in size
                // because of the limits of the Read/Write API.  Of course
                // it will be in any case as it would be insane to use more
                // memory than that, but we make sure anyway using the first
                // min comparison
                do {
                    ULONG next_chunk_size =
                        static_cast<ULONG>(
                            min(
                                (std::numeric_limits<ULONG>::max)(),
                                min(
                                    amount.QuadPart - bytes_read_out->QuadPart,
                                    buffer.size())));

                    ULONG read_this_round = 0U;
                    ULONG written_this_round = 0U;

                    // These two take care of updating the total on each pass
                    // round the loop as well as on termination, exception or
                    // natural.
                    //
                    // The means the out counts are valid even in the failure
                    // case. MSDN says they don't have to be but, as we can,
                    // we might as well
                    impl::byte_count_incrementer read_incrementer(
                        bytes_read_out, read_this_round);

                    impl::byte_count_incrementer write_incrementer(
                        bytes_written_out, written_this_round);

                    m_traits.do_read(
                        &buffer[0], next_chunk_size, read_this_round);
                    HRESULT hr = destination->Write(
                        &buffer[0], read_this_round, &written_this_round);
                    if (FAILED(hr))
                    {
                        com_error_from_interface(destination, hr);
                    }

                    if (read_this_round < next_chunk_size)
                    {
                        // EOF
                        break;
                    }

                } while (amount.QuadPart > bytes_read_out->QuadPart);

                return S_OK;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("CopyTo", "adapted_stream");
        }

        /**
         * Flush data in the buffer to the controlled output sequence.
         *
         * This implementation doesn't support transactions so ignores
         * the commit flags.
         *
         * Fails if called with an istream.
         */
        virtual HRESULT STDMETHODCALLTYPE Commit(DWORD /*commit_flags*/)
        {
            stream_failure_cleanser<Stream> state_resetter(m_stream);

            try
            {
                m_traits.do_flush();
                return S_OK;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("Commit", "adapted_stream");
        }

        virtual HRESULT STDMETHODCALLTYPE Revert()
        {
            try
            {
                throw com_error("Transactions not supported", E_NOTIMPL);
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("Revert", "adapted_stream");
        }

        virtual HRESULT STDMETHODCALLTYPE LockRegion(
            ULARGE_INTEGER /*offset*/, ULARGE_INTEGER /*extent*/,
            DWORD /*lock_type*/)
        {
            try
            {
                throw com_error(
                    "Locking not supported", STG_E_INVALIDFUNCTION);
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("LockRegion", "adapted_stream");
        }

        virtual HRESULT STDMETHODCALLTYPE UnlockRegion(
            ULARGE_INTEGER /*offset*/, ULARGE_INTEGER /*extent*/,
            DWORD /*lock_type*/)
        {
            try
            {
                throw com_error(
                    "Locking not supported", STG_E_INVALIDFUNCTION);
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("UnlockRegion", "adapted_stream");
        }

        /**
         * Get some metadata about the stream.
         *
         * The name returned (if requested) is the name optionally given
         * in the constructor.  If not given in the constructor, an
         * empty string is returned.
         *
         * Some fields, such as the date fields, are not valid as that data
         * is not available for IOStreams.
         */
        virtual HRESULT STDMETHODCALLTYPE Stat( 
            STATSTG* attributes_out, DWORD stat_flag)
        {
            try
            {
                if (!attributes_out)
                {
                    throw com_error("STATSTG not given", STG_E_INVALIDPOINTER);
                }

                *attributes_out = STATSTG();

                attributes_out->type = STGTY_STREAM;
                
                try
                {
                    attributes_out->cbSize.QuadPart =
                        impl::stream_size(m_traits);
                }
                catch (const std::exception&)
                {
                    // Swallow non-bad errors as many stream are not seekable and
                    // therefore not sizeable
                    if (m_stream.bad())
                        throw;
                }

                // Must be last as, after we detach, any failure will leak
                // memory
                if (!(stat_flag & STATFLAG_NONAME))
                {
                    // pwcsName is NOT a BSTR.  It's a null-terminated OLESTR
                    // managed by the COM memory allocator

                    size_t buffer_size = m_optional_name.size() + 1;
                    size_t buffer_size_in_bytes =
                        buffer_size * sizeof(wchar_t);
                    
                    attributes_out->pwcsName = static_cast<LPOLESTR>(
                        ::CoTaskMemAlloc(buffer_size_in_bytes));
                    if (!attributes_out->pwcsName)
                    {
                        throw com_error(
                            "Unable to allocate memory for stream name",
                            STG_E_INSUFFICIENTMEMORY);
                    }

                    try
                    {
                        HRESULT hr = ::StringCbCopyW(
                            attributes_out->pwcsName, buffer_size_in_bytes,
                            m_optional_name.c_str());
                        if (FAILED(hr))
                        {
                            throw com_error(
                                "Unable to copy stream name to STATSTG", hr);
                        }
                    }
                    catch(...)
                    {
                        ::CoTaskMemFree(attributes_out->pwcsName);
                        throw;
                    }
                }

                return S_OK;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("Stat", "adapted_stream");
        }

        /**
         * Cloning not supported for IOStreams as they are not copyable.
         */
        virtual HRESULT STDMETHODCALLTYPE Clone(IStream** stream_out)
        {
            if (stream_out)
            {
                *stream_out = NULL;
            }

            try
            {
                throw com_error(
                    "Cloning not supported", STG_E_INVALIDFUNCTION);
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY("Clone", "adapted_stream");
        }

    private:

        Stream& m_stream;
        stream_traits_type m_traits;
        bstr_t m_optional_name;
    };

    template<typename StreamPtr>
    class adapted_stream_pointer : public simple_object<IStream>
    {
    public:

        adapted_stream_pointer(StreamPtr stream, const bstr_t& optional_name)
            : m_stream(stream), m_inner(adapt_stream(*m_stream, optional_name))
        {}

        // The forwarded methods must return their HRESULT by throwing and
        // catching in order to propagate the IErrorInfo upwards.

        virtual HRESULT STDMETHODCALLTYPE Read( 
            void* buffer, ULONG buffer_size, ULONG* read_count_out)
        {
            if (read_count_out)
            {
                *read_count_out = 0U;
            }

            try
            {
                HRESULT hr = m_inner->Read(buffer, buffer_size, read_count_out);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);
                
                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "Read", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE Write(
            const void* buffer, ULONG buffer_size, ULONG* written_count_out)
        {
            if (written_count_out)
            {
                *written_count_out = 0U;
            }

            try
            {
                HRESULT hr = m_inner->Write(
                    buffer, buffer_size, written_count_out);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);

                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "Write", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE Seek(
            LARGE_INTEGER offset, DWORD origin,
            ULARGE_INTEGER* new_position_out)
        {
            if (new_position_out)
            {
                new_position_out->QuadPart = 0U;
            }

            try
            {
                HRESULT hr = m_inner->Seek(offset, origin, new_position_out);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);
                
                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "Seek", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER new_size)
        {
            try
            {
                HRESULT hr = m_inner->SetSize(new_size);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);

                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "SetSize", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE CopyTo( 
            IStream* destination, ULARGE_INTEGER amount,
            ULARGE_INTEGER* bytes_read_out, ULARGE_INTEGER* bytes_written_out)
        {
            if (bytes_read_out)
            {
                bytes_read_out->QuadPart = 0U;
            }

            if (bytes_written_out)
            {
                bytes_written_out->QuadPart = 0U;
            }

            try
            {
                HRESULT hr = m_inner->CopyTo(
                    destination, amount, bytes_read_out, bytes_written_out);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);

                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "CopyTo", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE Commit(DWORD commit_flags)
        {
            try
            {
                HRESULT hr = m_inner->Commit(commit_flags);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);

                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "Commit", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE Revert()
        {
            try
            {
                HRESULT hr = m_inner->Revert();
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);

                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "Revert", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE LockRegion(
            ULARGE_INTEGER offset, ULARGE_INTEGER extent, DWORD lock_type)
        {
            try
            {
                HRESULT hr = m_inner->LockRegion(offset, extent, lock_type);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);

                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "LockRegion", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE UnlockRegion(
            ULARGE_INTEGER offset, ULARGE_INTEGER extent, DWORD lock_type)
        {
            try
            {
                HRESULT hr = m_inner->UnlockRegion(offset, extent, lock_type);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);

                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "UnlockRegion", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE Stat( 
            STATSTG* attributes_out, DWORD stat_flag)
        {
            if (attributes_out)
            {
                *attributes_out = STATSTG();
            }

            try
            {
                HRESULT hr = m_inner->Stat(attributes_out, stat_flag);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);

                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "Stat", "adapted_stream_pointer");
        }

        virtual HRESULT STDMETHODCALLTYPE Clone(IStream** stream_out)
        {
            if (stream_out)
            {
                *stream_out = NULL;
            }

            try
            {
                HRESULT hr = m_inner->Clone(stream_out);
                if (FAILED(hr))
                    throw com_error_from_interface(m_inner, hr);

                return hr;
            }
            COMET_CATCH_CLASS_INTERFACE_BOUNDARY(
                "Clone", "adapted_stream_pointer");
        }

    private:

        StreamPtr m_stream;
        com_ptr<IStream> m_inner;
    };

}

/**
 * Wrap COM IStream interface around C++ IOStream.
 *
 * The caller must ensure that the C++ IOStream remains valid until the
 * last reference to the returned wrapper is released.
 *
 * Unlike C++ streams which may have separate read and write positions that
 * move independently, COM IStreams assume a single combined read/write head.
 * Therefore this wrapper always starts the next read or write operation
 * from the where the last operation finished, regardless of whether that
 * operation was a call to `Read` or `Write`.
 *
 * @note This only applies for as long as the read/write positions are
 *       modified only via this wrapper.  If the positions are modified by
 *       directly on the underlying IOStream, it is undefined whether the
 *       starting point for the next call to `Read`/`Write` is syncronised
 *       with the end of the previous operation.
 *
 * If operations on the inner stream results in failure (the `failbit`
 * is set), this is communicated via the COM-interface return code.  The
 * `failbit` is cleared before the call returns.  This allows further
 * wrapper methods to be called without having the clear the bit directly
 * on the underlying stream.  Fatal errors (`badbit`) and end-of-file
 * (`eofbit`) are left unchanged and remain visible in the underlying
 * stream.
 */
template<typename Stream>
inline com_ptr<IStream> adapt_stream(
    Stream& stream, const bstr_t& optional_name=bstr_t())
{
    return new impl::adapted_stream<Stream>(stream, optional_name);
}

/**
 * Wrap COM IStream interface around pointer (usually smart) to C++ IOStream.
 *
 * If the pointer type is a smart pointer, the caller need not must ensure
 * the lifetime of the C++ IOStream exceeds that of the adapter; the smart
 * pointer takes care of ensuring this.
 *
 * The main reason for having this function in addition to `adapt_stream` is
 * support the common case where a COM stream is intended to be the sole owner
 * of the C++ stream with which it is created.  This allows the caller to
 * create the IStream and forget about the C++ stream.  Using `adapt_stream`
 * they would have to manage the lifetime of both.
 *
 * When we support movable types (C++11 or Boost.Move emulation), this method
 * would become unnecessary for this purpose as the C++ stream could simply
 * be moved into the adapter
 */
template<typename StreamPtr>
inline com_ptr<IStream> adapt_stream_pointer(
    StreamPtr stream_pointer, const bstr_t& optional_name=bstr_t())
{
    return new impl::adapted_stream_pointer<StreamPtr>(
        stream_pointer, optional_name);
}

}

#endif