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

    #include "../algs.h"
    #include "lzp_buffer_kernel_abstract.h"
    #include <new>

    namespace dlib
    {

        template <
            typename sbuf
            >
        class lzp_buffer_kernel_2
        {
            /*!
                REQUIREMENTS ON sbuf
                    sbuf is an implementation of sliding_buffer/sliding_buffer_kernel_abstract.h
                    T == unsigned char

                INITIAL VALUE
                    - buffer.size() == the size as defined by the constructor
                    - table_size == the number of elements in the table3 and table4 arrays
                    - for all i: buffer[i] == 0
                    - for all i: table3[i] == buffer.size()
                    - for all i: table4[i] == buffer.size()

                CONVENTION
                    - table_size == the number of elements in the table3 and table4 arrays
                    - size() == buffer.size()
                    - operator[](i) == buffer[i]

                    - last_element == buffer.size()-1

                    This is LZP with an order-5-4-3 model with context confirmation.
                    To save memory the order5 and order3 predictions exist in the same
                    table, that is, table3.

            !*/

        public:

            explicit lzp_buffer_kernel_2 (
                unsigned long buffer_size
            );

            virtual ~lzp_buffer_kernel_2 (
            );

            void clear(
            );

            inline void add (
                unsigned char symbol
            );

            inline unsigned long predict_match (
                unsigned long& index
            );

            inline unsigned long size (
            ) const;

            inline unsigned char operator[] (
                unsigned long index
            ) const;

        private:

            inline bool verify (
                unsigned long index
            ) const
            /*!
                ensures
                    - returns true if buffer[index]'s context matches the current context
            !*/
            {
                if (index+3 < buffer.size())
                {
                    if (buffer[0] != buffer[index+1])
                        return false;
                    if (buffer[1] != buffer[index+2])
                        return false;
                    if (buffer[2] != buffer[index+3])
                        return false;
                    return true;
                }
                else
                {
                    // just call this a match
                    return true;
                }
            }

            sbuf buffer;
            unsigned long* table3;
            unsigned long* table4;
            unsigned long last_element;
            const unsigned long table_size;

            // restricted functions
            lzp_buffer_kernel_2(const lzp_buffer_kernel_2<sbuf>&);        // copy constructor
            lzp_buffer_kernel_2<sbuf>& operator=(const lzp_buffer_kernel_2<sbuf>&);    // assignment operator

        };

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

        template <
            typename sbuf
            >
        lzp_buffer_kernel_2<sbuf>::
        lzp_buffer_kernel_2 (
            unsigned long buffer_size
        ) :
            table3(0),
            table4(0),
            table_size(65536)
        {
            buffer.set_size(buffer_size);

            table3 = new (std::nothrow) unsigned long[table_size];
            table4 = new (std::nothrow) unsigned long[table_size];

            if (!table3 || !table4)
            {
                if (!table3)
                    delete [] table3;
                if (!table4)
                    delete [] table4;

                throw std::bad_alloc();
            }

            for (unsigned long i = 0; i < buffer.size(); ++i)
                buffer[i] = 0;

            for (unsigned long i = 0; i < table_size; ++i)
            {
                table3[i] = buffer.size();
                table4[i] = buffer.size();
            }

            last_element = buffer.size()-1;
        }

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

        template <
            typename sbuf
            >
        lzp_buffer_kernel_2<sbuf>::
        ~lzp_buffer_kernel_2 (
        )
        {
            delete [] table3;
            delete [] table4;
        }

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

        template <
            typename sbuf
            >
        void lzp_buffer_kernel_2<sbuf>::
        clear(
        )
        {
            for (unsigned long i = 0; i < buffer.size(); ++i)
                buffer[i] = 0;

            for (unsigned long i = 0; i < table_size; ++i)
            {
                table3[i] = buffer.size();
                table4[i] = buffer.size();
            }
        }

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

        template <
            typename sbuf
            >
        void lzp_buffer_kernel_2<sbuf>::
        add (
            unsigned char symbol
        )
        {
            buffer.rotate_left(1);
            buffer[0] = symbol;
        }

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

        template <
            typename sbuf
            >
        unsigned long lzp_buffer_kernel_2<sbuf>::
        predict_match (
            unsigned long& index
        )
        {
            unsigned long temp1 = buffer[0];
            unsigned long temp2 = buffer[1];
            temp2 <<= 8;
            unsigned long temp3 = buffer[2];
            temp3 <<= 16;
            unsigned long temp4 = buffer[3];
            temp4 <<= 24;
            unsigned long temp5 = buffer[4];
            temp5 <<= 12;

            unsigned long context1 = temp1|temp2|temp3;
            unsigned long context2 = context1|temp4;

            const unsigned long i5 = ((temp5|(context2>>20))^context2)&0xFFFF;
            const unsigned long i4 = ((context2>>15)^context2)&0xFFFF;
            const unsigned long i3 = ((context1>>11)^context1)&0xFFFF;

            // check the 5-order context's prediction
            if (table3[i5] != buffer.size() &&
                verify(buffer.get_element_index(table3[i5])) )
            {
                index = buffer.get_element_index(table3[i5]);
                if (index > 20)
                {
                    // update the prediction for this context
                    table3[i3] = buffer.get_element_id(last_element);
                    table4[i4] = table3[i3];
                    table3[i5] = table3[i3];
                }
                return 5;
            }
            // check the 4-order context's prediction
            else if (table4[i4] != buffer.size() &&
                verify(buffer.get_element_index(table4[i4])) )
            {
                index = buffer.get_element_index(table4[i4]);
                if (index > 20)
                {
                    // update the prediction for this context
                    table3[i3] = buffer.get_element_id(last_element);
                    table4[i4] = table3[i3];
                    table3[i5] = table3[i3];
                }
                return 4;
            }
            // check the 3-order context's prediction
            else if (table3[i3] != buffer.size() &&
                verify(buffer.get_element_index(table3[i3])))
            {
                index = buffer.get_element_index(table3[i3]);

                if (index > 20)
                {
                    // update the prediction for this context
                    table3[i3] = buffer.get_element_id(last_element);
                    table4[i4] = table3[i3];
                    table3[i5] = table3[i3];
                }
                return 3;
            }
            else
            {
                // update the prediction for this context
                table3[i3] = buffer.get_element_id(last_element);
                table4[i4] = table3[i3];
                table3[i5] = table3[i3];

                return 0;
            }
        }

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

        template <
            typename sbuf
            >
        unsigned long lzp_buffer_kernel_2<sbuf>::
        size (
        ) const
        {
            return buffer.size();
        }

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

        template <
            typename sbuf
            >
        unsigned char lzp_buffer_kernel_2<sbuf>::
        operator[] (
            unsigned long index
        ) const
        {
            return buffer[index];
        }

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

    }

    #endif // DLIB_LZP_BUFFER_KERNEl_2_