/*
 * block.c
 *
 * LZX block outputting
 */

#include "encoder.h"


/*
 * Internal function definitions
 */
static void do_block_output(
	t_encoder_context *context,
	long literal_to_end_at,
	long distance_to_end_at
);


static void do_block_output(
	t_encoder_context *context,
	long literal_to_end_at,
	long distance_to_end_at
)
{
	ulong			bytes_compressed;
	lzx_block_type	block_type;
	ulong			estimated_block_size;

	/*
	 * Calculate frequencies for all tree elements.
	 *
	 * How many uncompressed bytes does this account for? 
	 */
	bytes_compressed = get_block_stats(
		context,
		0, 
		0, 
		literal_to_end_at
	);

	/*
	 * Determine whether we wish to output a verbatim block or an
	 * aligned offset block
	 */
	block_type = get_aligned_stats(context, distance_to_end_at);

	/*
	 * Create trees from the frequency data 
	 */
	create_trees(context, true); /* we want to generate the codes too */

	/*
	 * Determine whether the block should be output as uncompressed
	 */
	estimated_block_size = estimate_compressed_block_size(context);

	if (estimated_block_size >= bytes_compressed)
	{
		if (context->enc_bufpos_at_last_block >= context->enc_earliest_window_data_remaining)
			block_type = BLOCKTYPE_UNCOMPRESSED;
	}

	output_bits(context, 3, (byte) block_type);

	/* output 24 bit number, number of bytes compressed here */
	output_bits(context, 8,  (bytes_compressed >> 16) & 255);
	output_bits(context, 8,  ((bytes_compressed >> 8) & 255));
	output_bits(context, 8,  (bytes_compressed & 255));

	if (block_type == BLOCKTYPE_VERBATIM)
	{
		encode_trees(context);
		encode_verbatim_block(context, literal_to_end_at);
		get_final_repeated_offset_states(context, distance_to_end_at);
	}
	else if (block_type == BLOCKTYPE_ALIGNED)
	{
		encode_aligned_tree(context);
		encode_trees(context);
		encode_aligned_block(context, literal_to_end_at);
		get_final_repeated_offset_states(context, distance_to_end_at);
	}
	else if (block_type == BLOCKTYPE_UNCOMPRESSED)
	{
		get_final_repeated_offset_states(context, distance_to_end_at);
		encode_uncompressed_block(context, context->enc_bufpos_at_last_block, bytes_compressed);
	}

	context->enc_bufpos_at_last_block += bytes_compressed;
}


/*
 * Returns the number of distances which correspond
 * to this number of literals
 */
ulong get_distances_from_literals(t_encoder_context *context, ulong literals)
{
	ulong	d = 0;
	ulong	i;

	for (i = 0; i < (literals >> 3); i++)
		d += context->enc_ones[ context->enc_ItemType[i] ];

	/*
	 * Handle remaining 0...7
	 */
	for (i = (literals & (~7)); i < literals; i++)
	{
		if (IsMatch(i))
			d++;
	}

	return d;
}


/*
 * Output a block
 *
 * If trees_only is true, then only the tree statistics are updated.
 */
void output_block(t_encoder_context *context)
{
	ulong	where_to_split;
	ulong	distances;

    //
    // We have now output a block.
    //
    // We set this here in case someone calls LCIFlushOutput, so that
    // we don't try to redo the first chunk of bytes in the file
    // (since we've been forced to output them)
    //
    context->enc_first_block = 0;

	(void) split_block(
		context,
		0,
		context->enc_literals,
		context->enc_distances,
		&where_to_split,
		&distances /* distances @ literal == where_to_split */
	);

	do_block_output(context, where_to_split, distances);

	if (where_to_split == context->enc_literals)
	{
		/*
		 * If we've output ALL of our literals, then clear the itemtype array
		 */
		memset(context->enc_ItemType, 0, MAX_LITERAL_ITEMS/8);

		context->enc_literals	= 0;
		context->enc_distances	= 0;
	}
	else
	{
		/*
		 * If we didn't output all of our literals, then move the literals
		 * and distances we didn't use, to the beginning of the list
		 */
		memcpy(
			&context->enc_ItemType[0], 
			&context->enc_ItemType[where_to_split/8], 
			&context->enc_ItemType[1+(context->enc_literals/8)] - &context->enc_ItemType[where_to_split/8]
		);

		memset(
			&context->enc_ItemType[1+(context->enc_literals-where_to_split)/8], 
			0, 
			&context->enc_ItemType[MAX_LITERAL_ITEMS/8] - &context->enc_ItemType[1+(context->enc_literals-where_to_split)/8]
		);

		memcpy(
			&context->enc_LitData[0],  
			&context->enc_LitData[where_to_split],
			context->enc_literals-where_to_split
		);

		memcpy(
			&context->enc_DistData[0], 
			&context->enc_DistData[distances], 
			sizeof(ulong)*(context->enc_distances-distances)
		);

		context->enc_literals  -= where_to_split;
		context->enc_distances -= distances;
	}

	fix_tree_cost_estimates(context);
}


void flush_output_bit_buffer(t_encoder_context *context)
{

	byte temp;

	if (context->enc_bitcount < 32)
	{
		temp = context->enc_bitcount-16;

		output_bits(context, temp, 0);
	}
}


/*
 * Estimate how much it would take to output the compressed
 * data left in the buffer
 */
long estimate_buffer_contents(t_encoder_context *context)
{
	long			estimated_block_size;

	/*
	 * Use frequency data sitting around from last tree creation
	 */
	create_trees(context, false); /* don't generate codes */

	estimated_block_size = estimate_compressed_block_size(context);

	/* so the optimal parser doesn't get confused */
	fix_tree_cost_estimates(context);

	return estimated_block_size;
}
