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

#include "array_kernel_abstract.h"
#include "../interfaces/enumerable.h"
#include "../algs.h"
#include "../serialize.h"

namespace dlib
{


    template <
        typename T,
        typename mem_manager = default_memory_manager 
        >
    class array_kernel_1 : public enumerable<T>
    {

        /*!
            INITIAL VALUE
                - array_size == 0
                - array_nodes == 0 
                - max_array_size == 0 
                - number_of_nodes == 0
                - mask == 0 
                - mask_size == 0
                - _at_start == true
                - pos == 0


            CONVENTION
                - current_element_valid() == (pos != array_size)
                - at_start() == _at_start
                - if (pos != array_size)
                    - element() == (*this)[pos]

                array_size == number of elements in the array.
                array_nodes == pointer to array of number_of_nodes pointers.
                max_array_size == the maximum allowed number of elements in the array.
                mask_size == the number of bits set to 1 in mask
                
        
                if (array_size > 0)
                {
                    Only array_nodes[0] though array_nodes[(array_size-1)/number_of_nodes] 
                    point to valid addresses.  All other elements in array_nodes
                    are set to 0.
                }
                else
                {
                    for all x: array_nodes[x] == 0
                }
                
                operator[](pos) == array_nodes[pos>>mask_size][pos&mask]

                if (max_array_size == 0)
                {
                    number_of_nodes == 0
                    array_nodes == 0
                    array_size == 0
                }                
        !*/
        
        public:

            typedef T type;
            typedef mem_manager mem_manager_type;

            array_kernel_1 (
            ) :
                array_nodes(0)
            {
                update_max_array_size(0);
            }

            virtual ~array_kernel_1 (
            ); 

            void clear (
            );

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

            void set_size (
                unsigned long size
            );

            inline unsigned long max_size(
            ) const;

            void set_max_size(
                unsigned long max
            );
            
            void swap (
                array_kernel_1& item
            );


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

            inline bool at_start (
            ) const;

            inline void reset (
            ) const;

            bool current_element_valid (
            ) const;

            inline const T& element (
            ) const;

            inline T& element (
            );

            bool move_next (
            ) const;


        private:


            void update_max_array_size (
                unsigned long new_max_array_size
            );
            /*!
                ensures
                    - everything in the CONVENTION is satisfied
                    - #max_array_size == new_max_array_size
                    - #array_size == 0
                    - mask_size, mask, and number_of_nodes have been set proper
                      values for the new max array size
                    - if (new_max_array_size != 0) then
                        - #array_nodes == pointer to an array of size #number_of_nodes 
                    - else
                        - #array_nodes == 0
                    - #at_start() == true
            !*/

            // data members
            T** array_nodes;
            unsigned long max_array_size;
            unsigned long array_size;
            unsigned long number_of_nodes;            
            mutable unsigned long pos;
            unsigned long mask;
            unsigned long mask_size;
            mutable bool _at_start;

            

            // restricted functions
            array_kernel_1(array_kernel_1<T>&);        // copy constructor
            array_kernel_1<T>& operator=(array_kernel_1<T>&);    // assignment operator        

    };

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

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

    template <
        typename T,
        typename mem_manager
        >
    void serialize (
        const array_kernel_1<T,mem_manager>& item,  
        std::ostream& out
    )
    {
        try
        {
            serialize(item.max_size(),out);
            serialize(item.size(),out);

            for (unsigned long i = 0; i < item.size(); ++i)
                serialize(item[i],out);
        }
        catch (serialization_error e)
        { 
            throw serialization_error(e.info + "\n   while serializing object of type array_kernel_1"); 
        }
    }

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

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

    template <
        typename T,
        typename mem_manager
        >
    array_kernel_1<T,mem_manager>::
    ~array_kernel_1 (
    )
    {
        update_max_array_size(0);
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void array_kernel_1<T,mem_manager>::
    clear (
    )
    {
        update_max_array_size(0);                
    }

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

    template <
        typename T,
        typename mem_manager
        >
    const T& array_kernel_1<T,mem_manager>::
    operator[] (
        unsigned long pos
    ) const
    {
        return array_nodes[pos>>mask_size][pos&mask];
    }

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

    template <
        typename T,
        typename mem_manager
        >
    T& array_kernel_1<T,mem_manager>::
    operator[] (
        unsigned long pos
    )
    {
        return array_nodes[pos>>mask_size][pos&mask];
    }

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

    template <
        typename T,
        typename mem_manager
        >
    unsigned long array_kernel_1<T,mem_manager>::
    max_size (
    ) const
    {
        return max_array_size;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void array_kernel_1<T,mem_manager>::
    set_max_size (
        unsigned long max
    )
    {       
        update_max_array_size(max);       
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void array_kernel_1<T,mem_manager>::
    set_size (
        unsigned long size
    )
    {
        if (array_size == 0 && size != 0)
        {
            const unsigned long new_biggest_node = (size-1)/(mask+1);
            try
            {
                // we need to initialize some array nodes
                for (unsigned long i = 0; i <= new_biggest_node; ++i)
                    array_nodes[i] = new T[mask+1];
            }
            catch (...)
            {
                // undo any changes
                for (unsigned long i = 0; i <= new_biggest_node; ++i)
                {
                    if (array_nodes[i] != 0)
                        delete [] array_nodes[i];
                    array_nodes[i] = 0;
                }
                throw;                
            }            
        }
        else if (size == 0)
        {
            // free all nodes
            for (unsigned long i = 0; i < number_of_nodes; ++i)
            {
                if (array_nodes[i] != 0)
                    delete [] array_nodes[i];
                array_nodes[i] = 0;
            }
        }
        else
        {
            const unsigned long biggest_node = (array_size-1)/(mask+1);
            const unsigned long new_biggest_node = (size-1)/(mask+1);
            try
            {
                if (biggest_node < new_biggest_node)
                {
                    // we need to initialize more array nodes
                    for (unsigned long i = biggest_node+1; i <= new_biggest_node; ++i)
                        array_nodes[i] = new T[mask+1];
                }
                else if (biggest_node > new_biggest_node)
                {
                    // we need to free some array nodes
                    for (unsigned long i = new_biggest_node+1; i <= biggest_node; ++i)
                    {
                        delete [] array_nodes[i];
                        array_nodes[i] = 0;
                    }
                }
            }
            catch (...)
            {
                // undo any changes
                for (unsigned long i = biggest_node+1; i <= new_biggest_node; ++i)
                {
                    if (array_nodes[i] != 0)
                        delete [] array_nodes[i];
                    array_nodes[i] = 0;
                }
                throw;
            }
        }
        array_size = size;        
        reset();
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void array_kernel_1<T,mem_manager>::
    swap (
        array_kernel_1<T,mem_manager>& item
    )
    {
        exchange(_at_start,item._at_start);
        exchange(pos,item.pos);
        exchange(mask_size,item.mask_size);
        exchange(mask,item.mask);

        unsigned long    max_array_size_temp    = item.max_array_size;
        unsigned long    array_size_temp        = item.array_size;
        unsigned long    number_of_nodes_temp   = item.number_of_nodes;
        T**                array_nodes_temp     = item.array_nodes;

        item.max_array_size     = max_array_size;
        item.array_size         = array_size;
        item.number_of_nodes    = number_of_nodes;
        item.array_nodes        = array_nodes;

        max_array_size          = max_array_size_temp;
        array_size              = array_size_temp;
        number_of_nodes         = number_of_nodes_temp;
        array_nodes             = array_nodes_temp;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    void array_kernel_1<T,mem_manager>::
    update_max_array_size (
        unsigned long new_max_array_size
    )
    {   
        max_array_size = new_max_array_size;

        // first free all memory 
        if (array_nodes != 0)
        {
            for (unsigned long i = 0; i < number_of_nodes; ++i)
            {
                if (array_nodes[i] != 0)
                    delete [] array_nodes[i];
                else
                    break;
            }
            delete [] array_nodes;
        }

        if (max_array_size > 0)
        {
            // select new values for number_of_nodes, mask_size, and mask
            if (max_array_size <= 0x1000)
            {
                number_of_nodes = 0x10;
                mask = 0xFF;
                mask_size = 8;
            }
            else if (max_array_size <= 0x10000)
            {
                number_of_nodes = 0x100;
                mask = 0xFF;
                mask_size = 8;
            }
            else if (max_array_size <= 0x100000)
            {
                number_of_nodes = 1024;
                mask = 0x3FF;
                mask_size = 10;
            }
            else if (max_array_size <= 0x1000000)
            {
                number_of_nodes = 4096;
                mask = 0xFFF;
                mask_size = 12;
            }
            else if (max_array_size <= 0x10000000)
            {
                number_of_nodes = 16384;
                mask = 0x3FFF;
                mask_size = 14;
            }
            else if (max_array_size <= 0x40000000)
            {
                number_of_nodes = 32768;
                mask = 0x7FFF;
                mask_size = 15;
            }
            else 
            {
                number_of_nodes = 65536;
                mask = 0xFFFF;
                mask_size = 16;                
            }

            try 
            {
                array_nodes = new T*[number_of_nodes];
                for (unsigned long i = 0; i < number_of_nodes; ++i)
                    array_nodes[i] = 0;
            }
            catch (...)
            {
                max_array_size = 0;
                array_nodes = 0;
                number_of_nodes = 0;
                array_size = 0;
                reset();
                throw;
            }
        }
        else
        {
            array_nodes = 0;
            number_of_nodes = 0;
        }
     
        array_size = 0;
        reset();
    }

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

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

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

    template <
        typename T,
        typename mem_manager
        >
    void array_kernel_1<T,mem_manager>::
    reset (
    ) const
    {
        _at_start = true;
        pos = array_size;
    }

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

    template <
        typename T,
        typename mem_manager
        >
    bool array_kernel_1<T,mem_manager>::
    current_element_valid (
    ) const
    {
        return (pos != array_size);
    }

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

    template <
        typename T,
        typename mem_manager
        >
    const T& array_kernel_1<T,mem_manager>::
    element (
    ) const
    {
        return operator[](pos);
    }

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

    template <
        typename T,
        typename mem_manager
        >
    T& array_kernel_1<T,mem_manager>::
    element (
    )
    {
        return operator[](pos);
    }

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

    template <
        typename T,
        typename mem_manager
        >
    bool array_kernel_1<T,mem_manager>::
    move_next (
    ) const
    {
        if (!_at_start)
        {
            if (pos+1 < array_size)
            {
                ++pos;
                return true;
            }
            else
            {
                pos = array_size;
                return false;
            }
        }
        else
        {
            _at_start = false;
            pos = 0;
            if (array_size == 0)
                return false;
            else
                return true;
        }
    }

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

    template <
        typename T,
        typename mem_manager
        >
    unsigned long array_kernel_1<T,mem_manager>::
    size (
    ) const
    {
        return array_size;
    }

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

}

#endif // DLIB_ARRAY_KERNEl_1_