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

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

    namespace dlib
    {

        template <
            unsigned long alphabet_size,
            typename entropy_encoder,
            typename cc,
            typename cc_high
            >
        class entropy_encoder_model_kernel_3
        {
            /*!
                REQUIREMENTS ON cc
                    cc is an implementation of conditioning_class/conditioning_class_kernel_abstract.h
                    cc::get_alphabet_size() == alphabet_size+1

                REQUIREMENTS ON cc_high
                    cc_high is an implementation of conditioning_class/conditioning_class_kernel_abstract.h
                    cc_high::get_alphabet_size() == alphabet_size+1

                INITIAL VALUE
                    - Initially this object's finite context model is empty
                    - previous_symbol == 0
                    - previous_symbol2 == 0
                    - order_1 == pointer to an array of alphabet_size elements
                    - order_2 == pointer to an array of alphabet_size*alphabet_size elements
                    - order_2[i] == 0

                CONVENTION
                    &get_entropy_encoder() == coder
                    &order_0.get_global_state() == &gs
                    &order_1[i]->get_global_state() == &gs

                    if (order_2[i] != 0) then
                        &order_2[i]->get_global_state() == &gs_high

                    This is an order-2-1-0 model. The last symbol in the order-2, order-1 and
                    order-0 contexts is an escape into the lower context.

                    previous_symbol == the last symbol seen
                    previous_symbol2 == the symbol we saw before previous_symbol
            !*/

        public:

            typedef entropy_encoder entropy_encoder_type;

            entropy_encoder_model_kernel_3 (
                entropy_encoder& coder
            );

            virtual ~entropy_encoder_model_kernel_3 (
            );

            inline void clear(
            );

            inline void encode (
                unsigned long symbol
            );

            entropy_encoder& get_entropy_encoder (
            ) { return coder; }

            static unsigned long get_alphabet_size (
            ) { return alphabet_size; }

        private:

            entropy_encoder& coder;
            typename cc::global_state_type gs;
            typename cc_high::global_state_type gs_high;
            cc order_0;
            cc** order_1;
            unsigned long previous_symbol;
            cc_high** order_2;
            unsigned long previous_symbol2;

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

        };

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

        template <
            unsigned long alphabet_size,
            typename entropy_encoder,
            typename cc,
            typename cc_high
            >
        entropy_encoder_model_kernel_3<alphabet_size,entropy_encoder,cc,cc_high>::
        entropy_encoder_model_kernel_3 (
            entropy_encoder& coder_
        ) :
            coder(coder_),
            order_0(gs),
            order_1(0),
            previous_symbol(0),
            order_2(0),
            previous_symbol2(0)
        {
            COMPILE_TIME_ASSERT( 1 < alphabet_size && alphabet_size < 65535 );

            try
            {
                order_1 = new cc*[alphabet_size];
                order_2 = new cc_high*[alphabet_size*alphabet_size];
            }
            catch (...)
            {
                if (order_1) delete [] order_1;
                if (order_2) delete [] order_2;
                throw;
            }

            unsigned long i;

            for (i = 0; i < (alphabet_size*alphabet_size); ++i)
            {
                order_2[i] = 0;
            }

            try
            {
                for (i = 0; i < alphabet_size; ++i)
                {
                    order_1[i] = new cc(gs);
                }
            }
            catch (...)
            {
                for (unsigned long j = 0; j < i; ++j)
                {
                    delete order_1[j];
                }
                throw;
            }
        }

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

        template <
            unsigned long alphabet_size,
            typename entropy_encoder,
            typename cc,
            typename cc_high
            >
        entropy_encoder_model_kernel_3<alphabet_size,entropy_encoder,cc,cc_high>::
        ~entropy_encoder_model_kernel_3 (
        )
        {
            for (unsigned long i = 0; i < alphabet_size; ++i)
            {
                delete order_1[i];
            }

            for (unsigned long i = 0; i < alphabet_size*alphabet_size; ++i)
            {
                if (order_2[i] != 0)
                    delete order_2[i];
            }
            delete [] order_1;
            delete [] order_2;
        }

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

        template <
            unsigned long alphabet_size,
            typename entropy_encoder,
            typename cc,
            typename cc_high
            >
        void entropy_encoder_model_kernel_3<alphabet_size,entropy_encoder,cc,cc_high>::
        clear(
        )
        {
            previous_symbol = 0;
            previous_symbol2 = 0;
            order_0.clear();
            for (unsigned long i = 0; i < alphabet_size; ++i)
            {
                order_1[i]->clear();
            }

            for (unsigned long i = 0; i < alphabet_size*alphabet_size; ++i)
            {
                if (order_2[i] != 0)
                {
                    delete order_2[i];
                    order_2[i] = 0;
                }
            }
        }

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

        template <
            unsigned long alphabet_size,
            typename entropy_encoder,
            typename cc,
            typename cc_high
            >
        void entropy_encoder_model_kernel_3<alphabet_size,entropy_encoder,cc,cc_high>::
        encode (
            unsigned long symbol
        )
        {
            unsigned long low_count = 0, high_count = 0, total_count = 0;

            // order-2 context stuff
            {
                unsigned long temp = previous_symbol + (previous_symbol2 * alphabet_size);
                previous_symbol2 = previous_symbol;

                if (order_2[temp] != 0)
                {
                    if (order_2[temp]->get_range(symbol,low_count,high_count,total_count))
                    {
                        // there was an entry for this symbol in this context

                        // update the count for this symbol
                        order_2[temp]->increment_count(symbol,2);
                        // encode this symbol
                        coder.encode(low_count,high_count,total_count);
                        previous_symbol = symbol;
                        return;
                    }

                    // there was no entry for this symbol in this context so we must
                    // escape to order-1

                    // escape to the order-1 context
                    order_2[temp]->get_range(alphabet_size,low_count,high_count,total_count);
                    coder.encode(low_count,high_count,total_count);

                    // increment the count for the escape symbol
                    order_2[temp]->increment_count(alphabet_size);

                }
                else
                {
                    order_2[temp] = new cc_high(gs_high);

                    // in this case the decoder knows to escape to order-1 because
                    // there was no conditioning_class object in this context yet.
                    // so we don't need to actually write the escape symbol
                }

                // update the count for this symbol in this context
                order_2[temp]->increment_count(symbol,2);
            }

            // order-1 context stuff
            {
                cc& context = *order_1[previous_symbol];

                // if we have seen this symbol in the order-1 context
                if (context.get_range(symbol,low_count,high_count,total_count))
                {
                    // update the count for this symbol
                    context.increment_count(symbol,2);
                    // encode this symbol
                    coder.encode(low_count,high_count,total_count);
                    previous_symbol = symbol;
                    return;
                }

                // we didn't find the symbol in the order-1 context so we must escape to a
                // lower context.

                // escape to the order-0 context
                context.get_range(alphabet_size,low_count,high_count,total_count);
                coder.encode(low_count,high_count,total_count);

                // increment counts for the escape symbol and the current symbol
                context.increment_count(alphabet_size);
                context.increment_count(symbol,2);
            }

            previous_symbol = symbol;

            // if we have seen this symbol in the order-0 context
            if (order_0.get_range(symbol,low_count,high_count,total_count))
            {
                // update the count for this symbol
                order_0.increment_count(symbol,2);
                // encode this symbol
                coder.encode(low_count,high_count,total_count);
                return;
            }

            // if we are here then the symbol does not appear in the order-0 context

            // since we have never seen the current symbol in this context
            // escape from order-0 context
            order_0.get_range(alphabet_size,low_count,high_count,total_count);
            coder.encode(low_count,high_count,total_count);
            // increment the count for the escape symbol
            order_0.increment_count(alphabet_size);

            // update the count for this symbol
            order_0.increment_count(symbol,2);

            // use order minus one context
            coder.encode(symbol,symbol+1,alphabet_size);

        }

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

    }

    #endif // DLIB_ENTROPY_ENCODER_MODEL_KERNEl_3_