// Copyright (C) 2003  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_ENTROPY_ENCODER_KERNEL_1_CPp_
#define DLIB_ENTROPY_ENCODER_KERNEL_1_CPp_
#include "entropy_encoder_kernel_1.h"
#include <iostream>
#include <streambuf>

namespace dlib
{


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

    entropy_encoder_kernel_1::
    entropy_encoder_kernel_1(
    ) :
        initial_low(0x00000001),
        initial_high(0xffffffff),
        out(0),
        low(initial_low),
        high(initial_high),
        buf(0),
        buf_used(0)
    {
    }

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

    entropy_encoder_kernel_1::
    ~entropy_encoder_kernel_1 (
    )
    {
        try {
            if (out != 0)
            {
                flush();
            }
        } catch (...) {}
    }

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

    void entropy_encoder_kernel_1::
    clear(
    )
    {
        if (out != 0)
        {
            flush();
        }
        out = 0;
    }

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

    void entropy_encoder_kernel_1::
    set_stream (
        std::ostream& out_
    )
    {
        if (out != 0)
        {
            // if a stream is currently set then flush the buffers to it before
            // we switch to the new stream
            flush();
        }
    
        out = &out_;
        streambuf = out_.rdbuf();

        // reset the encoder state
        buf_used = 0;
        buf = 0;
        low = initial_low;
        high = initial_high;
    }

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

    bool entropy_encoder_kernel_1::
    stream_is_set (
    ) const
    {
        if (out != 0)
            return true;
        else
            return false;
    }

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

    std::ostream& entropy_encoder_kernel_1::
    get_stream (
    ) const
    {
        return *out;
    }

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

    void entropy_encoder_kernel_1::
    encode (
        uint32 low_count,
        uint32 high_count,
        uint32 total
    )
    {
        // note that we must add one because of the convention that
        // high == the real upper range minus 1
        uint32 r = (high-low+1)/total;                 

        // note that we must subtract 1 to preserve the convention that
        // high == the real upper range - 1
        high = low + r*high_count-1;
        low = low + r*low_count;


        while (true)
        {

            // if the highest order bit in high and low is the same
            if ( low >= 0x80000000 || high < 0x80000000)
            {              
                // if buf is full then write it out
                if (buf_used == 8)
                {
                    if (streambuf->sputn(reinterpret_cast<char*>(&buf),1)==0)
                    {
                        throw std::ios_base::failure("error occured in the entropy_encoder object");
                    }
                    buf = 0;
                    buf_used = 0;
                }   


                // write the high order bit from low into buf
                buf <<= 1;
                ++buf_used;                
                if (low&0x80000000)
                    buf |= 0x1;

                // roll off the bit we just wrote to buf
                low <<= 1;                
                high <<= 1;  
                high |= 1;     // note that it is ok to add one to high here because
                            // of the convention that high == real upper range - 1.
                            // so that means that if we want to shift the upper range
                            // left by one then we must shift a one into high also
                            // since real upper range == high + 0.999999999...

                // make sure low is never zero
                if (low == 0)
                    low = 1;
            }
            // if the distance between high and low is small and there aren't
            // any bits we can roll off then round low up or high down.
            else if (high-low < 0x10000)
            {
                if (high == 0x80000000)
                    high = 0x7fffffff;
                else
                    low = 0x80000000;
            }
            else
            {
                break;
            }
        } // while (true)

    }

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

    void entropy_encoder_kernel_1::
    flush (
    )
    {
        // flush the next 4 or 5 bytes that are buffered
        // thats whatever is contained in buf and then all of low plus any extra 
        // bits needed to pad that to be an even 4 or 5 bytes


        if (buf_used != 8)
        {
            buf <<= (8-buf_used);   
            buf |= static_cast<unsigned char>(low>>(24+buf_used));         
            low <<= (8-buf_used);
        }

        if (streambuf->sputn(reinterpret_cast<char*>(&buf),1) == 0)
            throw std::ios_base::failure("error occured in the entropy_encoder object");



        buf = static_cast<unsigned char>((low >> 24)&0xFF);
        if (streambuf->sputn(reinterpret_cast<char*>(&buf),1) == 0)
            throw std::ios_base::failure("error occured in the entropy_encoder object");




        buf = static_cast<unsigned char>((low >> 16)&0xFF);
        if (streambuf->sputn(reinterpret_cast<char*>(&buf),1)==0)
            throw std::ios_base::failure("error occured in the entropy_encoder object");



        buf = static_cast<unsigned char>((low >> 8)&0xFF);
        if (streambuf->sputn(reinterpret_cast<char*>(&buf),1)==0)
            throw std::ios_base::failure("error occured in the entropy_encoder object");



        if (buf_used != 0)
        {
            buf = static_cast<unsigned char>((low)&0xFF);
            if (streambuf->sputn(reinterpret_cast<char*>(&buf),1)==0)
                throw std::ios_base::failure("error occured in the entropy_encoder object");
        }
    

        
        // make sure the stream buffer flushes to its I/O channel
        streambuf->pubsync();


        // reset the encoder state
        buf_used = 0;
        buf = 0;
        low = initial_low;
        high = initial_high;
    }

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

}
#endif // DLIB_ENTROPY_ENCODER_KERNEL_1_CPp_