    // Copyright (C) 2012  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_CIRCULAR_BuFFER_Hh_
    #define DLIB_CIRCULAR_BuFFER_Hh_

    #include "circular_buffer_abstract.h"
    #include <vector>
    #include "../algs.h"
    #include "../serialize.h"
    #include "../matrix/matrix_mat.h"

    namespace dlib
    {

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

        template <
            typename T
            >
        class circular_buffer
        {
        public:
            typedef default_memory_manager mem_manager_type;
            typedef T value_type;
            typedef T type;

            circular_buffer()
            {
            }

            explicit circular_buffer(unsigned long s)
            {
                resize(s);
            }

            void clear (
            )
            {
                offset = 0;
                data.clear();
            }

            T& operator[] ( unsigned long i)
            {
                DLIB_ASSERT(i < size(),
                    "\t T& circular_buffer::operator[](i)"
                    << "\n\t You have supplied an invalid index"
                    << "\n\t this:   " << this
                    << "\n\t i:      " << i
                    << "\n\t size(): " << size()
                );
                auto index = i + offset;
                if (index >= data.size())
                    index -= data.size();
                return data[index];
            }

            const T& operator[] ( unsigned long i) const
            {
                DLIB_ASSERT(i < size(),
                    "\t const T& circular_buffer::operator[](i)"
                    << "\n\t You have supplied an invalid index"
                    << "\n\t this:   " << this
                    << "\n\t i:      " << i
                    << "\n\t size(): " << size()
                );
                auto index = i + offset;
                if (index >= data.size())
                    index -= data.size();
                return data[index];
            }

            void resize(unsigned long size)
            {
                offset = 0;
                data.resize(size);
            }

            void assign(
                unsigned long size,
                const T& value
            )
            {
                offset = 0;
                data.assign(size,value);
            }

            unsigned long size() const { return data.size(); }

            void push_front(const T& value)
            {
                if (data.size() != 0)
                {
                    offset = (offset - 1 + data.size())%data.size();
                    data[offset] = value;
                }
            }

            void push_back(const T& value)
            {
                if (data.size() != 0)
                {
                    data[offset] = value;
                    offset = (offset + 1 + data.size())%data.size();
                }
            }

            T& front(
            )
            {
                DLIB_CASSERT(size() > 0,
                    "\t T& circular_buffer::front()"
                    << "\n\t You can't call front() on an empty circular_buffer"
                    << "\n\t this:   " << this
                );
                return (*this)[0];
            }

            const T& front(
            ) const
            {
                DLIB_CASSERT(size() > 0,
                    "\t const T& circular_buffer::front()"
                    << "\n\t You can't call front() on an empty circular_buffer"
                    << "\n\t this:   " << this
                );
                return (*this)[0];
            }

            T& back(
            )
            {
                DLIB_CASSERT(size() > 0,
                    "\t T& circular_buffer::back()"
                    << "\n\t You can't call back() on an empty circular_buffer"
                    << "\n\t this:   " << this
                );
                return (*this)[size()-1];
            }

            const T& back(
            ) const
            {
                DLIB_CASSERT(size() > 0,
                    "\t const T& circular_buffer::back()"
                    << "\n\t You can't call back() on an empty circular_buffer"
                    << "\n\t this:   " << this
                );
                return (*this)[size()-1];
            }

            void swap( circular_buffer& item)
            {
                std::swap(item.offset, offset);
                data.swap(item.data);
            }

        private:
            std::vector<T> data;

            unsigned long offset = 0;
        };

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

        template <
            typename T
            >
        void swap (
            circular_buffer<T>& a,
            circular_buffer<T>& b
        ) { a.swap(b); }

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

        template <
            typename T
            >
        void serialize (
            const circular_buffer<T>& item,
            std::ostream& out
        )
        {
            try
            {
                serialize(item.size(),out);
                for (unsigned long i = 0; i < item.size(); ++i)
                    serialize(item[i],out);
            }
            catch (serialization_error& e)
            {
                throw serialization_error(e.info + "\n   while serializing object of type circular_buffer");
            }

        }

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

        template <
            typename T
            >
        void deserialize (
            circular_buffer<T>& item,
            std::istream& in
        )
        {
            try
            {
                unsigned long size;
                deserialize(size,in);
                item.resize(size);
                for (unsigned long i = 0; i < size; ++i)
                    deserialize(item[i],in);
            }
            catch (serialization_error& e)
            {
                item.clear();
                throw serialization_error(e.info + "\n   while deserializing object of type circular_buffer");
            }
        }

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

        template <
            typename T
            >
        const matrix_op<op_array_to_mat<circular_buffer<T> > > mat (
            const circular_buffer<T>& m
        )
        {
            typedef op_array_to_mat<circular_buffer<T> > op;
            return matrix_op<op>(op(m));
        }

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

    }

    #endif // DLIB_CIRCULAR_BuFFER_Hh_