// Copyright (C) 2004  Davis E. King (davisking@users.sourceforge.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_CONDITIONING_CLASS_KERNEl_3_
#define DLIB_CONDITIONING_CLASS_KERNEl_3_

#include "conditioning_class_kernel_abstract.h"
#include "../assert.h"
#include "../algs.h"


namespace dlib
{

    template <
        unsigned long alphabet_size
        >
    class conditioning_class_kernel_3 
    {
        /*!
            INITIAL VALUE
                total == 1
                counts == pointer to an array of alphabet_size data structs                
                for all i except i == 0: counts[i].count == 0
                counts[0].count == 1
                counts[0].symbol == alphabet_size-1
                for all i except i == alphabet_size-1:  counts[i].present == false
                counts[alphabet_size-1].present == true

            CONVENTION
                counts == pointer to an array of alphabet_size data structs
                get_total() == total
                get_count(symbol) == counts[x].count where 
                                     counts[x].symbol == symbol


                LOW_COUNT(symbol) == sum of counts[0].count though counts[x-1].count
                                     where counts[x].symbol == symbol
                                     if (counts[0].symbol == symbol) LOW_COUNT(symbol)==0


                if (counts[i].count == 0) then
                    counts[i].symbol == undefined value

                if (symbol has a nonzero count) then
                    counts[symbol].present == true

                get_memory_usage() == global_state.memory_usage
        !*/

    public:

        class global_state_type
        {
        public:
            global_state_type () : memory_usage(0) {}
        private:
            unsigned long memory_usage;

            friend class conditioning_class_kernel_3<alphabet_size>;
        };

        conditioning_class_kernel_3 (
            global_state_type& global_state_
        );

        ~conditioning_class_kernel_3 (
        );

        void clear(
        );

        bool increment_count (
            unsigned long symbol,
            unsigned short amount = 1
        );

        unsigned long get_count (
            unsigned long symbol
        ) const;

        unsigned long get_total (
        ) const;
        
        unsigned long get_range (
            unsigned long symbol,
            unsigned long& low_count,
            unsigned long& high_count,
            unsigned long& total_count
        ) const;

        void get_symbol (
            unsigned long target,
            unsigned long& symbol,            
            unsigned long& low_count,
            unsigned long& high_count
        ) const;

        unsigned long get_memory_usage (
        ) const;

        global_state_type& get_global_state (
        );

        static unsigned long get_alphabet_size (
        );

    private:

        // restricted functions
        conditioning_class_kernel_3(conditioning_class_kernel_3<alphabet_size>&);        // copy constructor
        conditioning_class_kernel_3& operator=(conditioning_class_kernel_3<alphabet_size>&);    // assignment operator

        struct data
        {
            unsigned short count;
            unsigned short symbol;
            bool present;
        };

        // data members
        unsigned short total;
        data* counts;
        global_state_type& global_state;

    };   

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

    template <
        unsigned long alphabet_size
        >
    conditioning_class_kernel_3<alphabet_size>::
    conditioning_class_kernel_3 (
        global_state_type& global_state_
    ) :
        total(1),
        counts(new data[alphabet_size]),
        global_state(global_state_)
    {
        COMPILE_TIME_ASSERT( 1 < alphabet_size && alphabet_size < 65536 );

        data* start = counts;
        data* end = counts+alphabet_size;
        start->count = 1;
        start->symbol = alphabet_size-1;
        start->present = false;
        ++start;
        while (start != end)
        {
            start->count = 0;
            start->present = false;
            ++start;
        }        
        counts[alphabet_size-1].present = true;

        // update memory usage
        global_state.memory_usage += sizeof(data)*alphabet_size + 
                                     sizeof(conditioning_class_kernel_3);

    }

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

    template <
        unsigned long alphabet_size
        >
    conditioning_class_kernel_3<alphabet_size>::
    ~conditioning_class_kernel_3 (
    )
    {
        delete [] counts;
        // update memory usage
        global_state.memory_usage -= sizeof(data)*alphabet_size + 
                                     sizeof(conditioning_class_kernel_3);
    }

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

    template <
        unsigned long alphabet_size
        >
    void conditioning_class_kernel_3<alphabet_size>::
    clear(
    )
    {
        total = 1;
        data* start = counts;
        data* end = counts+alphabet_size;
        start->count = 1;
        start->symbol = alphabet_size-1;
        start->present = false;
        ++start;
        while (start != end)
        {
            start->count = 0;
            start->present = false;
            ++start;
        }        
        counts[alphabet_size-1].present = true; 

    }

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

    template <
        unsigned long alphabet_size
        >
    typename conditioning_class_kernel_3<alphabet_size>::global_state_type& conditioning_class_kernel_3<alphabet_size>::
    get_global_state(
    )
    {
        return global_state;
    }

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

    template <
        unsigned long alphabet_size
        >
    unsigned long conditioning_class_kernel_3<alphabet_size>::
    get_memory_usage(
    ) const
    {
        return global_state.memory_usage;
    }

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

    template <
        unsigned long alphabet_size
        >
    bool conditioning_class_kernel_3<alphabet_size>::
    increment_count (
        unsigned long symbol,
        unsigned short amount
    )
    {        
        // if we are going over a total of 65535 then scale down all counts by 2
        if (static_cast<unsigned long>(total)+static_cast<unsigned long>(amount) >= 65536)
        {
            total = 0;
            data* start = counts;
            data* end = counts+alphabet_size;
            
            while (start != end)
            {
                if (start->count == 1)
                {
                    if (start->symbol == alphabet_size-1)
                    {
                        // this symbol must never be zero so we will leave its count at 1
                        ++total;
                    }
                    else
                    {
                        start->count = 0;
                        counts[start->symbol].present = false;
                    }
                }
                else
                {
                    start->count >>= 1;
                    total += start->count;
                }
 
                ++start;
            }  
        }


        data* start = counts;   
        data* swap_spot = counts;

        if (counts[symbol].present)
        {
            while (true)
            {                
                if (start->symbol == symbol && start->count!=0)
                {                
                    unsigned short temp = start->count + amount;

                    start->symbol = swap_spot->symbol;
                    start->count = swap_spot->count;

                    swap_spot->symbol = static_cast<unsigned short>(symbol);                
                    swap_spot->count  = temp;
                    break;
                }
                
                if ( (start->count) < (swap_spot->count))
                {
                    swap_spot = start;
                }


                ++start;
            }
        }
        else
        {
            counts[symbol].present = true;
            while (true)
            {
                if (start->count == 0)
                {
                    start->symbol = swap_spot->symbol;
                    start->count = swap_spot->count;
                    
                    swap_spot->symbol = static_cast<unsigned short>(symbol);                
                    swap_spot->count  = amount;
                    break;
                }
                    
                if ((start->count) < (swap_spot->count))
                {
                    swap_spot = start;
                }

                ++start;
            }
        }
        
        total += amount;

        return true;
    }

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

    template <
        unsigned long alphabet_size
        >
    unsigned long conditioning_class_kernel_3<alphabet_size>::
    get_count (
        unsigned long symbol
    ) const
    {
        if (counts[symbol].present == false)
            return 0;

        data* start = counts;        
        while (start->symbol != symbol)
        {
            ++start;
        }
        return start->count;
    }

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

    template <
        unsigned long alphabet_size
        >
    unsigned long conditioning_class_kernel_3<alphabet_size>::
    get_alphabet_size (        
    ) 
    {
        return alphabet_size;
    }

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

    template <
        unsigned long alphabet_size
        >
    unsigned long conditioning_class_kernel_3<alphabet_size>::
    get_total (
    ) const
    {
        return total;
    }

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

    template <
        unsigned long alphabet_size
        >
    unsigned long conditioning_class_kernel_3<alphabet_size>::
    get_range (
        unsigned long symbol,
        unsigned long& low_count,
        unsigned long& high_count,
        unsigned long& total_count
    ) const
    {
        if (counts[symbol].present == false)
            return 0;

        total_count = total;
        unsigned long low_count_temp = 0;
        data* start = counts;        
        while (start->symbol != symbol)
        {
            low_count_temp += start->count;
            ++start;
        } 

        low_count = low_count_temp;
        high_count = low_count_temp + start->count;
        return start->count;
    }

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

    template <
        unsigned long alphabet_size
        >
    void conditioning_class_kernel_3<alphabet_size>::
    get_symbol (
        unsigned long target,
        unsigned long& symbol,            
        unsigned long& low_count,
        unsigned long& high_count
    ) const
    {
        unsigned long high_count_temp = counts->count;
        const data* start = counts;        
        while (target >= high_count_temp)
        {
            ++start;
            high_count_temp += start->count;
        } 

        low_count = high_count_temp - start->count;
        high_count = high_count_temp;
        symbol = static_cast<unsigned long>(start->symbol);
    }

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

}

#endif // DLIB_CONDITIONING_CLASS_KERNEl_3_

