/*
 * decverb.c
 *
 * Decoding verbatim-bit blocks
 */
#include "decoder.h"


static long special_decode_verbatim_block(
    t_decoder_context   *context,
    long                BufPos,
    int                 amount_to_decode
)
{
	ulong	match_pos;
    long    bufpos_end;
	int		match_length;
	int		c;
	ulong	dec_bitbuf;
    byte    *dec_input_curpos;
    byte    *dec_end_input_pos;
	byte	*dec_mem_window;
	char	dec_bitcount;
	char	m;

	/*
	 * Store commonly used variables locally
	 */
	dec_bitcount	  = context->dec_bitcount;
	dec_bitbuf		  = context->dec_bitbuf;
	dec_input_curpos  = context->dec_input_curpos;
	dec_end_input_pos = context->dec_end_input_pos;
	dec_mem_window	  = context->dec_mem_window;

    bufpos_end = BufPos + amount_to_decode;

    /*
     * We may overflow by up to MAX_MATCH
     */
    while (BufPos < bufpos_end)
	{
		/* decode an item from the main tree */
		DECODE_MAIN_TREE(c);

		if ((c -= NUM_CHARS) < 0)
		{
	 		/*	it's a character */
			/* note: c - 256 == c if c is a byte */
            context->dec_mem_window[BufPos] = (byte) c;

            /* we know BufPos < bufpos_end here, so no need to check for overflow */
            context->dec_mem_window[context->dec_window_size+BufPos] = (byte) c;
            BufPos++;
		}
		else
		{
	 		/* get match length header */
			if ((match_length = c & NUM_PRIMARY_LENGTHS) == NUM_PRIMARY_LENGTHS)
			{
				/* get match length footer if necessary */
				DECODE_LEN_TREE_NOEOFCHECK(match_length);
			}

			/* get match position slot */
			m = c >> NL_SHIFT;

	  		/* read any extra bits for the match position */
			if (m > 2) 
			{
				if (m > 3) /* dec_extra_bits[m] != 0 */
				{
	    			GET_BITS17_NOEOFCHECK(dec_extra_bits[ m ], match_pos);
					match_pos += MP_POS_minus2[m];
				}
				else
				{
                    match_pos = 1; // MP_POS_minus2[3];
				}

				/*
				 * Add match base to "extra bits".  Our match base
				 * table has 2 subtracted from all the elements.
				 *
				 * This is because encoded positions 0,1,2 denote
				 * repeated offsets.  Encoded position 3 denotes
				 * a match 1 character away, 4 encodes 2 away, etc.  
				 * Hence the subtraction of 2, which has been
				 * incorporated into the table.
				 */

				/* update LRU repeated offset list */
				context->dec_last_matchpos_offset[2] = context->dec_last_matchpos_offset[1];
				context->dec_last_matchpos_offset[1] = context->dec_last_matchpos_offset[0];
				context->dec_last_matchpos_offset[0] = match_pos;
			}
			else
	 		{
				/* positions 0, 1, 2 denote repeated offsets */
				match_pos = context->dec_last_matchpos_offset[m];

				if (m)
				{
					context->dec_last_matchpos_offset[m] = context->dec_last_matchpos_offset[0];
					context->dec_last_matchpos_offset[0] = match_pos;
				}
	 		}

			/* match lengths range from 2...257 */
			match_length += MIN_MATCH; 

			/* copy match data */
			do
			{
				context->dec_mem_window[BufPos] = context->dec_mem_window[(BufPos-match_pos) & context->dec_window_mask];

                /* replicate bytes */
                if (BufPos < MAX_MATCH)
                    context->dec_mem_window[context->dec_window_size+BufPos] = context->dec_mem_window[BufPos];

				BufPos++;
			} while (--match_length > 0);
		}
	}

	context->dec_bitcount	  = dec_bitcount;
	context->dec_bitbuf		  = dec_bitbuf;
	context->dec_input_curpos = dec_input_curpos;

    return BufPos;
}


#ifdef ASM_DECODE_VERBATIM_BLOCK

long fast_decode_verbatim_block(
    t_decoder_context   *context,
    long                BufPos,
    int                 amount_to_decode
);

#else /* !ASM_DECODE_VERBATIM_BLOCK */

long fast_decode_verbatim_block(t_decoder_context *context, long BufPos, int amount_to_decode)
{
	ulong	match_pos;
	ulong	match_ptr;
	long	bufpos_end;
	long	decode_residue;
	int		match_length;
	int		c;
	ulong	dec_bitbuf;
    byte   *dec_input_curpos;
    byte   *dec_end_input_pos;
	byte	*dec_mem_window;
	char	dec_bitcount;
	char	m;

	/*
	 * Store commonly used variables locally
	 */
	dec_bitcount	  = context->dec_bitcount;
	dec_bitbuf		  = context->dec_bitbuf;
	dec_input_curpos  = context->dec_input_curpos;
	dec_end_input_pos = context->dec_end_input_pos;
	dec_mem_window	  = context->dec_mem_window;

	bufpos_end = BufPos + amount_to_decode;

	while (BufPos < bufpos_end)
	{
		/* decode an item from the main tree */
		DECODE_MAIN_TREE(c);

		if ((c -= NUM_CHARS) < 0)
		{
	 		/*	it's a character */
			/* note: c - 256 == c if c is a byte */
			context->dec_mem_window[BufPos++] = (byte) c;
		}
		else
		{
	 		/* get match length header */
			if ((match_length = c & NUM_PRIMARY_LENGTHS) == NUM_PRIMARY_LENGTHS)
			{
				/* get match length footer if necessary */
				DECODE_LEN_TREE_NOEOFCHECK(match_length);
			}

			/* get match position slot */
			m = c >> NL_SHIFT;

	  		/* read any extra bits for the match position */
			if (m > 2) 
			{
				if (m > 3) /* dec_extra_bits[m] != 0 */
				{
	    			GET_BITS17_NOEOFCHECK(dec_extra_bits[ m ], match_pos);
					match_pos += MP_POS_minus2[m];
				}
				else
				{
					match_pos = MP_POS_minus2[3];
				}

				/*
				 * Add match base to "extra bits".  Our match base
				 * table has 2 subtracted from all the elements.
				 *
				 * This is because encoded positions 0,1,2 denote
				 * repeated offsets.  Encoded position 3 denotes
				 * a match 1 character away, 4 encodes 2 away, etc.  
				 * Hence the subtraction of 2, which has been
				 * incorporated into the table.
				 */

				/* update LRU repeated offset list */
				context->dec_last_matchpos_offset[2] = context->dec_last_matchpos_offset[1];
				context->dec_last_matchpos_offset[1] = context->dec_last_matchpos_offset[0];
				context->dec_last_matchpos_offset[0] = match_pos;
			}
			else
	 		{
				/* positions 0, 1, 2 denote repeated offsets */
				match_pos = context->dec_last_matchpos_offset[m];

				if (m)
				{
					context->dec_last_matchpos_offset[m] = context->dec_last_matchpos_offset[0];
					context->dec_last_matchpos_offset[0] = match_pos;
				}
	 		}

			/* match lengths range from 2...257 */
			match_length += MIN_MATCH; 
			
            match_ptr = (BufPos - match_pos) & context->dec_window_mask;

			/* copy match data */
			do
			{
                context->dec_mem_window[BufPos++] = context->dec_mem_window[match_ptr++];
			} while (--match_length > 0);
		}
	}

	context->dec_bitcount	  = dec_bitcount;
	context->dec_bitbuf		  = dec_bitbuf;
	context->dec_input_curpos = dec_input_curpos;

	/* should be zero */
	decode_residue = BufPos - bufpos_end;

	BufPos &= context->dec_window_mask;
	context->dec_bufpos = BufPos;

	return decode_residue;
}
#endif /* ASM_DECODE_VERBATIM_BLOCK */


int decode_verbatim_block(t_decoder_context *context, long BufPos, int amount_to_decode)
{
    /*
     * Special case code when BufPos is near the beginning of the window;
     * we must properly update our MAX_MATCH wrapper bytes.
     */
    if (BufPos < MAX_MATCH)
    {
        long    new_bufpos;
        long    amount_to_slowly_decode;

        amount_to_slowly_decode = (((MAX_MATCH-BufPos) < (amount_to_decode)) ? (MAX_MATCH-BufPos) : (amount_to_decode));

        /*
         * It's ok to end up decoding more than we wanted if we
         * restricted it to decoding only MAX_MATCH; there's
         * no guarantee a match doesn't straddle MAX_MATCH
         */
        new_bufpos = special_decode_verbatim_block(
            context,
            BufPos,
            amount_to_slowly_decode
        );

        amount_to_decode -= (new_bufpos-BufPos);

        context->dec_bufpos = BufPos = new_bufpos;

        /*
         * Note: if amount_to_decode < 0 then we're in trouble
         */
        if (amount_to_decode <= 0)
            return amount_to_decode;
    }

    return fast_decode_verbatim_block(context, BufPos, amount_to_decode);
}