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

#include "hash_table_kernel_abstract.h"
#include "../general_hash/general_hash.h"
#include "../algs.h"
#include "../interfaces/map_pair.h"
#include "../interfaces/enumerable.h"
#include "../interfaces/remover.h"
#include "../assert.h"
#include "../serialize.h"
#include <functional>

namespace dlib 
{

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager = default_memory_manager,
        typename compare = std::less<domain>
        >
    class hash_table_kernel_2 : public enumerable<map_pair<domain,range> >,
                                public pair_remover<domain,range>
    {

        /*!
            REQUIREMENTS ON bst_base
                bst_base is instantiated with domain and range and
                implements binray_search_tree/binary_search_tree_kernel_abstract.h

             INITIAL VALUE
                hash_size == 0
                table == pointer to an array of num_of_buckets bst_base objects
                num_of_buckets == the number of buckets in the hash table
                current_bucket == 0
                at_start_ == true

            CONVENTION
                current_element_valid() == (current_bucket != 0)
                element() == current_bucket->element()
                at_start_ == at_start()

                mask == num_of_buckets-1

                for all integers i where &table[i] != current_bucket
                    table[i].at_start() == true


                hash_size = size() == the number of elements in the hash_table and
                table == pointer to an array of num_of_buckets bst_base objects
                num_of_buckets == the number of buckets in the hash table and
                the elements in this hash table are stored in the bst_base objects in the
                array table

        !*/
        

 
        public:

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

            explicit hash_table_kernel_2(
                unsigned long expnum
            );

            virtual ~hash_table_kernel_2(
            )
            { pool.deallocate_array(table); }

            void clear(
            );

            unsigned long count (
                const domain& item
            ) const;

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

            void destroy (
                const domain& d
            );

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

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

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

            inline void swap (
                hash_table_kernel_2& item
            );

            // functions from the remover interface
            void remove_any (
                domain& d,
                range& r
            );

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

            inline bool at_start (
            ) const;

            inline void reset (
            ) const;

            bool current_element_valid (
            ) const;

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

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

            bool move_next (
            ) const;

        private:

            // data members   
            typename mem_manager::template rebind<bst_base>::other pool;         
            unsigned long mask;
            unsigned long hash_size;
            unsigned long num_of_buckets;
            bst_base* table;
            general_hash<domain> hash;
            mutable bst_base* current_bucket;
            mutable bool at_start_;
            compare comp;

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

    };

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    void deserialize (
        hash_table_kernel_2<domain,range,bst_base,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 hash_table_kernel_2"); 
        }
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    hash_table_kernel_2(
        unsigned long expnum
    ) :
        hash_size(0),
        current_bucket(0),
        at_start_(true)
    {

        num_of_buckets = 1;
        while (expnum != 0)
        {
            --expnum;
            num_of_buckets <<= 1;            
        }
        mask = num_of_buckets-1;

        table = pool.allocate_array(num_of_buckets);
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    clear(
    )
    {
        if (hash_size != 0)
        {
            hash_size = 0;
            for (unsigned long i = 0; i < num_of_buckets; ++i)
                table[i].clear();
        }
        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    size_t hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    size(
    ) const
    {
        return hash_size;
    }
// ----------------------------------------------------------------------------------------

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    unsigned long hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    count(
        const domain& item
    ) const
    {
        return table[hash(item)&mask].count(item);
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    destroy(
        const domain& item
    ) 
    {
        table[hash(item)&mask].destroy(item);
        --hash_size;

        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    add(
        domain& d,
        range& r
    )
    {
        table[hash(d)&mask].add(d,r);
        ++hash_size;

        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    remove(
        const domain& d,
        domain& d_copy,
        range& r
    )
    {
        table[hash(d)&mask].remove(d,d_copy,r);
        --hash_size;

        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    remove_any(
        domain& d,
        range& r
    )
    {
        unsigned long i = 0;
        while (table[i].size() == 0)
        {
            ++i;
        }
        table[i].remove_any(d,r);
        --hash_size;

        // reset the enumerator
        reset();
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    const range* hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    operator[](
        const domain& d
    ) const
    {
        return table[hash(d)&mask][d];
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    range* hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    operator[](
        const domain& d
    )
    {
        return table[hash(d)&mask][d];
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    swap(
        hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>& item
    )
    {
        pool.swap(item.pool);
        exchange(mask,item.mask);
        exchange(hash_size,item.hash_size);
        exchange(num_of_buckets,item.num_of_buckets);
        exchange(table,item.table);
        exchange(current_bucket,item.current_bucket);
        exchange(at_start_,item.at_start_);        
        exchange(comp,item.comp);
    }

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

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

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    void hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    reset (
    ) const
    {
        at_start_ = true;
        if (current_bucket != 0)
        {
            current_bucket->reset();
            current_bucket = 0;
        }
    }

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

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

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    const map_pair<domain,range>& hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    element (
    ) const
    {
        return current_bucket->element();
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    map_pair<domain,range>& hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    element (
    )
    {
        return current_bucket->element();
    }

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

    template <
        typename domain,
        typename range,
        typename bst_base,
        typename mem_manager,
        typename compare
        >
    bool hash_table_kernel_2<domain,range,bst_base,mem_manager,compare>::
    move_next (
    ) const
    {
        if (at_start_)
        {
            at_start_ = false;
            // if the queue is empty then there is nothing to do
            if (hash_size == 0)
            {
                return false;
            }
            else
            {
                // find the first element in the hash table
                current_bucket = table;
                while (current_bucket->size() == 0)
                {
                    ++current_bucket;
                }

                current_bucket->move_next();
                
                return true;
            }
        }
        else
        {
            // if we have already enumerated every element
            if (current_bucket == 0)
            {
                return false;
            }
            else
            {
                if (current_bucket->move_next())
                {
                    // if there is another element in this current bucket then use that
                    return true;
                }
                else
                {
                    // find the next bucket
                    bst_base* end = table + num_of_buckets;
                    current_bucket->reset();
                    
                    while (true)
                    {   
                        ++current_bucket;
                        // if we ran out of buckets and didn't find anything
                        if (current_bucket == end)
                        {
                            current_bucket = 0;
                            return false;
                        }
                        if (current_bucket->size() > 0)
                        {
                            current_bucket->move_next();
                            return true;
                        }
                    }
                }
            }
        }
    }
    
// ----------------------------------------------------------------------------------------

}

#endif // DLIB_HASH_TABLE_KERNEl_2_