#include "arith.h"
#include "files.h"


/*
	Declare variables use for arithmetic compression
*/

static int under_flow_count = 0;
static unsigned int low = 0;
static unsigned int range = MAX_RANGE;
static unsigned int value = 0;

static unsigned int code_value = 0;
static unsigned int code_bit_count = 0;
static unsigned codesize = 0;


static int shift_count (unsigned);

static void put_bit (int);
static void put_bit_string (unsigned, int);
static void clear_under_flow (unsigned);

static unsigned get_bit_string (int);
static unsigned get_bit (void);



/*
	Initialize arithmetic coder at start of any compress/expand procedure
*/

void InitCoder (void)

{
	low = 0;
	range = MAX_RANGE;
}



/*
	Terminate arithmetic code operation
	Write bit string whose value is within active code range
	Force final byte to be output to file
*/

void CloseCoder (void)

{
	int i;

	put_bit (1);
	for (i = 0; i < 7; i++) put_bit (0);
	CloseOutputFile ();
}



/*
	Save state of arithmetic coder
	Allows state to be restored at a later time
*/

void SaveCoderState (struct coder_state *p)

{
	p -> low = low;
	p -> range = range;
	p -> uflow = under_flow_count;
	p -> bits = code_bit_count;
	p -> fpos = codesize;
}


/*
	Restore arithmetic coder state from saved value
	Repositin output file
	Set all internal values to their original state
*/

void RestoreCoderState (struct coder_state *p)

{
	int n;
	int cv;
	static unsigned char reset_mask [] =

	{
		0x00,  0x80,  0xC0,  0xE0,  0xF0,  0xF8,  0xFC,  0xFE,  0xFF,
	};

	n = codesize - p -> fpos;
	cv = ResetOutputPointer (n);

	low = p -> low;
	range = p -> range;

	under_flow_count = p -> uflow;
	code_bit_count = p -> bits;
	code_value = cv & reset_mask [code_bit_count];
	codesize -= n;
}


/*
	Estimate length of output code string
	Uses previously saved coder state
	Returns difference between saved position and current position
*/

int CodeLength (struct coder_state *csptr)

{
	int len;

	len = codesize - csptr -> fpos;
	len *= 8;
	len += under_flow_count - csptr -> uflow;
	len += code_bit_count - csptr -> bits;

	return len;
}


/*
	Arithmetic coder
	Encode symbol using input frequencies
	Output as many bits as possible to output file
	Update coder values for next symbol
*/

void EncodeArith (unsigned base, unsigned freq, unsigned cmax)

{
	unsigned long t;
	unsigned x;

	int n1, n2;

	t  = (long) range * (long) base;
	t += (long) base;
	low  += (unsigned) (t / cmax);
	x     = (unsigned) (t % cmax);

	t = (long) range * (long) freq;

	t += (long) (freq + x);
	t -= cmax;
	range = (unsigned) (t / cmax);

	n1 = shift_count ((low + range) ^ low);
	if (n1 && under_flow_count)
	{
		clear_under_flow ((low & 0x8000) != 0);
		low ^= 0x8000;
	}

	if (n1)
	{
		put_bit_string (low, n1);
		low <<= n1;
		range = ((range + 1) << n1) - 1;
	}

	if (range < MIN_RANGE)
	{
		n2 = shift_count (range - 1) - 1;

		under_flow_count += n2;
		low <<= n2;
		low &= 0x7FFF;
		range = ((range + 1) << n2) - 1;
	}
}


/*
	Send bit string based on underflow count
	Uses bit value followed by its complement

	generates: 0111... or 1000...
*/
	
static void clear_under_flow (unsigned bit)

{
	put_bit (bit);
	bit ^= 0x01;
	while (-- under_flow_count) put_bit (bit);
}



/*
	Send bit string
*/

static void put_bit_string (unsigned x, int count)

{
	while (count)
	{
		put_bit ((x & 0x8000) != 0);
		x <<= 1;
		count --;
	}
}



/*
	Write single bit to output file using internal buffer
*/

static void put_bit (int bit)

{
	static unsigned char mask [] =
	{
		0x80,	0x40,	0x20,	0x10,	0x08,	0x04,	0x02,	0x01,
	};

	if (bit) code_value |= mask [code_bit_count];
	if (++ code_bit_count == 8)
	{
		WriteOutputFile (code_value);
		code_value = 0;
		code_bit_count = 0;
		codesize ++;
	}
}



/*
	Determine number of leading zero bits on unsigned value
*/

static int shift_count (unsigned n)

{
	int i;

	i = 0;
	while ((n & 0x8000) == 0 && i < 16)
	{
		i ++;
		n <<= 1;
	}

	return i;
}



/*
	Read bit string from input stream
	Length is limited to word size
*/

static unsigned get_bit_string (int n)

{
	unsigned x = 0;

	while (n)
	{
		x <<= 1;
		x += get_bit ();
		n --;
	}

	return x;
}



/*
	Read a single bit from input stream
	Uses a one byte buffer for intermediate values
*/

static unsigned get_bit (void)

{
	int n;

	if (code_bit_count == 0)
	{
		code_bit_count = 8;
		n = ReadInputFile ();
		code_value = n >= 0 ? n : 0;
	}

	code_bit_count --;
	n = code_value & 0x80;
	code_value <<= 1;

	return n != 0;
}



/*
	Initialize arithmetic decode procedure
*/

void StartDecode (void)

{
	int i;

	value = 0;
	for (i = 0; i < 16; i ++)
	{
		value <<= 1;
		value |= get_bit ();
	}
}


/*
	Return value of next input code
	Input consists of total frequency for active symbol set
	Note that decoder must be updated with actual frequencies used
*/

int DecodeArith (unsigned cmax)

{
	unsigned long t;

	t = (long) (value - low + 1) * (long) cmax;
	t -= 1;
	t /= (long) range + 1;

	return (unsigned) t;
}


/*
	Update arithmetic decoder using actual symbol frequencies
	Read additional bits from input based on symbol values
*/

void UpdateDecoder (unsigned base, unsigned freq, unsigned cmax)

{
	unsigned long t;
	unsigned x, y;

	int n1, n2;

	t  = (long) range * (long) base;
	t += (long) base;
	x  = (unsigned) (t / cmax);
	y  = (unsigned) (t % cmax);

	low += x;

	t = (long) range * (long) freq;
	t += (long) (freq + y);
	t -= cmax;
	range = (unsigned) (t / cmax);

	n1 = shift_count ((low + range) ^ low);
	if (n1)
	{
		low <<= n1;
		range = ((range + 1) << n1) - 1;
		value <<= n1;
		value |= get_bit_string (n1);
	}

	if (range < MIN_RANGE)
	{
		n2 = shift_count (range - 1) - 1;
		value -= low;
		low <<= n2;
		low &= 0x7FFF;
		range = ((range + 1) << n2) - 1;
		value <<= n2;
		value |= get_bit_string (n2);
		value += low;
	}
}
