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

#include "binary_search_tree_kernel_abstract.h"
#include "../algs.h"
#include "../interfaces/map_pair.h"
#include "../interfaces/enumerable.h"
#include "../interfaces/remover.h"
#include "../serialize.h"
#include <cstdlib>
#include <functional>

namespace dlib 
{

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare = std::less<domain>
        >
    class binary_search_tree_kernel_1 : public enumerable<map_pair<domain,range> >,
                                        public asc_pair_remover<domain,range,compare>
    {

        /*!
            INITIAL VALUE
                tree_size == 0
                tree_root == 0
                tree_height == 0
                at_start_ == true
                current_element == 0
                stack == array of 50 node pointers
                stack_pos == 0


            CONVENTION
                tree_size   == size()
                tree_height == height()

                stack[stack_pos-1] == pop()

                current_element_valid() == (current_element != 0)
                if (current_element_valid()) then
                    element() == current_element->d and current_element->r
                at_start_ == at_start()
                if (current_element != 0 && current_element != tree_root) then
                    stack[stack_pos-1] == the parent of the node pointed to by current_element

                if (tree_size != 0)
                    tree_root == pointer to the root node of the binary search tree
                else
                    tree_root == 0


                for all nodes:
                {
                    left points to the left subtree or 0 if there is no left subtree and
                    right points to the right subtree or 0 if there is no right subtree and
                    all elements in a left subtree are <= the root and
                    all elements in a right subtree are >= the root and
                    d is the item in the domain of *this contained in the node
                    r is the item in the range of *this contained in the node
                    balance:
                        balance == 0 if both subtrees have the same height
                        balance == -1 if the left subtree has a height that is greater 
                                   than the height of the right subtree by 1
                        balance == 1 if the right subtree has a height that is greater 
                                   than the height of the left subtree by 1
                    for all trees:
                        the height of the left and right subtrees differ by at most one
                }

        !*/
    
        class node
        {
        public:
            node* left;
            node* right;
            domain d;
            range r;
            signed char balance;
        };

        class mpair : public map_pair<domain,range>
        {
        public:
            const domain* d;
            range* r;

            const domain& key( 
            ) const { return *d; }

            const range& value(
            ) const { return *r; }

            range& value(
            ) { return *r; }
        };

   
        public:

            typedef domain domain_type;
            typedef range range_type;
            typedef compare compare_type;
            typedef mem_manager mem_manager_type;

            binary_search_tree_kernel_1(
            ) :
                tree_size(0),
                tree_root(0),
                current_element(0),
                tree_height(0),
                at_start_(true),
                stack_pos(0),
                stack(ppool.allocate_array(50))
            {
            }

            virtual ~binary_search_tree_kernel_1(
            ); 
    
            inline void clear(
            );

            inline short height (
            ) const;

            inline unsigned long count (
                const domain& item
            ) const;

            inline void add (
                domain& d,
                range& r
            );

            void remove (
                const domain& d,
                domain& d_copy,
                range& r
            );

            void destroy (
                const domain& item
            );

            inline const range* operator[] (
                const domain& item
            ) const;

            inline range* operator[] (
                const domain& item
            );

            inline void swap (
                binary_search_tree_kernel_1& item
            );

            // function from the asc_pair_remover interface
            void remove_any (
                domain& d,
                range& r
            );

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

            bool at_start (
            ) const;

            inline void reset (
            ) const;

            bool current_element_valid (
            ) const;

            const map_pair<domain,range>& element (
            ) const;

            map_pair<domain,range>& element (
            );

            bool move_next (
            ) const;

            void remove_last_in_order (
                domain& d,
                range& r
            );

            void remove_current_element (
                domain& d,
                range& r
            );

            void position_enumerator (
                const domain& d
            ) const;

        private:


            inline void rotate_left (
                node*& t
            );
            /*!
                requires
                    - t->balance == 2 
                    - t->right->balance == 0 or 1 
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - #t is still a binary search tree 
                    - #t->balance is between 1 and -1 
                    - #t now has a height smaller by 1 if #t->balance == 0
            !*/

            inline void rotate_right (
                node*& t
            );
            /*!
                requires
                    - t->balance == -2 
                    - t->left->balance == 0 or -1 
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - #t is still a binary search tree 
                    - #t->balance is between 1 and -1 
                    - #t now has a height smaller by 1 if #t->balance == 0

            !*/

            inline void double_rotate_right (
                node*& t
            );
            /*!
                requires
                    - t->balance == -2 
                    - t->left->balance == 1 
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - #t is still a binary search tree 
                    - #t now has a balance of 0 
                    - #t now has a height smaller by 1             
            !*/

            inline void double_rotate_left (
                node*& t
            );
            /*!
                requires
                    - t->balance == 2 
                    - t->right->balance == -1 
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - #t is still a binary search tree 
                    - #t now has a balance of 0 
                    - #t now has a height smaller by 1
            !*/

            bool remove_biggest_element_in_tree (
                node*& t,
                domain& d,
                range& r
            );
            /*!
                requires
                    - t != 0  (i.e. there must be something in the tree to remove) 
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - the biggest node in t has been removed 
                    - the biggest node domain element in t has been put into #d 
                    - the biggest node range element in t has been put into #r
                    - #t is still a binary search tree 
                    - returns false if the height of the tree has not changed 
                    - returns true if the height of the tree has shrunk by one
            !*/

            bool remove_least_element_in_tree (
                node*& t,
                domain& d,
                range& r
            );
            /*!
                requires
                    - t != 0  (i.e. there must be something in the tree to remove) 
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - the least node in t has been removed 
                    - the least node domain element in t has been put into #d 
                    - the least node range element in t has been put into #r
                    - #t is still a binary search tree 
                    - returns false if the height of the tree has not changed 
                    - returns true if the height of the tree has shrunk by one
            !*/

            bool add_to_tree (
                node*& t,
                domain& d,
                range& r
            );
            /*!
                requires
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - the mapping (d --> r) has been added to #t 
                    - #d and #r have initial values for their types
                    - #t is still a binary search tree 
                    - returns false if the height of the tree has not changed 
                    - returns true if the height of the tree has grown by one
            !*/

            bool remove_from_tree (
                node*& t,
                const domain& d,
                domain& d_copy,
                range& r
            );
            /*!
                requires
                    - return_reference(t,d) != 0
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - #d_copy is equivalent to d                                    
                    - an element in t equivalent to d has been removed and swapped 
                      into #d_copy and its associated range object has been 
                      swapped into #r
                    - #t is still a binary search tree                                     
                    - returns false if the height of the tree has not changed 
                    - returns true if the height of the tree has shrunk by one
            !*/

            bool remove_from_tree (
                node*& t,
                const domain& item
            );
            /*!
                requires
                    - return_reference(t,item) != 0
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - an element in t equivalent to item has been removed                      
                    - #t is still a binary search tree                                     
                    - returns false if the height of the tree has not changed 
                    - returns true if the height of the tree has shrunk by one
            !*/

            const range* return_reference (
                const node* t,
                const domain& d
            ) const;
            /*!
                ensures
                    - if (there is a domain element equivalent to d in t) then
                        - returns a pointer to the element in the range equivalent to d
                    - else
                        - returns 0
            !*/

            range* return_reference (
                node* t,
                const domain& d
            );
            /*!
                ensures
                    - if (there is a domain element equivalent to d in t) then
                        - returns a pointer to the element in the range equivalent to d
                    - else
                        - returns 0
            !*/


            inline bool keep_node_balanced (
                node*& t
            );
            /*!
                requires
                    - t != 0 
                    - t == reference to the pointer in t's parent node that points to t
                ensures
                    - if (t->balance is < 1 or > 1) then 
                        - keep_node_balanced() will ensure that #t->balance == 0, -1, or 1
                    - #t is still a binary search tree
                    - returns true if it made the tree one height shorter 
                    - returns false if it didn't change the height
            !*/


            unsigned long get_count (
                const domain& item,
                node* tree_root
            ) const;
            /*!
                requires
                    - tree_root == the root of a binary search tree or 0
                ensures
                    - if (tree_root == 0) then
                        - returns 0
                    - else
                        - returns the number of elements in tree_root that are 
                          equivalent to item
            !*/


            void delete_tree (
                node* t
            );
            /*!
                requires
                    - t != 0
                ensures
                    - deallocates the node pointed to by t and all of t's left and right children
            !*/


            void push (
                node* n
            ) const { stack[stack_pos] = n; ++stack_pos; }
            /*!
                ensures
                    - pushes n onto the stack
            !*/
            

            node* pop (
            ) const { --stack_pos; return stack[stack_pos]; }
            /*!
                ensures
                    - pops the top of the stack and returns it
            !*/



            bool fix_stack (
                node* t,
                unsigned char depth = 0
            );
            /*!
                requires
                    - current_element != 0
                    - depth == 0
                    - t == tree_root
                ensures
                    - makes the stack contain the correct set of parent pointers.
                      also adjusts stack_pos so it is correct.
                    - #t is still a binary search tree                                     
            !*/

            bool remove_current_element_from_tree (
                node*& t,
                domain& d,
                range& r,
                unsigned long cur_stack_pos = 1
            ); 
            /*!
                requires
                    - t == tree_root
                    - cur_stack_pos == 1
                    - current_element != 0
                ensures
                    - removes the data in the node given by current_element and swaps it into 
                      #d and #r.  
                    - #t is still a binary search tree                                     
                    - the enumerator is advances on to the next element but its stack is 
                      potentially corrupted.  so you must call fix_stack(tree_root) to fix
                      it.
                    - returns false if the height of the tree has not changed 
                    - returns true if the height of the tree has shrunk by one
            !*/


            // data members

            mutable mpair p;
            unsigned long tree_size;            
            node* tree_root;
            mutable node* current_element;            
            typename mem_manager::template rebind<node>::other pool;
            typename mem_manager::template rebind<node*>::other ppool;
            short tree_height;
            mutable bool at_start_;
            mutable unsigned char stack_pos;
            mutable node** stack;
            compare comp; 

            // restricted functions
            binary_search_tree_kernel_1(binary_search_tree_kernel_1&);        
            binary_search_tree_kernel_1& operator=(binary_search_tree_kernel_1&); 


    };

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    inline void swap (
        binary_search_tree_kernel_1<domain,range,mem_manager,compare>& a, 
        binary_search_tree_kernel_1<domain,range,mem_manager,compare>& b 
    ) { a.swap(b); }




    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void deserialize (
        binary_search_tree_kernel_1<domain,range,mem_manager,compare>& item, 
        std::istream& in
    )
    {
        try
        {
            item.clear();
            unsigned long size;
            deserialize(size,in);
            domain d;
            range r;
            for (unsigned long i = 0; i < size; ++i)
            {
                deserialize(d,in);
                deserialize(r,in);
                item.add(d,r);
            }
        }
        catch (serialization_error e)
        { 
            item.clear();
            throw serialization_error(e.info + "\n   while deserializing object of type binary_search_tree_kernel_1"); 
        }
    }



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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    ~binary_search_tree_kernel_1 (
    )
    {
        ppool.deallocate_array(stack);
        if (tree_size != 0)
        {
            delete_tree(tree_root);
        }        
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    clear (
    )
    {
        if (tree_size > 0)
        {
            delete_tree(tree_root);
            tree_root = 0;
            tree_size = 0;
            tree_height = 0;
        }
        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    size_t binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    size (
    ) const
    {
        return tree_size;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    short binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    height (
    ) const
    {
        return tree_height;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    unsigned long binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    count (
        const domain& item
    ) const
    {
        return get_count(item,tree_root);        
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    add (
        domain& d,
        range& r
    ) 
    {
        tree_height += add_to_tree(tree_root,d,r);
        ++tree_size;
        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    remove (
        const domain& d,
        domain& d_copy,
        range& r
    ) 
    {
        tree_height -= remove_from_tree(tree_root,d,d_copy,r);
        --tree_size;
        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    destroy (
        const domain& item
    ) 
    {
        tree_height -= remove_from_tree(tree_root,item);
        --tree_size;
        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    remove_any (
        domain& d,
        range& r
    ) 
    {
        tree_height -= remove_least_element_in_tree(tree_root,d,r);
        --tree_size;
        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    range* binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    operator[] (
        const domain& item
    ) 
    {
        return return_reference(tree_root,item);
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    const range* binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    operator[] (
        const domain& item
    ) const
    {
        return return_reference(tree_root,item);
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    swap (
        binary_search_tree_kernel_1<domain,range,mem_manager,compare>& item
    ) 
    {
        pool.swap(item.pool);
        ppool.swap(item.ppool);
        exchange(p,item.p);
        exchange(stack,item.stack);
        exchange(stack_pos,item.stack_pos);
        exchange(comp,item.comp);
        

        node* tree_root_temp            = item.tree_root;
        unsigned long tree_size_temp    = item.tree_size;
        short tree_height_temp          = item.tree_height;
        node* current_element_temp      = item.current_element;
        bool at_start_temp              = item.at_start_;

        item.tree_root   = tree_root;
        item.tree_size   = tree_size;
        item.tree_height = tree_height;
        item.current_element = current_element;
        item.at_start_   = at_start_;

        tree_root   = tree_root_temp;
        tree_size   = tree_size_temp;
        tree_height = tree_height_temp;
        current_element = current_element_temp;
        at_start_   = at_start_temp;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    remove_last_in_order (
        domain& d,
        range& r
    )
    {
        tree_height -= remove_biggest_element_in_tree(tree_root,d,r);
        --tree_size;
        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    remove_current_element (
        domain& d,
        range& r
    )
    {
        tree_height -= remove_current_element_from_tree(tree_root,d,r);
        --tree_size;

        // fix the enumerator stack if we need to
        if (current_element)
            fix_stack(tree_root);
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    position_enumerator (
        const domain& d
    ) const
    {
        // clear the enumerator state and make sure the stack is empty
        reset();
        at_start_ = false;
        node* t = tree_root;
        bool went_left = false;
        while (t != 0)
        {
            if ( comp(d , t->d) )
            {
                push(t);
                // if item is on the left then look in left
                t = t->left;
                went_left = true;
            }
            else if (comp(t->d , d))
            {
                push(t);
                // if item is on the right then look in right
                t = t->right;
                went_left = false;
            }
            else
            {
                current_element = t;
                return;
            }
        }

        // if we didn't find any matches but there might be something after the
        // d in this tree.
        if (stack_pos > 0)
        {
            current_element = pop();
            // if we went left from this node then this node is the next
            // biggest.
            if (went_left)
            {
                return;
            }
            else
            {
                move_next();
            }
        }
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    at_start (
    ) const
    {
        return at_start_;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    reset (
    ) const
    {
        at_start_ = true;
        current_element = 0;
        stack_pos = 0;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    current_element_valid (
    ) const
    {
        return (current_element != 0);
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    const map_pair<domain,range>& binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    element (
    ) const
    {
        p.d = &(current_element->d);
        p.r = &(current_element->r);
        return p;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    map_pair<domain,range>& binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    element (
    )
    {
        p.d = &(current_element->d);
        p.r = &(current_element->r);
        return p;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    move_next (
    ) const
    {
        // if we haven't started iterating yet
        if (at_start_)
        {
            at_start_ = false;
            if (tree_size == 0)
            {
                return false;
            }
            else
            {                    
                // find the first element in the tree
                current_element = tree_root;
                node* temp = current_element->left;
                while (temp != 0)
                {
                    push(current_element);
                    current_element = temp;
                    temp = current_element->left;
                }
                return true;
            }
        }
        else
        {
            if (current_element == 0)
            {
                return false;
            }
            else
            {
                node* temp;
                bool went_up;  // true if we went up the tree from a child node to parent
                bool from_left = false; // true if we went up and were coming from a left child node
                // find the next element in the tree
                if (current_element->right != 0)
                {
                    // go right and down    
                    temp = current_element;
                    push(current_element);
                    current_element = temp->right;
                    went_up = false;
                }
                else
                {
                    // go up to the parent if we can
                    if (current_element == tree_root)
                    {
                        // in this case we have iterated over all the element of the tree
                        current_element = 0;
                        return false;
                    }
                    went_up = true;
                    node* parent = pop();


                    from_left = (parent->left == current_element);
                    // go up to parent
                    current_element = parent;
                }


                while (true)
                {
                    if (went_up)
                    {
                        if (from_left)
                        {
                            // in this case we have found the next node
                            break;
                        }
                        else
                        {
                            if (current_element == tree_root)
                            {
                                // in this case we have iterated over all the elements
                                // in the tree
                                current_element = 0;
                                return false;
                            }
                            // we should go up
                            node* parent = pop();
                            from_left = (parent->left == current_element);                            
                            current_element = parent;
                        }
                    }
                    else
                    {
                        // we just went down to a child node
                        if (current_element->left != 0)
                        {
                            // go left
                            went_up = false;
                            temp = current_element;
                            push(current_element);
                            current_element = temp->left;
                        }
                        else
                        {
                            // if there is no left child then we have found the next node
                            break;
                        }
                    }
                }

                return true;               
            }
        }

    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    delete_tree (
        node* t
    ) 
    {
        if (t->left != 0)
            delete_tree(t->left);
        if (t->right != 0)
            delete_tree(t->right);
        pool.deallocate(t);
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    rotate_left (
        node*& t
    ) 
    {

        // set the new balance numbers
        if (t->right->balance == 1)
        {
            t->balance = 0;
            t->right->balance = 0;
        }
        else
        {
            t->balance = 1;
            t->right->balance = -1;            
        }

        // perform the rotation
        node* temp = t->right;
        t->right = temp->left;
        temp->left = t;
        t = temp;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    rotate_right (
        node*& t
    ) 
    {
        // set the new balance numbers
        if (t->left->balance == -1)
        {
            t->balance = 0;
            t->left->balance = 0;
        }
        else
        {
            t->balance = -1;
            t->left->balance = 1;            
        }

        // preform the rotation
        node* temp = t->left;
        t->left = temp->right;
        temp->right = t;
        t = temp;    

    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    double_rotate_right (
        node*& t
    )
    {

        node* temp = t;
        t = t->left->right;
        
        temp->left->right = t->left;
        t->left = temp->left;

        temp->left = t->right;
        t->right = temp;

        if (t->balance < 0)
        {  
            t->left->balance = 0;
            t->right->balance = 1;
        }
        else if (t->balance > 0)
        {
            t->left->balance = -1;
            t->right->balance = 0;
        }
        else 
        {
            t->left->balance = 0;
            t->right->balance = 0;
        }
        t->balance = 0;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    void binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    double_rotate_left (
        node*& t
    )
    {
        node* temp = t;
        t = t->right->left;
        
        temp->right->left = t->right;
        t->right = temp->right;

        temp->right = t->left;
        t->left = temp;

        if (t->balance < 0)
        {  
            t->left->balance = 0;
            t->right->balance = 1;
        }
        else if (t->balance > 0)
        {
            t->left->balance = -1;
            t->right->balance = 0;
        }
        else 
        {
            t->left->balance = 0;
            t->right->balance = 0;
        }

        t->balance = 0;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    remove_biggest_element_in_tree (
        node*& t,
        domain& d,
        range& r
    ) 
    {
        // make a reference to the current node so we don't have to dereference a 
        // pointer a bunch of times
        node& tree = *t;

        // if the right tree is an empty tree
        if ( tree.right == 0)
        {
            // swap nodes domain and range elements into d and r
            exchange(d,tree.d);
            exchange(r,tree.r);

            // plug hole left by removing this node
            t = tree.left;

            // delete the node that was just removed
            pool.deallocate(&tree);    

            // return that the height of this part of the tree has decreased
            return true;
        }
        else
        {

            // keep going right

            // if remove made the tree one height shorter
            if ( remove_biggest_element_in_tree(tree.right,d,r) ) 
            {
                // if this caused the current tree to strink then report that
                if ( tree.balance == 1)
                {
                    --tree.balance;
                    return true;
                }
                else
                {
                    --tree.balance;
                    return keep_node_balanced(t);
                }                
            }

            return false;            
        }
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    remove_least_element_in_tree (
        node*& t,
        domain& d,
        range& r
    ) 
    {
        // make a reference to the current node so we don't have to dereference a 
        // pointer a bunch of times
        node& tree = *t;

        // if the left tree is an empty tree
        if ( tree.left == 0)
        {
            // swap nodes domain and range elements into d and r
            exchange(d,tree.d);
            exchange(r,tree.r);

            // plug hole left by removing this node
            t = tree.right;

            // delete the node that was just removed
            pool.deallocate(&tree);    

            // return that the height of this part of the tree has decreased
            return true;
        }
        else
        {

            // keep going left

            // if remove made the tree one height shorter
            if ( remove_least_element_in_tree(tree.left,d,r) ) 
            {
                // if this caused the current tree to strink then report that
                if ( tree.balance == -1)
                {
                    ++tree.balance;
                    return true;
                }
                else
                {
                    ++tree.balance;
                    return keep_node_balanced(t);
                }                
            }

            return false;            
        }
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    add_to_tree (
        node*& t,
        domain& d,
        range& r
    ) 
    {

        // if found place to add
        if (t == 0)
        {
            // create a node to add new item into
            t = pool.allocate(); 

            // make a reference to the current node so we don't have to dereference a 
            // pointer a bunch of times
            node& tree = *t;


            // set left and right pointers to NULL to indicate that there are no 
            // left or right subtrees
            tree.left = 0;
            tree.right = 0;
            tree.balance = 0;

            // put d and r into t
            exchange(tree.d,d);
            exchange(tree.r,r);

            // indicate that the height of this tree has increased
            return true;
        }
        else  // keep looking for a place to add the new item
        {
            // make a reference to the current node so we don't have to dereference 
            // a pointer a bunch of times
            node& tree = *t;
            signed char old_balance = tree.balance;

            // add the new item to whatever subtree it should go into
            if (comp( d , tree.d) )
                tree.balance -= add_to_tree(tree.left,d,r);
            else
                tree.balance += add_to_tree(tree.right,d,r);


            // if the tree was balanced to start with
            if (old_balance == 0)
            {
                // if its not balanced anymore then it grew in height
                if (tree.balance != 0)
                    return true;
                else
                    return false;
            }
            else
            {
                // if the tree is now balanced then it didn't grow
                if (tree.balance == 0)
                {
                    return false;
                }
                else
                {
                    // if the tree needs to be balanced
                    if (tree.balance != old_balance)
                    {
                        return !keep_node_balanced(t);
                    }
                    // if there has been no change in the heights
                    else
                    {
                        return false;
                    }
                }
            }
        }
    } 

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    fix_stack (
        node* t,
        unsigned char depth 
    ) 
    {
        // if we found the node we were looking for
        if (t == current_element)
        {
            stack_pos = depth;
            return true;
        }
        else if (t == 0)
        {
            return false;
        }

        if (!( comp(t->d , current_element->d)))
        {
            // go left
            if (fix_stack(t->left,depth+1))
            {
                stack[depth] = t;
                return true;
            }            
        }
        if (!(comp(current_element->d , t->d)))
        {
            // go right
            if (fix_stack(t->right,depth+1))
            {
                stack[depth] = t;
                return true;
            }            
        }
        return false;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    remove_current_element_from_tree (
        node*& t,
        domain& d,
        range& r,
        unsigned long cur_stack_pos 
    ) 
    {
        // make a reference to the current node so we don't have to dereference 
        // a pointer a bunch of times
        node& tree = *t;

        // if we found the node we were looking for
        if (t == current_element)
        {

            // swap nodes domain and range elements into d_copy and r
            exchange(d,tree.d);
            exchange(r,tree.r);

            // if there is no left node
            if (tree.left == 0)
            {
                // move the enumerator on to the next element before we mess with the 
                // tree
                move_next();

                // plug hole left by removing this node and free memory
                t = tree.right;  // plug hole with right subtree
                
                // delete old node
                pool.deallocate(&tree);  

                // indicate that the height has changed
                return true;
            }
            // if there is no right node
            else if (tree.right == 0)
            {
                // move the enumerator on to the next element before we mess with the 
                // tree
                move_next();

                // plug hole left by removing this node and free memory
                t = tree.left;  // plug hole with left subtree

                // delete old node
                pool.deallocate(&tree);  

                // indicate that the height of this tree has changed
                return true;
            }
            // if there are both a left and right sub node
            else
            {

                // in this case the next current element is going to get swapped back
                // into this t node.
                current_element = t;

                // get an element that can replace the one being removed and do this 
                // if it made the right subtree shrink by one
                if (remove_least_element_in_tree(tree.right,tree.d,tree.r))
                {
                    // adjust the tree height
                    --tree.balance;

                    // if the height of the current tree has dropped by one
                    if (tree.balance == 0)
                    {
                        return true;
                    }
                    else
                    {
                        return keep_node_balanced(t);
                    }
                }
                // else this remove did not effect the height of this tree
                else
                {
                    return false;
                }

            }

        }
        else if (  (cur_stack_pos < stack_pos && stack[cur_stack_pos] == tree.left) || 
                    tree.left == current_element )
        {
            // go left
            if (tree.balance == -1)
            {
                int balance = tree.balance;
                balance += remove_current_element_from_tree(tree.left,d,r,cur_stack_pos+1);
                tree.balance = balance;
                return !tree.balance;
            }
            else
            {
                int balance = tree.balance;
                balance += remove_current_element_from_tree(tree.left,d,r,cur_stack_pos+1);
                tree.balance = balance;
                return keep_node_balanced(t);
            }
        }
        else if (  (cur_stack_pos < stack_pos && stack[cur_stack_pos] == tree.right) || 
                    tree.right == current_element )
        {
            // go right
            if (tree.balance == 1)
            {
                int balance = tree.balance;
                balance -= remove_current_element_from_tree(tree.right,d,r,cur_stack_pos+1);
                tree.balance = balance;
                return !tree.balance;
            }
            else
            {
                int balance = tree.balance;
                balance -= remove_current_element_from_tree(tree.right,d,r,cur_stack_pos+1);
                tree.balance = balance;
                return keep_node_balanced(t);
            }
        }
        
        // this return should never happen but do it anyway to suppress compiler warnings
        return false;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    remove_from_tree (
        node*& t,
        const domain& d,
        domain& d_copy,
        range& r
    ) 
    {
        // make a reference to the current node so we don't have to dereference 
        // a pointer a bunch of times
        node& tree = *t;

        // if item is on the left
        if (comp(d , tree.d))
        {
            // if the left side of the tree has the greatest height
            if (tree.balance == -1)
            {
                int balance = tree.balance;
                balance += remove_from_tree(tree.left,d,d_copy,r);
                tree.balance = balance;
                return !tree.balance;
            }
            else
            {
                int balance = tree.balance;
                balance += remove_from_tree(tree.left,d,d_copy,r);
                tree.balance = balance;
                return keep_node_balanced(t);
            }
             
        }
        // if item is on the right
        else if (comp(tree.d , d))
        {

            // if the right side of the tree has the greatest height
            if (tree.balance == 1)
            {
                int balance = tree.balance;
                balance -= remove_from_tree(tree.right,d,d_copy,r);
                tree.balance = balance;
                return !tree.balance;
            }
            else
            {
                int balance = tree.balance;
                balance -= remove_from_tree(tree.right,d,d_copy,r);
                tree.balance = balance;
                return keep_node_balanced(t);
            }
        }
        // if item is found
        else 
        {

            // swap nodes domain and range elements into d_copy and r
            exchange(d_copy,tree.d);
            exchange(r,tree.r);

            // if there is no left node
            if (tree.left == 0)
            {

                // plug hole left by removing this node and free memory
                t = tree.right;  // plug hole with right subtree
                
                // delete old node
                pool.deallocate(&tree);  

                // indicate that the height has changed
                return true;
            }
            // if there is no right node
            else if (tree.right == 0)
            {

                // plug hole left by removing this node and free memory
                t = tree.left;  // plug hole with left subtree

                // delete old node
                pool.deallocate(&tree);  

                // indicate that the height of this tree has changed
                return true;
            }
            // if there are both a left and right sub node
            else
            {

                // get an element that can replace the one being removed and do this 
                // if it made the right subtree shrink by one
                if (remove_least_element_in_tree(tree.right,tree.d,tree.r))
                {
                    // adjust the tree height
                    --tree.balance;

                    // if the height of the current tree has dropped by one
                    if (tree.balance == 0)
                    {
                        return true;
                    }
                    else
                    {
                        return keep_node_balanced(t);
                    }
                }
                // else this remove did not effect the height of this tree
                else
                {
                    return false;
                }

            }
        }
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    remove_from_tree (
        node*& t,
        const domain& d
    ) 
    {
        // make a reference to the current node so we don't have to dereference 
        // a pointer a bunch of times
        node& tree = *t;

        // if item is on the left
        if (comp(d , tree.d))
        {
            // if the left side of the tree has the greatest height
            if (tree.balance == -1)
            {
                int balance = tree.balance;
                balance += remove_from_tree(tree.left,d);
                tree.balance = balance;
                return !tree.balance;
            }
            else
            {
                int balance = tree.balance;
                balance += remove_from_tree(tree.left,d);
                tree.balance = balance;
                return keep_node_balanced(t);
            }
             
        }
        // if item is on the right
        else if (comp(tree.d , d))
        {

            // if the right side of the tree has the greatest height
            if (tree.balance == 1)
            {
                int balance = tree.balance;
                balance -= remove_from_tree(tree.right,d);
                tree.balance = balance;
                return !tree.balance;
            }
            else
            {
                int balance = tree.balance;
                balance -= remove_from_tree(tree.right,d);
                tree.balance = balance;
                return keep_node_balanced(t);
            }
        }
        // if item is found
        else 
        {

            // if there is no left node
            if (tree.left == 0)
            {

                // plug hole left by removing this node and free memory
                t = tree.right;  // plug hole with right subtree
                
                // delete old node
                pool.deallocate(&tree);  

                // indicate that the height has changed
                return true;
            }
            // if there is no right node
            else if (tree.right == 0)
            {

                // plug hole left by removing this node and free memory
                t = tree.left;  // plug hole with left subtree

                // delete old node
                pool.deallocate(&tree);  

                // indicate that the height of this tree has changed
                return true;
            }
            // if there are both a left and right sub node
            else
            {

                // get an element that can replace the one being removed and do this 
                // if it made the right subtree shrink by one
                if (remove_least_element_in_tree(tree.right,tree.d,tree.r))
                {
                    // adjust the tree height
                    --tree.balance;

                    // if the height of the current tree has dropped by one
                    if (tree.balance == 0)
                    {
                        return true;
                    }
                    else
                    {
                        return keep_node_balanced(t);
                    }
                }
                // else this remove did not effect the height of this tree
                else
                {
                    return false;
                }

            }
        }
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    range* binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    return_reference (
        node* t,
        const domain& d
    ) 
    {
        while (t != 0)
        {

            if ( comp(d , t->d ))
            {
                // if item is on the left then look in left
                t = t->left;
            }
            else if (comp(t->d , d))
            {
                // if item is on the right then look in right
                t = t->right;
            }
            else
            {
                // if it's found then return a reference to it
                return &(t->r);
            }
        }
        return 0;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    const range* binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    return_reference (
        const node* t,
        const domain& d
    ) const
    {
        while (t != 0)
        {

            if ( comp(d , t->d) )
            {
                // if item is on the left then look in left
                t = t->left;
            }
            else if (comp(t->d , d))
            {
                // if item is on the right then look in right
                t = t->right;
            }
            else
            {
                // if it's found then return a reference to it
                return &(t->r);
            }
        }
        return 0;
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    bool binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    keep_node_balanced (
        node*& t
    )
    {
        // make a reference to the current node so we don't have to dereference 
        // a pointer a bunch of times
        node& tree = *t;
 
        // if tree does not need to be balanced then return false
        if (tree.balance == 0)
            return false;


        // if tree needs to be rotated left
        if (tree.balance == 2)
        {
            if (tree.right->balance >= 0)
                rotate_left(t);
            else
                double_rotate_left(t);
        }
        // else if the tree needs to be rotated right
        else if (tree.balance == -2)
        {
            if (tree.left->balance <= 0)
                rotate_right(t);
            else
                double_rotate_right(t);
        }
   

        if (t->balance == 0)
            return true;
        else
            return false; 
    }

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

    template <
        typename domain,
        typename range,
        typename mem_manager,
        typename compare
        >   
    unsigned long binary_search_tree_kernel_1<domain,range,mem_manager,compare>::
    get_count (
        const domain& d,
        node* tree_root
    ) const
    {
        if (tree_root != 0)
        {
            if (comp(d , tree_root->d))
            {
                // go left
                return get_count(d,tree_root->left);                
            }
            else if (comp(tree_root->d , d))
            {
                // go right
                return get_count(d,tree_root->right);
            }
            else
            {
                // go left and right to look for more matches
                return   get_count(d,tree_root->left) 
                       + get_count(d,tree_root->right) 
                       + 1;
            }
        }
        return 0;
    }

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

}

#endif // DLIB_BINARY_SEARCH_TREE_KERNEl_1_