// 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_

