#ifndef OSMIUM_MEMORY_CALLBACK_BUFFER_HPP
#define OSMIUM_MEMORY_CALLBACK_BUFFER_HPP

/*

This file is part of Osmium (https://osmcode.org/libosmium).

Copyright 2013-2020 Jochen Topf <jochen@topf.org> and others (see README).

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

*/

#include <osmium/memory/buffer.hpp>

#include <cstddef>
#include <functional>
#include <utility>

namespace osmium {

    namespace memory {

        /**
         * This is basically a wrapper around osmium::memory::Buffer with an
         * additional callback function that is called whenever the buffer is
         * full.
         *
         * The internal buffer is created with the `initial_buffer_size` set
         * in the constructor. When it grows beyond the `max_buffer_size` set
         * in the constructor, the callback function is called with the buffer
         * and a new, empty buffer is created internally.
         *
         * Note that the buffer can grow beyond the initial buffer size if
         * needed. This can happen if a new object doesn't fit into the rest
         * of the buffer available or if no callback function is set (yet).
         *
         * Example:
         * @code
         *     CallbackBuffer cb;
         *     cb.set_callback([&](osmium::memory::Buffer&& buffer) {
         *         ...handle buffer...
         *     }
         *     osmium::builder::add_node(cb.buffer(), _id(9), ...);
         *     osmium::builder::add_way(cb.buffer(), _id(27), ...);
         * @endcode
         */
        class CallbackBuffer {

        public:

            /// The type for the callback function
            using callback_func_type = std::function<void(osmium::memory::Buffer&&)>;

        private:

            enum {
                default_initial_buffer_size = 1024UL * 1024UL
            };

            enum {
                default_max_buffer_size =  800UL * 1024UL
            };

            osmium::memory::Buffer m_buffer;
            std::size_t m_initial_buffer_size;
            std::size_t m_max_buffer_size;
            callback_func_type m_callback;

        public:

            /**
             * Construct a CallbackBuffer without a callback function. You
             * can later call set the callback with set_callback().
             *
             * @param initial_buffer_size The initial size of newly created
             *                            internal buffers.
             * @param max_buffer_size If the buffer grows beyond this size the
             *                        callback will be called.
             */
            explicit CallbackBuffer(std::size_t initial_buffer_size = default_initial_buffer_size, std::size_t max_buffer_size = default_max_buffer_size) :
                m_buffer(initial_buffer_size, osmium::memory::Buffer::auto_grow::yes),
                m_initial_buffer_size(initial_buffer_size),
                m_max_buffer_size(max_buffer_size),
                m_callback(nullptr) {
            }

            /**
             * Construct a CallbackBuffer with a callback function.
             *
             * @param callback The callback function. Must be of type
             *                 @code void(osmium::memory::Buffer&&) @endcode
             * @param initial_buffer_size The initial size of newly created
             *                            internal buffers.
             * @param max_buffer_size If the buffer grows beyond this size the
             *                        callback will be called.
             */
            explicit CallbackBuffer(callback_func_type callback, std::size_t initial_buffer_size = default_initial_buffer_size, std::size_t max_buffer_size = default_max_buffer_size) :
                m_buffer(initial_buffer_size, osmium::memory::Buffer::auto_grow::yes),
                m_initial_buffer_size(initial_buffer_size),
                m_max_buffer_size(max_buffer_size),
                m_callback(std::move(callback)) {
            }

            /**
             * Access the internal buffer. This is used to fill the buffer,
             * the CallbackBuffer still owns the buffer.
             *
             * Use read() or the callback if you need to own the buffer.
             */
            osmium::memory::Buffer& buffer() noexcept {
                return m_buffer;
            }

            /**
             * Set the callback. The function must take a rvalue reference to
             * a buffer and return void.
             *
             * @param callback The callback function. Must be of type
             *                 @code void(osmium::memory::Buffer&&) @endcode
             */
            void set_callback(const callback_func_type& callback = nullptr) noexcept {
                m_callback = callback;
            }

            /**
             * Flush the internal buffer regardless of how full it is. Calls
             * the callback with the buffer and creates an new empty internal
             * one.
             *
             * This will do nothing if no callback is set or if the buffer
             * is empty.
             */
            void flush() {
                if (m_callback && m_buffer.committed() > 0) {
                    m_callback(read());
                }
            }

            /**
             * Flush the internal buffer if and only if it contains more than
             * the max_buffer_size set in the constructor. Calls the callback
             * with the buffer and creates an new empty internal one.
             *
             * This will do nothing if no callback is set or if the buffer
             * is empty.
             */
            void possibly_flush() {
                if (m_buffer.committed() > m_max_buffer_size) {
                    flush();
                }
            }

            /**
             * Return the internal buffer and create a new empty internal one.
             * You can use this as an alternative access instead of using the
             * callback.
             */
            osmium::memory::Buffer read() {
                osmium::memory::Buffer buffer{m_initial_buffer_size, osmium::memory::Buffer::auto_grow::yes};
                using std::swap;
                swap(buffer, m_buffer);
                return buffer;
            }

        }; // class CallbackBuffer

    } // namespace memory

} // namespace osmium

#endif // OSMIUM_MEMORY_CALLBACK_BUFFER_HPP
