#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
