#ifndef ARICODER_H
#define ARICODER_H

#include "bitops.h"

#include <algorithm>
#include <cstdint>
#include <memory>
#include <vector>

// defines for coder
constexpr uint32_t CODER_USE_BITS = 31; // Must never be above 31.
constexpr uint32_t CODER_LIMIT100 = uint32_t(1 << CODER_USE_BITS);
constexpr uint32_t CODER_LIMIT025 = CODER_LIMIT100 / 4;
constexpr uint32_t CODER_LIMIT050 = (CODER_LIMIT100 / 4) * 2;
constexpr uint32_t CODER_LIMIT075 = (CODER_LIMIT100 / 4) * 3;
constexpr uint32_t CODER_MAXSCALE = CODER_LIMIT025 - 1;
constexpr uint32_t ESCAPE_SYMBOL = CODER_LIMIT025;

// symbol struct, used in arithmetic coding
struct symbol
{
    uint32_t low_count;
    uint32_t high_count;
    uint32_t scale;
};

// table struct, used in in statistical models,
// holding all info needed for one context
struct table
{
    // counts for each symbol contained in the table
    std::vector<uint16_t> counts;
    // links to higher order contexts
    std::vector<table*> links;
    // accumulated counts
    uint32_t scale = uint32_t(0);

    /* -----------------------------------------------
    Recursively deletes all the tables pointed to in links.
    ----------------------------------------------- */
    ~table()
    {
        for (auto& link : links)
        {
            if (link != nullptr)
            {
                delete link;
            }
        }
    }

    /* -----------------------------------------------
    Checks if counts exist, creating it if it does not.
    ----------------------------------------------- */
    inline void check_counts()
    {
        // check if counts are available
        if (counts.empty())
        {
            // setup counts for current table
            counts.resize(2, uint16_t(1));
            // set scale
            scale = uint32_t(2);
        }
    }

    /* -----------------------------------------------
    Resizes the table by rightshifting each count by 1.
    ----------------------------------------------- */
    inline void rescale_table()
    {
        // Do nothing if counts is not set:
        if (!counts.empty())
        {
            // Scale the table by bitshifting each count, be careful not to set any count zero:
            counts[0] = std::max(uint16_t(1), uint16_t(counts[0] >> 1));
            counts[1] = std::max(uint16_t(1), uint16_t(counts[1] >> 1));
            scale = counts[0] + counts[1];
        }
    }

    /* -----------------------------------------------
    Recursively runs rescale_table on this and all linked contexts.
    ----------------------------------------------- */
    inline void recursive_flush()
    {
        for (auto& link : links)
        {
            if (link != nullptr)
            {
                link->recursive_flush();
            }
        }
        // rescale specific table
        rescale_table();
    }
};

// special table struct, used in in model_s,
// holding additional info for a speedier 'totalize_table'
struct table_s
{
    // counts for each symbol contained in the table
    std::vector<uint16_t> counts;
    // links to higher order contexts
    std::vector<table_s*> links;
    // speedup info
    uint16_t max_count = uint16_t(0);
    uint16_t max_symbol = uint16_t(0);

    /* -----------------------------------------------
    Recursively deletes all the tables pointed to in links.
    ----------------------------------------------- */
    ~table_s()
    {
        for (auto& link : links)
        {
            if (link != nullptr)
            {
                delete link;
            }
        }
    }

    /* -----------------------------------------------
    Resizes the table by rightshifting each count by 1.
    ----------------------------------------------- */
    inline void rescale_table()
    {
        // Nothing to do if counts has not been set.
        if (counts.empty())
        {
            return;
        }

        // now scale the table by bitshifting each count
        int lst_symbol = max_symbol;
        int i;
        for (i = 0; i < lst_symbol; i++)
        {
            counts[i] >>= 1; // Counts will not become negative since it is an unsigned type.
        }

        // also rescale tables max count
        max_count >>= 1;

        // seek for new last symbol
        for (i = lst_symbol - 1; i >= 0; i--)
        {
            if (counts[i] > 0)
            {
                break;
            }
        }
        max_symbol = i + 1;
    }

    /* -----------------------------------------------
    Recursively runs rescale_table on this and all linked contexts.
    ----------------------------------------------- */
    inline void recursive_flush()
    {
        for (auto& link : links)
        {
            if (link != nullptr)
            {
                link->recursive_flush();
            }
        }

        // rescale specific table
        rescale_table();
    }
};

class ArithmeticBitWriter
{
public:
    template <std::uint8_t bit>
    void write_bit();

    void write_n_zero_bits(std::size_t n);

    void write_n_one_bits(std::size_t n);

    void pad();

    std::vector<std::uint8_t> get_data() const;


private:
    std::vector<std::uint8_t> data_;
    std::uint8_t curr_byte_ = 0;
    std::size_t curr_bit_ = 0;
};


/* -----------------------------------------------
    class for arithmetic coding of data to/from iostream
    ----------------------------------------------- */

class ArithmeticEncoder
{
public:
    ArithmeticEncoder(Writer& writer);
    ~ArithmeticEncoder();
    void encode(symbol* s);

    void finalize();

private:

    // i/o variables

    bool finalized = false;
    Writer& writer_;
    std::unique_ptr<ArithmeticBitWriter> bitwriter_ = std::make_unique<ArithmeticBitWriter>();

    // arithmetic coding variables
    unsigned int ccode = 0;
    unsigned int clow = 0;
    unsigned int chigh = CODER_LIMIT100 - 1;
    unsigned int cstep = 0;
    unsigned int nrbits = 0;
};

class ArithmeticDecoder
{
public:
    ArithmeticDecoder(Reader& reader);
    ~ArithmeticDecoder() {}
    unsigned int decode_count(symbol* s);
    void decode(symbol* s);

private:
    unsigned char read_bit();

    // i/o variables
    Reader& reader_;
    unsigned char bbyte = 0;
    unsigned char cbit = 0;

    // arithmetic coding variables
    unsigned int ccode = 0;
    unsigned int clow = 0;
    unsigned int chigh = CODER_LIMIT100 - 1;
    unsigned int cstep = 0;
};


/* -----------------------------------------------
    universal statistical model for arithmetic coding
    ----------------------------------------------- */

class model_s
{
public:

    model_s(int max_s, int max_c, int max_o, int c_lim);
    ~model_s();

    void update_model(int symbol);
    void shift_context(int c);
    void flush_model();
    void exclude_symbols(int c);

    int  convert_int_to_symbol(int c, symbol* s);
    void get_symbol_scale(symbol* s);
    int  convert_symbol_to_int(uint32_t count, symbol* s);

private:

    inline void totalize_table(table_s* context);

    const int max_symbol;
    const int max_context;
    const int max_order;
    const int max_count;

    int current_order;
    int sb0_count;

    std::vector<uint32_t> totals;
    bool* scoreboard;
    std::vector<table_s*> contexts;
};


/* -----------------------------------------------
    binary statistical model for arithmetic coding
    ----------------------------------------------- */

class model_b
{
public:

    model_b(int max_c, int max_o, int c_lim);
    ~model_b();

    void update_model(int symbol);
    void shift_context(int c);
    void flush_model();

    int  convert_int_to_symbol(int c, symbol* s);
    void get_symbol_scale(symbol* s);
    int  convert_symbol_to_int(uint32_t count, symbol* s);

private:

    const int max_context;
    const int max_order;
    const int max_count;

    std::vector<table*> contexts;
};

// Base case for shifting an arbitrary number of contexts into the model.
template <typename M>
static void shift_model(M) {}

// Shift an arbitrary number of contexts into the model (at most max_c contexts).
template <typename M, typename C, typename... Cargs>
static void shift_model(M model, C context, Cargs ... contextList)
{
    model->shift_context(context);
    shift_model(model, contextList...);
}

/* -----------------------------------------------
    generic model_s encoder function
    ----------------------------------------------- */
static inline void encode_ari(ArithmeticEncoder* encoder, model_s* model, int c)
{
    symbol s;
    int esc;

    do
    {
        esc = model->convert_int_to_symbol(c, &s);
        encoder->encode(&s);
    }
    while (esc);
    model->update_model(c);
}

/* -----------------------------------------------
    generic model_s decoder function
    ----------------------------------------------- */
static inline int decode_ari(ArithmeticDecoder* decoder, model_s* model)
{
    symbol s;
    uint32_t count;
    int c;

    do
    {
        model->get_symbol_scale(&s);
        count = decoder->decode_count(&s);
        c = model->convert_symbol_to_int(count, &s);
        decoder->decode(&s);
    }
    while (c == ESCAPE_SYMBOL);
    model->update_model(c);

    return c;
}

/* -----------------------------------------------
    generic model_b encoder function
    ----------------------------------------------- */
static inline void encode_ari(ArithmeticEncoder* encoder, model_b* model, int c)
{
    symbol s;

    model->convert_int_to_symbol(c, &s);
    encoder->encode(&s);
    model->update_model(c);
}

/* -----------------------------------------------
    generic model_b decoder function
    ----------------------------------------------- */
static inline int decode_ari(ArithmeticDecoder* decoder, model_b* model)
{
    symbol s;

    model->get_symbol_scale(&s);
    uint32_t count = decoder->decode_count(&s);
    int c = model->convert_symbol_to_int(count, &s);
    decoder->decode(&s);
    model->update_model(c);

    return c;
}

#endif
