/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef avro_Buffer_hh__
#define avro_Buffer_hh__

#ifndef _WIN32
#include <sys/uio.h>
#endif
#include <boost/type_traits.hpp>
#include <vector>

#include "../Config.hh"
#include "detail/BufferDetail.hh"
#include "detail/BufferDetailIterator.hh"

/** 
 * \file Buffer.hh 
 *
 * \brief Definitions for InputBuffer and OutputBuffer classes
 *
 **/

namespace avro {

class OutputBuffer;
class InputBuffer;


/** 
 * The OutputBuffer (write-only buffer)
 *
 * Use cases for OutputBuffer 
 *
 * - write message to buffer using ostream class or directly
 * - append messages to headers
 * - building up streams of messages via append
 * - converting to read-only buffers for sending
 * - extracting parts of the messages into read-only buffers
 *   
 * -# ASIO access:
 *     - write to a buffer(s) by asio using iterator
 *     - convert to read buffer for deserializing
 *
 * OutputBuffer is assignable and copy-constructable.  On copy or assignment,
 * only a pointer is copied, so the two resulting copies are identical, so 
 * modifying one will modify both.
 **/

class AVRO_DECL OutputBuffer 
{

  public:

    typedef detail::size_type size_type;
    typedef detail::data_type data_type;

    /** 
     * The asio library expects a const_iterator (the const-ness refers to the
     * fact that the underlying avro of buffers will not be modified, even
     * though the data in those buffers is being modified).  The iterator
     * provides the list of addresses an operation can write to.
     **/

    typedef detail::OutputBufferIterator const_iterator;

    /**
     * Default constructor.  Will pre-allocate at least the requested size, but
     * can grow larger on demand.
     *
     * Destructor uses the default, which resets a shared pointer, deleting the
     * underlying data if no other copies of exist.
     *
     * Copy and assignment operators are not explicitly provided because the
     * default ones work fine.  The default makes only a shallow copy, so the
     * copies will refer to the same memory.  This is required by asio
     * functions, which will implicitly make copies for asynchronous
     * operations.  Therefore, the user must be careful that if they create
     * multiple copies of the same OutputBuffer, only one is being modified
     * otherwise undefined behavior may occur.
     *
     **/

    OutputBuffer(size_type reserveSize = 0) :
        pimpl_(new detail::BufferImpl) 
    { 
        if(reserveSize) { 
            reserve(reserveSize); 
        }
    }

    /** 
     * Reserve enough space for a wroteTo() operation.  When using writeTo(),
     * the buffer will grow dynamically as needed.  But when using the iterator
     * to write (followed by wroteTo()), data may only be written to the space
     * available,  so this ensures there is enough room in the buffer before
     * the write operation.
     **/

    void reserve(size_type reserveSize) 
    {
        pimpl_->reserveFreeSpace(reserveSize);
    }

    /** 
     * Write a block of data to the buffer.  The buffer size will automatically
     * grow if the size is larger than what is currently free.  
     **/

    size_type writeTo(const data_type *data, size_type size) {
        return pimpl_->writeTo(data, size);
    }

    /** 
     * Write a single value to the buffer. The buffer size will automatically
     * grow if there is not room for the byte.  The value must be a
     * "fundamental" type, e.g. int, float, etc.  (otherwise use the other
     * writeTo tests).
     **/

    template<typename T>
    void writeTo(T val) {
        pimpl_->writeTo(val, boost::is_fundamental<T>());
    }

    /** 
     * Update the state of the buffer after writing through the iterator
     * interface.  This function exists primarily for the boost:asio which
     * writes directly to the buffer using its iterator.  In this case, the
     * internal state of the buffer does not reflect that the data was written
     * This informs the buffer how much data was written.  
     *
     * The buffer does not automatically resize in this case, the bytes written
     * cannot exceed the amount of free space.  Attempting to write more will
     * throw a std::length_error exception.
     **/

    size_type wroteTo(size_type size) 
    {
        int wrote = 0;
        if(size) {
            if(size > freeSpace()) {
                throw std::length_error("Impossible to write more data than free space");
            }
            wrote = pimpl_->wroteTo(size);
        }
        return wrote;
    }

    /**
     * Does the buffer have any data? 
     **/

    bool empty() const {
        return  (pimpl_->size()==0);
    }

    /** 
     *  Returns the size of the buffer, in bytes. 
     */

    size_type size() const {
        return  pimpl_->size();
    }

    /** 
     * Returns the current free space that is available to write to in the
     * buffer, in bytes.  This is not a strict limit in size, as writeTo() can
     * automatically increase capacity if necessary.
     **/

    size_type freeSpace() const {
        return  pimpl_->freeSpace();
    }

    /**
     * Appends the data in the argument to the end of this buffer.  The
     * argument can be either an InputBuffer or OutputBuffer.
     *
     **/

    template <class BufferType>
    void append(const BufferType &buf) {
        // don't append an empty buffer
        if(buf.size()) {
            pimpl_->append(*(buf.pimpl_.get()));
        }
    }

    /** 
     * Return an iterator pointing to the first data chunk of this buffer
     * that may be written to. 
     **/

    const_iterator begin() const {
        return const_iterator(pimpl_->beginWrite());
    }

    /** 
     * Return the end iterator for writing. 
     **/

    const_iterator end() const {
        return const_iterator(pimpl_->endWrite());
    }

    /** 
     * Discard any data in this buffer.
     **/

    void discardData()
    {
        pimpl_->discardData();
    }

    /** 
     * Discard the specified number of bytes from this data, starting at the beginning.
     * Throws if the size is greater than the number of bytes.
     **/

    void discardData(size_t bytes)
    {
        if(bytes > 0) {
            if(bytes < pimpl_->size()) {
                pimpl_->discardData(bytes);
            }
            else if(bytes == pimpl_->size()) {
                pimpl_->discardData();
            }
            else {
                throw std::out_of_range("trying to discard more data than exists");
            }
        }
    }

    /** 
     * Remove bytes from this buffer, starting from the beginning, and place
     * them into a new buffer.  Throws if the number of requested bytes exceeds
     * the size of the buffer.  Data and freeSpace in the buffer after bytes
     * remains in this buffer.
     **/

    InputBuffer extractData(size_type bytes);

    /** 
     * Remove all bytes from this buffer, returning them in a new buffer.
     * After removing data, some freeSpace may remain in this buffer.
     **/

    InputBuffer extractData();

    /** 
     * Clone this buffer, creating a copy that contains the same data.
     **/

    OutputBuffer clone() const 
    {
        detail::BufferImpl::SharedPtr newImpl(new detail::BufferImpl(*pimpl_));
        return OutputBuffer(newImpl);
    }

    /** 
     * Add unmanaged data to the buffer.  The buffer will not automatically
     * free the data, but it will call the supplied function when the data is
     * no longer referenced by the buffer (or copies of the buffer).
     **/

    void appendForeignData(const data_type *data, size_type size, const detail::free_func &func) {
        pimpl_->appendForeignData(data, size, func);
    }

    /** 
     * Returns the number of chunks that contain free space.  
     **/

    int numChunks() const {
        return  pimpl_->numFreeChunks();
    }

    /** 
     * Returns the number of chunks that contain data
     **/

    int numDataChunks() const {
        return  pimpl_->numDataChunks();
    }

  private:

    friend class InputBuffer;
    friend class BufferReader;

    explicit OutputBuffer(const detail::BufferImpl::SharedPtr &pimpl) :
        pimpl_(pimpl) 
    { }

    detail::BufferImpl::SharedPtr pimpl_; ///< Must never be null.
};

/** 
 * The InputBuffer (read-only buffer)
 *
 * InputBuffer is an immutable buffer which that may be constructed from an
 * OutputBuffer, or several of OutputBuffer's methods.  Once the data is
 * transfered to an InputBuffer it cannot be modified, only read (via 
 * BufferReader, istream, or its iterator).
 *
 * Assignments and copies are shallow copies.
 *
 * -# ASIO access: - iterate using const_iterator for sending messages
 *
 **/

class AVRO_DECL InputBuffer 
{

  public:

    typedef detail::size_type size_type;
    typedef detail::data_type data_type;

    // needed for asio
    typedef detail::InputBufferIterator const_iterator;

    /** 
     * Default InputBuffer creates an empty buffer.
     *
     * Copy/assignment functions use the default ones.  They will do a shallow
     * copy, and because InputBuffer is immutable, the copies will be
     * identical.
     *
     * Destructor also uses the default, which resets a shared pointer,
     * deleting the underlying data if no other copies of exist.
     **/

    InputBuffer() :
        pimpl_(new detail::BufferImpl)
    { }

    /** 
     * Construct an InputBuffer that contains the contents of an OutputBuffer.
     * The two buffers will have the same contents, but this copy will be
     * immutable, while the the OutputBuffer may still be written to.  
     *
     * If you wish to move the data from the OutputBuffer to a new InputBuffer
     * (leaving only free space in the OutputBuffer),
     * OutputBuffer::extractData() will do this more efficiently.
     *
     * Implicit conversion is allowed.
     **/

    InputBuffer(const OutputBuffer &src) :
        pimpl_(new detail::BufferImpl(*src.pimpl_))
    { }

    /** 
     * Does the buffer have any data? 
     **/

    bool empty() const {
        return (pimpl_->size() == 0);
    }

    /** 
     * Returns the size of the buffer, in bytes. 
     **/

    size_type size() const {
        return pimpl_->size();
    }

    /**
     * Return an iterator pointing to the first data chunk of this buffer
     * that contains data.
     **/

    const_iterator begin() const {
        return const_iterator(pimpl_->beginRead());
    }

    /**
     * Return the end iterator. 
     **/

    const_iterator end() const {
        return const_iterator(pimpl_->endRead());
    }

    /** 
     * Returns the number of chunks containing data.
     **/

    int numChunks() const {
        return pimpl_->numDataChunks();
    }


  private:

    friend class OutputBuffer; // for append function
    friend class istreambuf;
    friend class BufferReader;

    explicit InputBuffer(const detail::BufferImpl::SharedPtr &pimpl) :
        pimpl_(pimpl) 
    { }

    /**
     * Class to indicate that a copy of a OutputBuffer to InputBuffer should be
     * a shallow copy, used to enable reading of the contents of an
     * OutputBuffer without need to convert it to InputBuffer using a deep
     * copy.  It is private and only used by BufferReader and istreambuf
     * classes.
     *
     * Writing to an OutputBuffer while it is being read may lead to undefined
     * behavior.
     **/

    class ShallowCopy {};

    /** 
     * Make a shallow copy of an OutputBuffer in order to read it without 
     * causing conversion overhead.
     **/
    InputBuffer(const OutputBuffer &src, const ShallowCopy &) : 
        pimpl_(src.pimpl_)
    { }

    /** 
     * Make a shallow copy of an InputBuffer.  The default copy constructor
     * already provides shallow copy, this is just provided for generic
     * algorithms that wish to treat InputBuffer and OutputBuffer in the same
     * manner.
     **/

     InputBuffer(const InputBuffer &src, const ShallowCopy &) : 
        pimpl_(src.pimpl_)
    { }


    detail::BufferImpl::ConstSharedPtr pimpl_; ///< Must never be null.
};


/* 
 * Implementations of some OutputBuffer functions are inlined here
 * because InputBuffer definition was required before.
 */

inline InputBuffer OutputBuffer::extractData() 
{
    detail::BufferImpl::SharedPtr newImpl(new detail::BufferImpl);
    if(pimpl_->size()) {
        pimpl_->extractData(*newImpl);
    }
    return InputBuffer(newImpl);
}

inline InputBuffer OutputBuffer::extractData(size_type bytes)
{
    if(bytes > pimpl_->size()) {
        throw std::out_of_range("trying to extract more data than exists");
    }

    detail::BufferImpl::SharedPtr newImpl(new detail::BufferImpl);
    if(bytes > 0) {
        if(bytes < pimpl_->size()) {
            pimpl_->extractData(*newImpl, bytes);
        }
        else {
            pimpl_->extractData(*newImpl);
        }
    }

    return InputBuffer(newImpl);
}

/** 
 * Create an array of iovec structures from the buffer.  This utility is used
 * to support writev and readv function calls.  The caller should ensure the
 * buffer object is not deleted while using the iovec vector.
 *
 * If the BufferType is an InputBuffer, the iovec will point to the data that
 * already exists in the buffer, for reading.
 *
 * If the BufferType is an OutputBuffer, the iovec will point to the free
 * space, which may be written to.  Before writing, the caller should call
 * OutputBuffer::reserve() to create enough room for the desired write (which
 * can be verified by calling OutputBuffer::freeSpace()), and after writing,
 * they MUST call OutputBuffer::wroteTo(), otherwise the buffer will not know
 * the space is not free anymore.
 *
 **/

template<class BufferType>
inline void toIovec(BufferType &buf, std::vector<struct iovec> &iov) 
{
    const int chunks = buf.numChunks();
    iov.resize(chunks);
    typename BufferType::const_iterator iter = buf.begin();
    for (int i = 0; i < chunks; ++i) {
        iov[i].iov_base = const_cast<typename BufferType::data_type *>(iter->data());
        iov[i].iov_len = iter->size();
        ++iter;
    }
}

} // namespace

#endif
