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

#include "sequence_kernel_abstract.h"
#include "../algs.h"
#include "../interfaces/enumerable.h"
#include "../interfaces/remover.h"
#include "../serialize.h"

namespace dlib
{


    template <
        typename T,
        typename mem_manager = default_memory_manager
        >
    class sequence_kernel_2 : public enumerable<T>,
                              public remover<T>
    {
        /*!
            INITIAL VALUE
                sequence_size   == 0 
                at_start_       == true
                current_enumeration_node == 0

            CONVENTION
                sequence_size == the number of elements in the sequence

                at_start_ == at_start()
                (current_enumeration_node!=0) == current_element_valid()
                if (current_enumeration_node!=0) then
                    current_enumeration_node->item == element()
                    current_enumeration_pos == the position of the node pointed to by
                                               current_enumeration_node

                if ( sequence_size > 0 )
                {
                    current_node        == pointer to a node in the linked list and
                    current_node->right->right->... eventually == current_node and
                    current_node->left->left->... eventually == current_node and
                    current_pos         == the position in the sequence of 
                                           current_node->item
                }

        !*/

        struct node {
            T item;
            node* right;
            node* left;
        };
        
        public:

            typedef T type;
            typedef mem_manager mem_manager_type;

            sequence_kernel_2 (
            ) :
                sequence_size(0),
                at_start_(true),
                current_enumeration_node(0)
            {}

            virtual ~sequence_kernel_2 (
            ); 

            inline void clear (
            );

            void add (
                unsigned long pos,
                T& item
            );

            void remove (
                unsigned long pos,
                T& item
            );

            void cat (
                sequence_kernel_2& item
            );

            const T& operator[] (
                unsigned long pos
            ) const;
            
            T& operator[] (
                unsigned long pos
            );

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

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

            bool at_start (
            ) const;

            inline void reset (
            ) const;

            bool current_element_valid (
            ) const;

            const T& element (
            ) const;

            T& element (
            );

            bool move_next (
            ) const;

        private:

            void delete_nodes (
                node* current_node,
                unsigned long sequence_size
            );
            /*!
                requires
                    CONVENTION IS CORRECT
                ensures
                    all memory associated with the ring of nodes has been freed
            !*/

            void move_to_pos (
                node*& current_node,
                unsigned long& current_pos,
                unsigned long pos,
                unsigned long size
            ) const;
            /*!
                requires
                    everything in the CONVENTION is correct and
                    there is a node corresponding to pos in the CONVENTION and
                    0 <= pos < size
                ensures
                    current_pos == pos and
                    current_node->item is the item in the sequence associated with 
                    position pos
            !*/

            // data members
            unsigned long sequence_size;
            mutable node* current_node;
            mutable unsigned long current_pos;
            mutable bool at_start_;
            mutable node* current_enumeration_node;
            mutable unsigned long current_enumeration_pos;

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

    };


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

    template <
        typename T,
        typename mem_manager
        >
    void deserialize (
        sequence_kernel_2<T,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.add(i,temp);
            }
        }
        catch (serialization_error& e)
        { 
            item.clear();
            throw serialization_error(e.info + "\n   while deserializing object of type sequence_kernel_2"); 
        }
    }

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

    template <
        typename T,
        typename mem_manager
        >
    sequence_kernel_2<T,mem_manager>::
    ~sequence_kernel_2 (
    )
    {
        delete_nodes(current_node,sequence_size);
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void sequence_kernel_2<T,mem_manager>::
    clear (
    )
    {
        if (sequence_size != 0)
        {
            delete_nodes(current_node,sequence_size);
            sequence_size = 0;
        }
        // reset the enumerator
        reset();
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void sequence_kernel_2<T,mem_manager>::
    add (
        unsigned long pos,
        T& item
    )
    {
        // make new node and swap item into it
        node* new_node = new node;
        exchange(item,new_node->item);

        if (sequence_size > 0)
        {
            if (pos == sequence_size)
            {
                move_to_pos(current_node,current_pos,pos-1,sequence_size);
                
                node& n_node = *new_node;
                node& c_node = *current_node;

                // make new node point to the nodes to its left and right
                n_node.right = c_node.right;
                n_node.left  = current_node;

                // make the left node point back to new_node
                c_node.right->left = new_node;

                // make the right node point back to new_node
                c_node.right = new_node;
                current_pos = pos;

            }
            else
            {
                move_to_pos(current_node,current_pos,pos,sequence_size);

                node& n_node = *new_node;
                node& c_node = *current_node;

                // make new node point to the nodes to its left and right
                n_node.right = current_node;
                n_node.left  = c_node.left;

                // make the left node point back to new_node
                c_node.left->right = new_node;

                // make the right node point back to new_node
                c_node.left = new_node;
            }
            
        }
        else
        {
            current_pos = 0;
            new_node->left = new_node;
            new_node->right = new_node;
        }

        // make the new node the current node
        current_node = new_node;    
    
        ++sequence_size;
        // reset the enumerator
        reset();
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void sequence_kernel_2<T,mem_manager>::
    remove (
        unsigned long pos,
        T& item
    )
    {
        move_to_pos(current_node,current_pos,pos,sequence_size);
        node& c_node = *current_node;
        exchange(c_node.item,item);
        
        node* temp = current_node;
        
        // close up gap left by remove
        c_node.left->right = c_node.right;
        c_node.right->left = c_node.left;

        current_node = c_node.right;

        --sequence_size;

        delete temp;
        // reset the enumerator
        reset();
    }

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

    template <
        typename T,
        typename mem_manager
        >
    const T& sequence_kernel_2<T,mem_manager>::
    operator[] (
        unsigned long pos
    ) const
    {
        move_to_pos(current_node,current_pos,pos,sequence_size);
        return current_node->item;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void sequence_kernel_2<T,mem_manager>::
    cat (
        sequence_kernel_2<T,mem_manager>& item
    )
    {
        if (item.sequence_size > 0)
        {
            if (sequence_size > 0)
            {
                // move both sequences to a convenient location
                move_to_pos(current_node,current_pos,0,sequence_size);
                item.move_to_pos (
                    item.current_node,
                    item.current_pos,
                    item.sequence_size-1,
                    item.sequence_size
                );

                // make copies of poitners
                node& item_right = *item.current_node->right;
                node& left = *current_node->left;


                item.current_node->right = current_node;
                current_node->left = item.current_node;

                left.right = &item_right;
                item_right.left = &left;

                // set sizes
                sequence_size += item.sequence_size;
                item.sequence_size = 0;
            }
            else
            {
                // *this is empty so just swap
                item.swap(*this);                
            }
        }
        item.clear();
        // reset the enumerator
        reset();
    }

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

    template <
        typename T,
        typename mem_manager
        >
    T& sequence_kernel_2<T,mem_manager>::
    operator[] (
        unsigned long pos
    ) 
    {
        move_to_pos(current_node,current_pos,pos,sequence_size);
        return current_node->item;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    size_t sequence_kernel_2<T,mem_manager>::
    size (
    ) const
    {
        return sequence_size;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void sequence_kernel_2<T,mem_manager>::
    swap (
        sequence_kernel_2<T,mem_manager>& item
    )
    {
        unsigned long   sequence_size_temp         = item.sequence_size;
        node*           current_node_temp          = item.current_node;
        unsigned long   current_pos_temp           = item.current_pos;
        bool            at_start_temp              = item.at_start_;
        node*           current_enumeration_node_temp = item.current_enumeration_node;
        unsigned long   current_enumeration_pos_temp = item.current_enumeration_pos;

        item.sequence_size  = sequence_size;
        item.current_node   = current_node;
        item.current_pos    = current_pos;
        item.at_start_      = at_start_;
        item.current_enumeration_node = current_enumeration_node;
        item.current_enumeration_pos = current_enumeration_pos;

        sequence_size   = sequence_size_temp;
        current_node    = current_node_temp;
        current_pos     = current_pos_temp;
        at_start_       = at_start_temp;
        current_enumeration_node = current_enumeration_node_temp;
        current_enumeration_pos = current_enumeration_pos_temp;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    bool sequence_kernel_2<T,mem_manager>::
    at_start (
    ) const
    {
        return at_start_;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void sequence_kernel_2<T,mem_manager>::
    reset (
    ) const
    {
        at_start_ = true;
        current_enumeration_node = 0;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    bool sequence_kernel_2<T,mem_manager>::
    current_element_valid (
    ) const
    {
        return (current_enumeration_node!=0);
    }

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

    template <
        typename T,
        typename mem_manager
        >
    const T& sequence_kernel_2<T,mem_manager>::
    element (
    ) const
    {
        return current_enumeration_node->item;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    T& sequence_kernel_2<T,mem_manager>::
    element (
    )
    {
        return current_enumeration_node->item;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    bool sequence_kernel_2<T,mem_manager>::
    move_next (
    ) const
    {
        if (at_start_ && sequence_size>0)
        {            
            move_to_pos(current_node,current_pos,0,sequence_size);            
            current_enumeration_node = current_node;
            current_enumeration_pos = 0;
        }
        else if (current_enumeration_node!=0)
        {
            ++current_enumeration_pos;
            if (current_enumeration_pos<sequence_size)
            {                
                current_enumeration_node = current_enumeration_node->right;
            }
            else
            {
                // we have reached the end of the sequence
                current_enumeration_node = 0;
            }
        }

        at_start_ = false;
        return (current_enumeration_node!=0);
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void sequence_kernel_2<T,mem_manager>::
    remove_any (
        T& item
    ) 
    {
        remove(0,item);
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void sequence_kernel_2<T,mem_manager>::
    delete_nodes (
        node* current_node,
        unsigned long sequence_size
    )
    {
        node* temp;
        while (sequence_size)
        {
            temp = current_node->right;
            delete current_node;
            current_node = temp;
            --sequence_size;
        }
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void sequence_kernel_2<T,mem_manager>::
    move_to_pos (
        node*& current_node,
        unsigned long& current_pos,
        unsigned long pos,
        unsigned long size
    ) const
    {
        if ( current_pos > pos)
        {
            // number of hops in each direction needed to reach pos
            unsigned long right = size + pos - current_pos;
            unsigned long left = current_pos - pos;
            current_pos = pos;

            if (left < right)
            {
                // move left to position pos
                for (; left > 0; --left)
                    current_node = current_node->left;
            }
            else
            {
                // move left to position pos
                for (; right > 0; --right)
                    current_node = current_node->right;
            }
        }
        else if (current_pos != pos)
        {
            // number of hops in each direction needed to reach pos
            unsigned long right = pos - current_pos;
            unsigned long left = size - pos + current_pos;
            current_pos = pos;

            if (left < right)
            {
                // move left to position pos
                for (; left > 0; --left)
                    current_node = current_node->left;
            }
            else
            {
                // move left to position pos
                for (; right > 0; --right)
                    current_node = current_node->right;
            }
        }
    }

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

}

#endif // DLIB_SEQUENCE_KERNEl_2_

