/*
 * 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;
    ulong   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 */
#ifdef TRACING
            TracingLiteral(BufPos, (byte) c);
#endif
            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;

#ifdef EXTRALONGMATCHES

            if ( match_length == MAX_MATCH ) {

                //
                //  See detailed explanation in decalign.c
                //

                ULONG ExtraMatchLength, ExtraMatchLengthResidue;

                GET_BITS_NOEOFCHECK( 9, ExtraMatchLength );

                if ( ExtraMatchLength & ( 1 << 8 )) {
                    if ( ExtraMatchLength & ( 1 << 7 )) {
                        if ( ExtraMatchLength & ( 1 << 6 )) {
                            ExtraMatchLength = ( ExtraMatchLength & (( 1 << 6 ) - 1 )) << 9;
                            GET_BITS_NOEOFCHECK( 9, ExtraMatchLengthResidue );
                            ExtraMatchLength |= ExtraMatchLengthResidue;
                            }
                        else {
                            ExtraMatchLength = ( ExtraMatchLength & (( 1 << 6 ) - 1 )) << 6;
                            GET_BITS_NOEOFCHECK( 6, ExtraMatchLengthResidue );
                            ExtraMatchLength |= ExtraMatchLengthResidue;
                            ExtraMatchLength += ( 1 << 8 ) + ( 1 << 10 );
                            }
                        }
                    else {
                        ExtraMatchLength = ( ExtraMatchLength & (( 1 << 7 ) - 1 )) << 3;
                        GET_BITS_NOEOFCHECK( 3, ExtraMatchLengthResidue );
                        ExtraMatchLength |= ExtraMatchLengthResidue;
                        ExtraMatchLength += ( 1 << 8 );
                        }
                    }

                match_length += ExtraMatchLength;
                }

#endif

#ifdef TRACING
            TracingMatch(BufPos,
                         BufPos - match_pos,
                         context->dec_window_size,
                         match_length,
                         m);
#endif

            /* 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) // what does this do?  Does it need to be more than MAX_MATCH now?
                    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 __cdecl 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;
    ulong   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 */
#ifdef TRACING
            TracingLiteral(BufPos, (byte) c);
#endif
            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;

#ifdef EXTRALONGMATCHES

            if ( match_length == MAX_MATCH ) {

                //
                //  See detailed explanation in decalign.c
                //

                ULONG ExtraMatchLength, ExtraMatchLengthResidue;

                GET_BITS_NOEOFCHECK( 9, ExtraMatchLength );

                if ( ExtraMatchLength & ( 1 << 8 )) {
                    if ( ExtraMatchLength & ( 1 << 7 )) {
                        if ( ExtraMatchLength & ( 1 << 6 )) {
                            ExtraMatchLength = ( ExtraMatchLength & (( 1 << 6 ) - 1 )) << 9;
                            GET_BITS_NOEOFCHECK( 9, ExtraMatchLengthResidue );
                            ExtraMatchLength |= ExtraMatchLengthResidue;
                            }
                        else {
                            ExtraMatchLength = ( ExtraMatchLength & (( 1 << 6 ) - 1 )) << 6;
                            GET_BITS_NOEOFCHECK( 6, ExtraMatchLengthResidue );
                            ExtraMatchLength |= ExtraMatchLengthResidue;
                            ExtraMatchLength += ( 1 << 8 ) + ( 1 << 10 );
                            }
                        }
                    else {
                        ExtraMatchLength = ( ExtraMatchLength & (( 1 << 7 ) - 1 )) << 3;
                        GET_BITS_NOEOFCHECK( 3, ExtraMatchLengthResidue );
                        ExtraMatchLength |= ExtraMatchLengthResidue;
                        ExtraMatchLength += ( 1 << 8 );
                        }
                    }

                match_length += ExtraMatchLength;
                }

#endif

            match_ptr = (BufPos - match_pos) & context->dec_window_mask;

#ifdef TRACING
            TracingMatch(BufPos,
                         BufPos - match_pos,
                         context->dec_window_size,
                         match_length,
                         m);
#endif

            /* 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 = min(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);
}