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

#include "queue_kernel_abstract.h"
#include "../algs.h"
#include "../assert.h"
#include "../interfaces/enumerable.h"
#include "../interfaces/remover.h"
#include "../serialize.h"
#include "../memory_manager.h"

namespace dlib
{

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager = memory_manager<char>::kernel_1a
        >
    class queue_kernel_2 : public enumerable<T>,
                           public remover<T>
    {

        /*!
            REQUIREMENTS ON block_size
                0 < block_size < 2000000000

            INITIAL VALUE
                queue_size == 0   
                current_element == 0
                at_start_ == true
            
            CONVENTION
                queue_size == the number of elements in the queue
                at_start() == at_start_
                current_element_valid() == (current_element != 0)
                if (current_element_valid()) then
                    element() == current_element->item[current_element_pos]

                if (queue_size > 0)                
                {                    
                    in->item[in_pos] == the spot where we will put the next item added
                                        into the queue
                    out->item[out_pos] == current()

                    when enqueuing elements inside each node item[0] is filled first, then 
                    item[1], then item[2], etc...
                                                         

                    each node points to the node inserted after it except for the most 
                    recently inserted node.  
                }
                
        !*/


        struct node
        {
            node* next;

            T item[block_size];
        };

        
        public:

            typedef T type;
            typedef mem_manager mem_manager_type;

            queue_kernel_2 (
            ) :
                in(0),
                out(0),
                queue_size(0),
                current_element(0),
                at_start_(true)
            {
            }

            virtual ~queue_kernel_2 (
            ); 

            inline void clear(
            );

            void enqueue (
                T& item
            );

            void dequeue (
                T& item
            );

            void cat (
                queue_kernel_2& item
            );

            T& current (
            );

            const T& current (
            ) const;            

            void swap (
                queue_kernel_2& item
            );

            // functions from the remover interface
            inline void remove_any (
                T& item
            );

            // functions from the enumerable interface
            inline unsigned long size (
            ) const;

            inline bool at_start (
            ) const;

            inline void reset (
            ) const;

            bool current_element_valid (
            ) const;

            inline const T& element (
            ) const;

            inline T& element (
            );

            bool move_next (
            ) const;

        private:

            void delete_nodes (
                node* start,
                node* end
            );
            /*!
                requires
                    - start points to a node in a singly linked list 
                    - start->next points to the next node in the list 
                    - by following the next pointers you eventually hit the node pointed
                      to by end
                ensures
                    - calls delete on the start node, the end node, and all nodes in between
            !*/

            // data members

            typename mem_manager::template rebind<node>::other pool; 

            node* in;
            node* out;
            unsigned long queue_size;
            unsigned long in_pos;
            unsigned long out_pos;


            mutable node* current_element;
            mutable unsigned long current_element_pos;
            mutable bool at_start_;

            // restricted functions
            queue_kernel_2(queue_kernel_2&);        // copy constructor
            queue_kernel_2& operator=(queue_kernel_2&);    // assignment operator

    };

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    inline void swap (
        queue_kernel_2<T,block_size,mem_manager>& a, 
        queue_kernel_2<T,block_size,mem_manager>& b 
    ) { a.swap(b); } 

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

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    queue_kernel_2<T,block_size,mem_manager>::
    ~queue_kernel_2 (
    )
    {
        COMPILE_TIME_ASSERT(0 < block_size && block_size < (unsigned long)(2000000000));

        if (queue_size > 0)
            delete_nodes(out,in);
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    void queue_kernel_2<T,block_size,mem_manager>::
    clear (
    )
    {
        if (queue_size > 0)
        {
            delete_nodes(out,in);
            queue_size = 0;
        }

        // put the enumerator at the start
        reset();
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    void queue_kernel_2<T,block_size,mem_manager>::
    enqueue (
        T& item
    )
    {
        if (queue_size == 0)
        {
            out = in = pool.allocate();
            in_pos = 0;
            out_pos = 0;
        }
        else if (in_pos >= block_size)
        {            
            in->next = pool.allocate();
            in_pos = 0;
            in = in->next;
        }

        exchange(item,in->item[in_pos]);
        ++in_pos;

        ++queue_size;

        // put the enumerator at the start
        reset();
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    void queue_kernel_2<T,block_size,mem_manager>::
    dequeue (
        T& item
    )
    {
        // swap out into item
        exchange(item,out->item[out_pos]);
        
        ++out_pos;
        --queue_size;

        // if this was the last element in this node then remove this node
        if (out_pos == block_size)
        {
            out_pos = 0;
            node* temp = out;
            out = out->next;
            pool.deallocate(temp);
        }
        else if (queue_size == 0)
        {
            pool.deallocate(out);
        }

        // put the enumerator at the start
        reset();
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    void queue_kernel_2<T,block_size,mem_manager>::
    cat (
        queue_kernel_2<T,block_size,mem_manager>& item
    )
    {
        if (queue_size > 0)
        {
            T temp;
            assign_zero_if_built_in_scalar_type(temp);
            while (item.size() > 0)
            {
                item.dequeue(temp);
                enqueue(temp);
            }
        }
        else
        {
            in = item.in;
            out = item.out;
            out_pos = item.out_pos;
            in_pos = item.in_pos;

            queue_size = item.queue_size;
            item.queue_size = 0;

            // put the enumerator at the start
            reset();
        }       
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    T& queue_kernel_2<T,block_size,mem_manager>::
    current (
    )
    {
        return out->item[out_pos];
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    const T& queue_kernel_2<T,block_size,mem_manager>::
    current (
    ) const
    {
        return out->item[out_pos];
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    void queue_kernel_2<T,block_size,mem_manager>::
    swap (
        queue_kernel_2<T,block_size,mem_manager>& item
    )
    {
        exchange(in,item.in);
        exchange(out,item.out);
        exchange(queue_size,item.queue_size);
        exchange(in_pos,item.in_pos);
        exchange(out_pos,item.out_pos);
        exchange(current_element,item.current_element);
        exchange(current_element_pos,item.current_element_pos);
        exchange(at_start_,item.at_start_);        
        pool.swap(item.pool);
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // enumerable function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    unsigned long queue_kernel_2<T,block_size,mem_manager>::
    size (
    ) const
    {
        return queue_size;
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    bool queue_kernel_2<T,block_size,mem_manager>::
    at_start (
    ) const
    {
        return at_start_;
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    void queue_kernel_2<T,block_size,mem_manager>::
    reset (
    ) const
    {
        at_start_ = true;
        current_element = 0;
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    bool queue_kernel_2<T,block_size,mem_manager>::
    current_element_valid (
    ) const
    {
        return (current_element != 0);
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    const T& queue_kernel_2<T,block_size,mem_manager>::
    element (
    ) const
    {
        return current_element->item[current_element_pos];
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    T& queue_kernel_2<T,block_size,mem_manager>::
    element (
    )
    {
        return current_element->item[current_element_pos];
    }

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

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    bool queue_kernel_2<T,block_size,mem_manager>::
    move_next (
    ) const
    {
        if (at_start_)
        {
            at_start_ = false;
            // if the queue is empty then there is nothing to do
            if (queue_size == 0)
            {
                return false;
            }
            else
            {
                current_element = out;
                current_element_pos = out_pos;
                return true;
            }
        }
        else if (current_element == 0)
        {
            return false;
        }
        else
        {
            ++current_element_pos;
            // if we are at the last element then the enumeration has finished
            if (current_element == in && current_element_pos == in_pos )
            {
                current_element = 0;
                return false;
            }
            else if (current_element_pos == block_size)
            {
                current_element_pos = 0;
                current_element = current_element->next;               
            }           

            return true;
        }
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // remover function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    void queue_kernel_2<T,block_size,mem_manager>::
    remove_any (
        T& item
    ) 
    {
        dequeue(item);
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // private member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    template <
        typename T,
        unsigned long block_size,
        typename mem_manager
        >
    void queue_kernel_2<T,block_size,mem_manager>::
    delete_nodes (
        node* start,
        node* end
    )
    {
        node* temp;
        while (start != end)
        {
            temp = start;
            start = start->next;
            pool.deallocate(temp);
        }
        pool.deallocate(start);
    }

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

}

#endif // DLIB_QUEUE_KERNEl_2_

