/**********************************************************************
  Copyright(c) 2011-2016 Intel Corporation All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in
      the documentation and/or other materials provided with the
      distribution.
    * Neither the name of Intel Corporation nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************************************/

#define ASM

#include <assert.h>
#include <string.h>
#include <wchar.h>
#ifdef _WIN32
#include <intrin.h>
#endif

#define MAX_WRITE_BITS_SIZE  8
#define FORCE_FLUSH          64
#define MIN_OBUF_SIZE        224
#define NON_EMPTY_BLOCK_SIZE 6
#define MAX_SYNC_FLUSH_SIZE  NON_EMPTY_BLOCK_SIZE + MAX_WRITE_BITS_SIZE

#include "huffman.h"
#include "bitbuf2.h"
#include "igzip_lib.h"
#include "crc.h"
#include "repeated_char_result.h"
#include "huff_codes.h"
#include "encode_df.h"
#include "igzip_level_buf_structs.h"
#include "igzip_checksums.h"
#include "igzip_wrapper.h"
#include "unaligned.h"

extern void
isal_deflate_hash_lvl0(uint16_t *, uint32_t, uint32_t, uint8_t *, uint32_t);
extern void
isal_deflate_hash_lvl1(uint16_t *, uint32_t, uint32_t, uint8_t *, uint32_t);
extern void
isal_deflate_hash_lvl2(uint16_t *, uint32_t, uint32_t, uint8_t *, uint32_t);
extern void
isal_deflate_hash_lvl3(uint16_t *, uint32_t, uint32_t, uint8_t *, uint32_t);
extern const uint8_t gzip_hdr[];
extern const uint32_t gzip_hdr_bytes;
extern const uint32_t gzip_trl_bytes;
extern const uint8_t zlib_hdr[];
extern const uint32_t zlib_hdr_bytes;
extern const uint32_t zlib_trl_bytes;
extern const struct isal_hufftables hufftables_default;
extern const struct isal_hufftables hufftables_static;

static uint32_t
write_stored_block(struct isal_zstream *stream);

static int
write_stream_header_stateless(struct isal_zstream *stream);
static void
write_stream_header(struct isal_zstream *stream);
static int
write_deflate_header_stateless(struct isal_zstream *stream);
static int
write_deflate_header_unaligned_stateless(struct isal_zstream *stream);

#define TYPE0_HDR_LEN     4
#define TYPE0_BLK_HDR_LEN 5
#define TYPE0_MAX_BLK_LEN 65535

void
isal_deflate_body(struct isal_zstream *stream);
void
isal_deflate_finish(struct isal_zstream *stream);

void
isal_deflate_icf_body(struct isal_zstream *stream);
void
isal_deflate_icf_finish_lvl1(struct isal_zstream *stream);
void
isal_deflate_icf_finish_lvl2(struct isal_zstream *stream);
void
isal_deflate_icf_finish_lvl3(struct isal_zstream *stream);
/*****************************************************************/

/* Forward declarations */
static inline void
reset_match_history(struct isal_zstream *stream);
static void
write_header(struct isal_zstream *stream, uint8_t *deflate_hdr, uint32_t deflate_hdr_count,
             uint32_t extra_bits_count, uint32_t next_state, uint32_t toggle_end_of_stream);
static void
write_trailer(struct isal_zstream *stream);

/*****************************************************************/

// isal_adler32_bam1 - adler with (B | A minus 1) storage

uint32_t
isal_adler32_bam1(uint32_t adler32, const unsigned char *start, uint64_t length)
{
        uint64_t a;

        /* Internally the checksum is being stored as B | (A-1) so crc and
         * addler have same init value */
        a = adler32 & 0xffff;
        a = (a == ADLER_MOD - 1) ? 0 : a + 1;
        adler32 = isal_adler32((adler32 & 0xffff0000) | a, start, length);
        a = (adler32 & 0xffff);
        a = (a == 0) ? ADLER_MOD - 1 : a - 1;

        return (adler32 & 0xffff0000) | a;
}

static void
update_checksum(struct isal_zstream *stream, uint8_t *start_in, uint64_t length)
{
        struct isal_zstate *state = &stream->internal_state;
        switch (stream->gzip_flag) {
        case IGZIP_GZIP:
        case IGZIP_GZIP_NO_HDR:
                state->crc = crc32_gzip_refl(state->crc, start_in, length);
                break;
        case IGZIP_ZLIB:
        case IGZIP_ZLIB_NO_HDR:
                state->crc = isal_adler32_bam1(state->crc, start_in, length);
                break;
        }
}

static void
sync_flush(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        uint64_t bits_to_write = 0xFFFF0000, bits_len;
        uint64_t bytes;
        int flush_size;

        if (stream->avail_out >= 8) {
                set_buf(&state->bitbuf, stream->next_out, stream->avail_out);

                flush_size = (-(state->bitbuf.m_bit_count + 3)) % 8;

                bits_to_write <<= flush_size + 3;
                bits_len = 32 + flush_size + 3;

                state->state = ZSTATE_NEW_HDR;
                state->has_eob = 0;

                write_bits(&state->bitbuf, bits_to_write, bits_len);

                bytes = buffer_used(&state->bitbuf);
                stream->next_out = buffer_ptr(&state->bitbuf);
                stream->avail_out -= bytes;
                stream->total_out += bytes;

                if (stream->flush == FULL_FLUSH) {
                        /* Clear match history so there are no cross
                         * block length distance pairs */
                        state->has_hist = IGZIP_NO_HIST;
                }
        }
}

static void
flush_write_buffer(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        int bytes = 0;
        if (stream->avail_out >= 8) {
                set_buf(&state->bitbuf, stream->next_out, stream->avail_out);
                flush(&state->bitbuf);
                stream->next_out = buffer_ptr(&state->bitbuf);
                bytes = buffer_used(&state->bitbuf);
                stream->avail_out -= bytes;
                stream->total_out += bytes;
                state->state = ZSTATE_NEW_HDR;
        }
}

static void
flush_icf_block(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;
        struct BitBuf2 *write_buf = &state->bitbuf;
        struct deflate_icf *icf_buf_encoded_next;

        set_buf(write_buf, stream->next_out, stream->avail_out);

        icf_buf_encoded_next =
                encode_deflate_icf(level_buf->icf_buf_start + state->count, level_buf->icf_buf_next,
                                   write_buf, &level_buf->encode_tables);

        state->count = icf_buf_encoded_next - level_buf->icf_buf_start;
        stream->next_out = buffer_ptr(write_buf);
        stream->total_out += buffer_used(write_buf);
        stream->avail_out -= buffer_used(write_buf);

        if (level_buf->icf_buf_next <= icf_buf_encoded_next) {
                state->count = 0;
                if (stream->avail_in == 0 && stream->end_of_stream)
                        state->state = ZSTATE_TRL;
                else if (stream->avail_in == 0 && stream->flush != NO_FLUSH)
                        state->state = ZSTATE_SYNC_FLUSH;
                else
                        state->state = ZSTATE_NEW_HDR;
        }
}

static int
check_level_req(struct isal_zstream *stream)
{
        if (stream->level == 0)
                return 0;

        if (stream->level_buf == NULL)
                return ISAL_INVALID_LEVEL_BUF;

        switch (stream->level) {
        case 3:
                if (stream->level_buf_size < ISAL_DEF_LVL3_MIN)
                        return ISAL_INVALID_LEVEL;
                break;

        case 2:
                if (stream->level_buf_size < ISAL_DEF_LVL2_MIN)
                        return ISAL_INVALID_LEVEL;
                break;
        case 1:
                if (stream->level_buf_size < ISAL_DEF_LVL1_MIN)
                        return ISAL_INVALID_LEVEL;
                break;
        default:
                return ISAL_INVALID_LEVEL;
        }

        return 0;
}

static int
init_hash8k_buf(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;
        state->has_level_buf_init = 1;
        return sizeof(struct level_buf) - MAX_LVL_BUF_SIZE + sizeof(level_buf->hash8k);
}

static int
init_hash_hist_buf(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;
        state->has_level_buf_init = 1;
        return sizeof(struct level_buf) - MAX_LVL_BUF_SIZE + sizeof(level_buf->hash_hist);
}

static int
init_hash_map_buf(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;
        if (!state->has_level_buf_init) {
                level_buf->hash_map.matches_next = level_buf->hash_map.matches;
                level_buf->hash_map.matches_end = level_buf->hash_map.matches;
        }
        state->has_level_buf_init = 1;

        return sizeof(struct level_buf) - MAX_LVL_BUF_SIZE + sizeof(level_buf->hash_map);
}

/* returns the size of the level specific buffer */
static int
init_lvlX_buf(struct isal_zstream *stream)
{
        switch (stream->level) {
        case 3:
                return init_hash_map_buf(stream);
        case 2:
                return init_hash_hist_buf(stream);
        default:
                return init_hash8k_buf(stream);
        }
}

static void
init_new_icf_block(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;
        int level_struct_size;

        level_struct_size = init_lvlX_buf(stream);

        state->block_next = state->block_end;
        level_buf->icf_buf_start = (struct deflate_icf *) (stream->level_buf + level_struct_size);

        level_buf->icf_buf_next = level_buf->icf_buf_start;
        level_buf->icf_buf_avail_out =
                stream->level_buf_size - level_struct_size - sizeof(struct deflate_icf);

        memset(&level_buf->hist, 0, sizeof(struct isal_mod_hist));
        state->state = ZSTATE_BODY;
}

static int
are_buffers_empty_hashX(struct isal_zstream *stream)
{
        return !stream->avail_in;
}

static int
are_buffers_empty_hash_map(struct isal_zstream *stream)
{
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;

        return (!stream->avail_in &&
                level_buf->hash_map.matches_next >= level_buf->hash_map.matches_end);
}

static int
are_buffers_empty(struct isal_zstream *stream)
{

        switch (stream->level) {
        case 3:
                return are_buffers_empty_hash_map(stream);
        case 2:
                return are_buffers_empty_hashX(stream);
        default:
                return are_buffers_empty_hashX(stream);
        }
}

static void
create_icf_block_hdr(struct isal_zstream *stream, uint8_t *start_in)
{
        struct isal_zstate *state = &stream->internal_state;
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;
        struct BitBuf2 *write_buf = &state->bitbuf;
        struct BitBuf2 write_buf_tmp;
        uint32_t out_size = stream->avail_out;
        uint32_t avail_output, block_start_offset;
        uint8_t *end_out = stream->next_out + out_size;
        uint64_t cur_in_processed;
        uint64_t bit_count;
        uint64_t block_in_size = state->block_end - state->block_next;
        uint64_t block_size;
        int buffer_header = 0;

        memcpy(&write_buf_tmp, write_buf, sizeof(struct BitBuf2));

        /* Calculate the bytes required to store a type 0 block. Need to account
         * for bits stored in the bitbuf. Since 3 bits correspond to the deflate
         * type 0 header, we need to add one byte more when the number of bits
         * is at least 6 mod 8. */
        block_size = (TYPE0_BLK_HDR_LEN) *
                             ((block_in_size + TYPE0_MAX_BLK_LEN - 1) / TYPE0_MAX_BLK_LEN) +
                     block_in_size;
        block_size = block_size ? block_size : TYPE0_BLK_HDR_LEN;
        block_size += (write_buf->m_bit_count + 2) / 8;

        /* Write EOB in icf_buf */
        level_buf->hist.ll_hist[256] = 1;
        level_buf->icf_buf_next->lit_len = 0x100;
        level_buf->icf_buf_next->lit_dist = NULL_DIST_SYM;
        level_buf->icf_buf_next->dist_extra = 0;
        level_buf->icf_buf_next++;

        state->has_eob_hdr = (stream->end_of_stream && are_buffers_empty(stream)) ? 1 : 0;

        if (end_out - stream->next_out >= ISAL_DEF_MAX_HDR_SIZE) {
                /* Assumes ISAL_DEF_MAX_HDR_SIZE is large enough to contain a
                 * max length header and a gzip header */
                if (stream->gzip_flag == IGZIP_GZIP || stream->gzip_flag == IGZIP_ZLIB)
                        write_stream_header_stateless(stream);
                set_buf(write_buf, stream->next_out, stream->avail_out);
                buffer_header = 0;

        } else {
                /* Start writing into temporary buffer */
                set_buf(write_buf, level_buf->deflate_hdr, ISAL_DEF_MAX_HDR_SIZE);
                buffer_header = 1;
        }

        bit_count = create_hufftables_icf(write_buf, &level_buf->encode_tables, &level_buf->hist,
                                          state->has_eob_hdr);

        /* Assumes that type 0 block has size less than 4G */
        block_start_offset = (stream->total_in - state->block_next);
        cur_in_processed = stream->next_in - start_in;
        avail_output =
                stream->avail_out + sizeof(state->buffer) - (stream->total_in - state->block_end);

        if (bit_count / 8 >= block_size && cur_in_processed >= block_start_offset &&
            block_size <= avail_output) {
                /* Reset stream for writing out a type0 block */
                state->has_eob_hdr = 0;
                memcpy(write_buf, &write_buf_tmp, sizeof(struct BitBuf2));
                state->state = ZSTATE_TYPE0_HDR;

        } else if (buffer_header) {
                /* Setup stream to write out a buffered header */
                level_buf->deflate_hdr_count = buffer_used(write_buf);
                level_buf->deflate_hdr_extra_bits = write_buf->m_bit_count;
                flush(write_buf);
                memcpy(write_buf, &write_buf_tmp, sizeof(struct BitBuf2));
                write_buf->m_bits = 0;
                write_buf->m_bit_count = 0;
                state->state = ZSTATE_HDR;

        } else {
                stream->next_out = buffer_ptr(write_buf);
                stream->total_out += buffer_used(write_buf);
                stream->avail_out -= buffer_used(write_buf);
                state->state = ZSTATE_FLUSH_ICF_BUFFER;
        }
}

static void
isal_deflate_pass(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        struct isal_hufftables *hufftables = stream->hufftables;
        uint8_t *start_in = stream->next_in;

        if (state->state == ZSTATE_NEW_HDR || state->state == ZSTATE_HDR) {
                if (state->count == 0)
                        /* Assume the final header is being written since the header
                         * stored in hufftables is the final header. */
                        state->has_eob_hdr = 1;
                write_header(stream, hufftables->deflate_hdr, hufftables->deflate_hdr_count,
                             hufftables->deflate_hdr_extra_bits, ZSTATE_BODY,
                             !stream->end_of_stream);
        }

        if (state->state == ZSTATE_BODY)
                isal_deflate_body(stream);

        if (state->state == ZSTATE_FLUSH_READ_BUFFER)
                isal_deflate_finish(stream);
        if (state->state == ZSTATE_SYNC_FLUSH)
                sync_flush(stream);

        if (state->state == ZSTATE_FLUSH_WRITE_BUFFER)
                flush_write_buffer(stream);

        if (stream->gzip_flag)
                update_checksum(stream, start_in, stream->next_in - start_in);

        if (state->state == ZSTATE_TRL)
                write_trailer(stream);
}

static void
isal_deflate_icf_finish(struct isal_zstream *stream)
{
        switch (stream->level) {
        case 3:
                isal_deflate_icf_finish_lvl3(stream);
                break;
        case 2:
                isal_deflate_icf_finish_lvl2(stream);
                break;
        default:
                isal_deflate_icf_finish_lvl1(stream);
        }
}

static void
isal_deflate_icf_pass(struct isal_zstream *stream, uint8_t *inbuf_start)
{
        uint8_t *start_in = stream->next_in;
        struct isal_zstate *state = &stream->internal_state;
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;

        do {
                if (state->state == ZSTATE_NEW_HDR)
                        init_new_icf_block(stream);

                if (state->state == ZSTATE_BODY)
                        isal_deflate_icf_body(stream);

                if (state->state == ZSTATE_FLUSH_READ_BUFFER)
                        isal_deflate_icf_finish(stream);

                if (state->state == ZSTATE_CREATE_HDR)
                        create_icf_block_hdr(stream, inbuf_start);

                if (state->state == ZSTATE_HDR)
                        /* Note that the header may be prepended by the
                         * remaining bits in the previous block, as such the
                         * toggle header flag cannot be used */
                        write_header(stream, level_buf->deflate_hdr, level_buf->deflate_hdr_count,
                                     level_buf->deflate_hdr_extra_bits, ZSTATE_FLUSH_ICF_BUFFER, 0);

                if (state->state == ZSTATE_FLUSH_ICF_BUFFER)
                        flush_icf_block(stream);

                if (state->state == ZSTATE_TYPE0_HDR || state->state == ZSTATE_TYPE0_BODY) {
                        if (stream->gzip_flag == IGZIP_GZIP || stream->gzip_flag == IGZIP_ZLIB)
                                write_stream_header(stream);
                        write_stored_block(stream);
                }

        } while (state->state == ZSTATE_NEW_HDR);

        if (state->state == ZSTATE_SYNC_FLUSH)
                sync_flush(stream);

        if (state->state == ZSTATE_FLUSH_WRITE_BUFFER)
                flush_write_buffer(stream);

        if (stream->gzip_flag)
                update_checksum(stream, start_in, stream->next_in - start_in);

        if (state->state == ZSTATE_TRL)
                write_trailer(stream);
}

static void
isal_deflate_int(struct isal_zstream *stream, uint8_t *start_in)
{
        struct isal_zstate *state = &stream->internal_state;
        uint32_t size;

        /* Move data from temporary output buffer to output buffer */
        if (state->state >= ZSTATE_TMP_OFFSET) {
                size = state->tmp_out_end - state->tmp_out_start;
                if (size > stream->avail_out)
                        size = stream->avail_out;
                memcpy(stream->next_out, state->tmp_out_buff + state->tmp_out_start, size);
                stream->next_out += size;
                stream->avail_out -= size;
                stream->total_out += size;
                state->tmp_out_start += size;

                if (state->tmp_out_start == state->tmp_out_end)
                        state->state -= ZSTATE_TMP_OFFSET;

                if (stream->avail_out == 0 ||
                    state->state == ZSTATE_END
                    // or do not write out empty blocks since the outbuffer was processed
                    || (state->state == ZSTATE_NEW_HDR && stream->avail_out == 0))
                        return;
        }
        assert(state->tmp_out_start == state->tmp_out_end);

        if (stream->level == 0)
                isal_deflate_pass(stream);
        else
                isal_deflate_icf_pass(stream, start_in);

        /* Fill temporary output buffer then complete filling output buffer */
        if (stream->avail_out > 0 && stream->avail_out < 8 && state->state != ZSTATE_NEW_HDR) {
                uint8_t *next_out;
                uint32_t avail_out;
                uint32_t total_out;

                next_out = stream->next_out;
                avail_out = stream->avail_out;
                total_out = stream->total_out;

                stream->next_out = state->tmp_out_buff;
                stream->avail_out = sizeof(state->tmp_out_buff);
                stream->total_out = 0;

                if (stream->level == 0)
                        isal_deflate_pass(stream);
                else
                        isal_deflate_icf_pass(stream, start_in);

                state->tmp_out_start = 0;
                state->tmp_out_end = stream->total_out;

                stream->next_out = next_out;
                stream->avail_out = avail_out;
                stream->total_out = total_out;
                if (state->tmp_out_end) {
                        size = state->tmp_out_end;
                        if (size > stream->avail_out)
                                size = stream->avail_out;
                        memcpy(stream->next_out, state->tmp_out_buff, size);
                        stream->next_out += size;
                        stream->avail_out -= size;
                        stream->total_out += size;
                        state->tmp_out_start += size;
                        if (state->tmp_out_start != state->tmp_out_end)
                                state->state += ZSTATE_TMP_OFFSET;
                }
        }
}

static void
write_constant_compressed_stateless(struct isal_zstream *stream, uint32_t repeated_length)
{
        /* Assumes repeated_length is at least 1.
         * Assumes the input end_of_stream is either 0 or 1. */
        struct isal_zstate *state = &stream->internal_state;
        uint32_t rep_bits = ((repeated_length - 1) / 258) * 2;
        uint32_t rep_bytes = rep_bits / 8;
        uint32_t rep_extra = (repeated_length - 1) % 258;
        uint32_t bytes;
        uint32_t repeated_char = *stream->next_in;
        uint8_t *start_in = stream->next_in;

        /* Guarantee there is enough space for the header even in the worst case */
        if (stream->avail_out < HEADER_LENGTH + MAX_FIXUP_CODE_LENGTH + rep_bytes + 8)
                return;

        /* Assumes the repeated char is either 0 or 0xFF. */
        memcpy(stream->next_out, repeated_char_header[repeated_char & 1], HEADER_LENGTH);

        if (stream->avail_in == repeated_length && stream->end_of_stream > 0) {
                stream->next_out[0] |= 1;
                state->has_eob_hdr = 1;
                state->has_eob = 1;
                state->state = ZSTATE_TRL;
        } else {
                state->state = ZSTATE_NEW_HDR;
        }

        memset(stream->next_out + HEADER_LENGTH, 0, rep_bytes);
        stream->avail_out -= HEADER_LENGTH + rep_bytes;
        stream->next_out += HEADER_LENGTH + rep_bytes;
        stream->total_out += HEADER_LENGTH + rep_bytes;

        set_buf(&state->bitbuf, stream->next_out, stream->avail_out);

        /* These two lines are basically a modified version of init. */
        state->bitbuf.m_bits = 0;
        state->bitbuf.m_bit_count = rep_bits % 8;

        /* Add smaller repeat codes as necessary. Code280 can describe repeat
         * lengths of 115-130 bits. Code10 can describe repeat lengths of 10
         * bits. If more than 230 bits, fill code with two code280s. Else if
         * more than 115 repeats, fill with code10s until one code280 can
         * finish the rest of the repeats. Else, fill with code10s and
         * literals */
        if (rep_extra > 115) {
                while (rep_extra > 130 && rep_extra < 230) {
                        write_bits(&state->bitbuf, CODE_10, CODE_10_LENGTH);
                        rep_extra -= 10;
                }

                if (rep_extra >= 230) {
                        write_bits(&state->bitbuf,
                                   CODE_280 | ((rep_extra / 2 - 115) << CODE_280_LENGTH),
                                   CODE_280_TOTAL_LENGTH);
                        rep_extra -= rep_extra / 2;
                }

                write_bits(&state->bitbuf, CODE_280 | ((rep_extra - 115) << CODE_280_LENGTH),
                           CODE_280_TOTAL_LENGTH);

        } else {
                while (rep_extra >= 10) {

                        write_bits(&state->bitbuf, CODE_10, CODE_10_LENGTH);
                        rep_extra -= 10;
                }

                for (; rep_extra > 0; rep_extra--)
                        write_bits(&state->bitbuf, CODE_LIT, CODE_LIT_LENGTH);
        }

        write_bits(&state->bitbuf, END_OF_BLOCK, END_OF_BLOCK_LEN);

        stream->next_in += repeated_length;
        stream->avail_in -= repeated_length;
        stream->total_in += repeated_length;
        state->block_end += repeated_length;

        bytes = buffer_used(&state->bitbuf);
        stream->next_out = buffer_ptr(&state->bitbuf);
        stream->avail_out -= bytes;
        stream->total_out += bytes;

        if (stream->gzip_flag)
                update_checksum(stream, start_in, stream->next_in - start_in);

        return;
}

static int
detect_repeated_char_length(uint8_t *in, uint32_t length)
{
        /* This currently assumes the first 8 bytes are the same character.
         * This won't work effectively if the input stream isn't aligned well. */
        uint8_t *p_8, *end = in + length;
        uint64_t *p_64 = (uint64_t *) in;
        uint64_t w = *p_64;
        uint8_t c = (uint8_t) w;

        for (; (p_64 <= (uint64_t *) (end - 8)) && (w == *p_64); p_64++)
                ;

        p_8 = (uint8_t *) p_64;

        for (; (p_8 < end) && (c == *p_8); p_8++)
                ;

        return p_8 - in;
}

static int
isal_deflate_int_stateless(struct isal_zstream *stream)
{
        uint32_t repeat_length;
        struct isal_zstate *state = &stream->internal_state;

        if (stream->gzip_flag == IGZIP_GZIP || stream->gzip_flag == IGZIP_ZLIB)
                if (write_stream_header_stateless(stream))
                        return STATELESS_OVERFLOW;

        if (stream->avail_in >= 8 && (load_native_u64(stream->next_in) == 0 ||
                                      load_native_u64(stream->next_in) == ~(uint64_t) 0)) {
                repeat_length = detect_repeated_char_length(stream->next_in, stream->avail_in);

                if (stream->avail_in == repeat_length || repeat_length >= MIN_REPEAT_LEN)
                        write_constant_compressed_stateless(stream, repeat_length);
        }

        if (stream->level == 0) {
                if (state->state == ZSTATE_NEW_HDR || state->state == ZSTATE_HDR) {
                        write_deflate_header_unaligned_stateless(stream);
                        if (state->state == ZSTATE_NEW_HDR || state->state == ZSTATE_HDR)
                                return STATELESS_OVERFLOW;

                        reset_match_history(stream);
                }

                isal_deflate_pass(stream);

        } else if (stream->level <= ISAL_DEF_MAX_LEVEL) {
                if (state->state == ZSTATE_NEW_HDR || state->state == ZSTATE_HDR)
                        reset_match_history(stream);

                state->count = 0;
                isal_deflate_icf_pass(stream, stream->next_in);
        }

        if (state->state == ZSTATE_END ||
            (state->state == ZSTATE_NEW_HDR && stream->flush == FULL_FLUSH))
                return COMP_OK;
        else
                return STATELESS_OVERFLOW;
}

static void
write_type0_header(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        uint64_t stored_blk_hdr;
        uint32_t copy_size;
        uint32_t memcpy_len, avail_in;
        uint32_t block_in_size = state->block_end - state->block_next;
        uint32_t block_next_offset;
        struct BitBuf2 *bitbuf = &stream->internal_state.bitbuf;

        if (block_in_size > TYPE0_MAX_BLK_LEN) {
                stored_blk_hdr = 0xFFFF;
                copy_size = TYPE0_MAX_BLK_LEN;
        } else {
                stored_blk_hdr = ~block_in_size;
                stored_blk_hdr <<= 16;
                stored_blk_hdr |= (block_in_size & 0xFFFF);
                copy_size = block_in_size;

                /* Handle BFINAL bit */
                block_next_offset = stream->total_in - state->block_next;
                avail_in = stream->avail_in + block_next_offset;
                if (stream->end_of_stream && avail_in == block_in_size)
                        stream->internal_state.has_eob_hdr = 1;
        }

        if (bitbuf->m_bit_count == 0 && stream->avail_out >= TYPE0_HDR_LEN + 1) {
                stored_blk_hdr = stored_blk_hdr << 8;
                stored_blk_hdr |= stream->internal_state.has_eob_hdr;
                memcpy_len = TYPE0_HDR_LEN + 1;
                stored_blk_hdr = to_le64(stored_blk_hdr);
                memcpy(stream->next_out, &stored_blk_hdr, memcpy_len);
        } else if (stream->avail_out >= 8) {
                set_buf(bitbuf, stream->next_out, stream->avail_out);
                write_bits_flush(bitbuf, stream->internal_state.has_eob_hdr, 3);
                stream->next_out = buffer_ptr(bitbuf);
                stream->total_out += buffer_used(bitbuf);
                stream->avail_out -= buffer_used(bitbuf);
                memcpy_len = TYPE0_HDR_LEN;
                stored_blk_hdr = to_le64(stored_blk_hdr);
                memcpy(stream->next_out, &stored_blk_hdr, memcpy_len);
        } else {
                stream->internal_state.has_eob_hdr = 0;
                return;
        }

        stream->next_out += memcpy_len;
        stream->avail_out -= memcpy_len;
        stream->total_out += memcpy_len;
        stream->internal_state.state = ZSTATE_TYPE0_BODY;

        stream->internal_state.count = copy_size;
}

static uint32_t
write_stored_block(struct isal_zstream *stream)
{
        uint32_t copy_size, avail_in, block_next_offset;
        uint8_t *next_in;
        struct isal_zstate *state = &stream->internal_state;

        do {
                if (state->state == ZSTATE_TYPE0_HDR) {
                        write_type0_header(stream);
                        if (state->state == ZSTATE_TYPE0_HDR)
                                break;
                }

                assert(state->count <= state->block_end - state->block_next);
                copy_size = state->count;

                block_next_offset = stream->total_in - state->block_next;
                next_in = stream->next_in - block_next_offset;
                avail_in = stream->avail_in + block_next_offset;

                if (copy_size > stream->avail_out || copy_size > avail_in) {
                        state->count = copy_size;
                        copy_size = (stream->avail_out <= avail_in) ? stream->avail_out : avail_in;

                        memcpy(stream->next_out, next_in, copy_size);
                        state->count -= copy_size;
                } else {
                        memcpy(stream->next_out, next_in, copy_size);

                        state->count = 0;
                        state->state = ZSTATE_TYPE0_HDR;
                }

                state->block_next += copy_size;
                stream->next_out += copy_size;
                stream->avail_out -= copy_size;
                stream->total_out += copy_size;

                if (state->block_next == state->block_end) {
                        state->state = state->has_eob_hdr ? ZSTATE_TRL : ZSTATE_NEW_HDR;
                        if (stream->flush == FULL_FLUSH && state->state == ZSTATE_NEW_HDR &&
                            are_buffers_empty(stream)) {
                                /* Clear match history so there are no cross
                                 * block length distance pairs */
                                reset_match_history(stream);
                        }
                }
        } while (state->state == ZSTATE_TYPE0_HDR);

        return state->block_end - state->block_next;
}

static inline void
reset_match_history(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;
        uint16_t *hash_table;
        uint32_t hash_table_size;

        hash_table_size = 2 * (state->hash_mask + 1);

        switch (stream->level) {
        case 3:
                hash_table = level_buf->lvl3.hash_table;
                break;
        case 2:
                hash_table = level_buf->lvl2.hash_table;
                break;
        case 1:
                hash_table = level_buf->lvl1.hash_table;
                break;
        default:
                hash_table = state->head;
        }

        state->has_hist = IGZIP_NO_HIST;

        /* There is definitely more than 16 bytes in the hash table. Set this
         * minimum to avoid a wmemset of size 0 */
        if (hash_table_size <= sizeof(wchar_t))
                hash_table_size = sizeof(wchar_t);

        if (sizeof(wchar_t) == 2) {
                uint16_t hash_init_val;

                hash_init_val = stream->total_in & 0xffff;
                wmemset((wchar_t *) hash_table, hash_init_val, hash_table_size / sizeof(wchar_t));

        } else if (sizeof(wchar_t) == 4) {
                uint32_t hash_init_val;
                int rep_bits;

                hash_init_val = stream->total_in & 0xffff;
                for (rep_bits = sizeof(uint16_t) * 8; rep_bits < sizeof(wchar_t) * 8; rep_bits *= 2)
                        hash_init_val |= hash_init_val << rep_bits;

                wmemset((wchar_t *) hash_table, hash_init_val, hash_table_size / sizeof(wchar_t));
        } else {
                if ((stream->total_in & 0xFFFF) == 0)
                        memset(hash_table, 0, hash_table_size);
                else {
                        int i;
                        for (i = 0; i < hash_table_size / 2; i++) {
                                hash_table[i] = (uint16_t) (stream->total_in);
                        }
                }
        }
}

static void inline set_dist_mask(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        uint32_t hist_size;

        if (stream->hist_bits > ISAL_DEF_MAX_HIST_BITS || stream->hist_bits == 0)
                stream->hist_bits = ISAL_DEF_MAX_HIST_BITS;

        hist_size = (1 << (stream->hist_bits));
        state->dist_mask = hist_size - 1;

        if (IGZIP_HIST_SIZE < ISAL_DEF_HIST_SIZE && state->dist_mask > IGZIP_HIST_SIZE - 1)
                state->dist_mask = IGZIP_HIST_SIZE - 1;
}

static void inline set_hash_mask(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;

        switch (stream->level) {
        case 3:
                state->hash_mask = LVL3_HASH_MASK;
                break;
        case 2:
                state->hash_mask = LVL2_HASH_MASK;
                break;
        case 1:
                state->hash_mask = LVL1_HASH_MASK;
                break;
        case 0:
                state->hash_mask = LVL0_HASH_MASK;
        }
}

void
isal_deflate_init(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;

        stream->total_in = 0;
        stream->total_out = 0;
        stream->hufftables = (struct isal_hufftables *) &hufftables_default;
        stream->level = 0;
        stream->level_buf = NULL;
        stream->level_buf_size = 0;
        stream->end_of_stream = 0;
        stream->flush = NO_FLUSH;
        stream->gzip_flag = 0;
        stream->hist_bits = 0;

        state->block_next = 0;
        state->block_end = 0;
        state->b_bytes_valid = 0;
        state->b_bytes_processed = 0;
        state->total_in_start = 0;
        state->has_wrap_hdr = 0;
        state->has_eob = 0;
        state->has_eob_hdr = 0;
        state->has_hist = IGZIP_NO_HIST;
        state->has_level_buf_init = 0;
        state->state = ZSTATE_NEW_HDR;
        state->count = 0;

        state->tmp_out_start = 0;
        state->tmp_out_end = 0;

        init(&state->bitbuf);

        state->crc = 0;

        return;
}

void
isal_deflate_reset(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;

        stream->total_in = 0;
        stream->total_out = 0;

        state->block_next = 0;
        state->block_end = 0;
        state->b_bytes_valid = 0;
        state->b_bytes_processed = 0;
        state->total_in_start = 0;
        state->has_wrap_hdr = 0;
        state->has_eob = 0;
        state->has_level_buf_init = 0;
        state->has_eob_hdr = 0;
        state->has_hist = IGZIP_NO_HIST;
        state->state = ZSTATE_NEW_HDR;
        state->count = 0;

        state->tmp_out_start = 0;
        state->tmp_out_end = 0;

        init(&state->bitbuf);

        state->crc = 0;
}

void
isal_gzip_header_init(struct isal_gzip_header *gz_hdr)
{
        gz_hdr->text = 0;
        gz_hdr->time = 0;
        gz_hdr->xflags = 0;
        gz_hdr->os = 0xff;
        gz_hdr->extra = NULL;
        gz_hdr->extra_buf_len = 0;
        gz_hdr->extra_len = 0;
        gz_hdr->name = NULL;
        gz_hdr->name_buf_len = 0;
        gz_hdr->comment = NULL;
        gz_hdr->comment_buf_len = 0;
        gz_hdr->hcrc = 0;
        gz_hdr->flags = 0;
}

void
isal_zlib_header_init(struct isal_zlib_header *z_hdr)
{
        z_hdr->info = 0;
        z_hdr->level = 0;
        z_hdr->dict_id = 0;
        z_hdr->dict_flag = 0;
}

uint32_t
isal_write_gzip_header(struct isal_zstream *stream, struct isal_gzip_header *gz_hdr)
{
        uint32_t flags = 0, hcrc, hdr_size = GZIP_HDR_BASE;
        uint8_t *out_buf = stream->next_out, *out_buf_start = stream->next_out;
        uint32_t name_len = 0, comment_len = 0;

        if (gz_hdr->text)
                flags |= TEXT_FLAG;
        if (gz_hdr->extra) {
                flags |= EXTRA_FLAG;
                hdr_size += GZIP_EXTRA_LEN + gz_hdr->extra_len;
        }
        if (gz_hdr->name) {
                flags |= NAME_FLAG;
                name_len = strnlen(gz_hdr->name, gz_hdr->name_buf_len);
                if (name_len < gz_hdr->name_buf_len)
                        name_len++;
                hdr_size += name_len;
        }
        if (gz_hdr->comment) {
                flags |= COMMENT_FLAG;
                comment_len = strnlen(gz_hdr->comment, gz_hdr->comment_buf_len);
                if (comment_len < gz_hdr->comment_buf_len)
                        comment_len++;
                hdr_size += comment_len;
        }
        if (gz_hdr->hcrc) {
                flags |= HCRC_FLAG;
                hdr_size += GZIP_HCRC_LEN;
        }

        if (stream->avail_out < hdr_size)
                return hdr_size;

        out_buf[0] = 0x1f;
        out_buf[1] = 0x8b;
        out_buf[2] = DEFLATE_METHOD;
        out_buf[3] = flags;
        store_le_u32(out_buf + 4, gz_hdr->time);
        out_buf[8] = gz_hdr->xflags;
        out_buf[9] = gz_hdr->os;

        out_buf += GZIP_HDR_BASE;
        if (flags & EXTRA_FLAG) {
                store_le_u16(out_buf, gz_hdr->extra_len);
                out_buf += GZIP_EXTRA_LEN;

                memcpy(out_buf, gz_hdr->extra, gz_hdr->extra_len);
                out_buf += gz_hdr->extra_len;
        }

        if (flags & NAME_FLAG) {
                memcpy(out_buf, gz_hdr->name, name_len);
                out_buf += name_len;
        }

        if (flags & COMMENT_FLAG) {
                memcpy(out_buf, gz_hdr->comment, comment_len);
                out_buf += comment_len;
        }

        if (flags & HCRC_FLAG) {
                hcrc = crc32_gzip_refl(0, out_buf_start, out_buf - out_buf_start);
                store_le_u16(out_buf, hcrc);
                out_buf += GZIP_HCRC_LEN;
        }

        stream->next_out += hdr_size;
        stream->total_out += hdr_size;
        stream->avail_out -= hdr_size;

        return ISAL_DECOMP_OK;
}

uint32_t
isal_write_zlib_header(struct isal_zstream *stream, struct isal_zlib_header *z_hdr)
{
        uint32_t cmf, flg, dict_flag = 0, hdr_size = ZLIB_HDR_BASE;
        uint8_t *out_buf = stream->next_out;

        if (z_hdr->dict_flag) {
                dict_flag = ZLIB_DICT_FLAG;
                hdr_size = ZLIB_HDR_BASE + ZLIB_DICT_LEN;
        }

        if (stream->avail_out < hdr_size)
                return hdr_size;

        cmf = DEFLATE_METHOD | (z_hdr->info << 4);
        flg = (z_hdr->level << 6) | dict_flag;

        flg += 31 - ((256 * cmf + flg) % 31);

        out_buf[0] = cmf;
        out_buf[1] = flg;

        if (dict_flag)
                store_le_u32(out_buf + 2, z_hdr->dict_id);

        stream->next_out += hdr_size;
        stream->total_out += hdr_size;
        stream->avail_out -= hdr_size;

        return ISAL_DECOMP_OK;
}

int
isal_deflate_set_hufftables(struct isal_zstream *stream, struct isal_hufftables *hufftables,
                            int type)
{
        if (stream->internal_state.state != ZSTATE_NEW_HDR)
                return ISAL_INVALID_OPERATION;

        switch (type) {
        case IGZIP_HUFFTABLE_DEFAULT:
                stream->hufftables = (struct isal_hufftables *) &hufftables_default;
                break;
        case IGZIP_HUFFTABLE_STATIC:
                stream->hufftables = (struct isal_hufftables *) &hufftables_static;
                break;
        case IGZIP_HUFFTABLE_CUSTOM:
                if (hufftables != NULL) {
                        stream->hufftables = hufftables;
                        break;
                }
        default:
                return ISAL_INVALID_OPERATION;
        }

        return COMP_OK;
}

void
isal_deflate_stateless_init(struct isal_zstream *stream)
{
        stream->total_in = 0;
        stream->total_out = 0;
        stream->hufftables = (struct isal_hufftables *) &hufftables_default;
        stream->level = 0;
        stream->level_buf = NULL;
        stream->level_buf_size = 0;
        stream->end_of_stream = 0;
        stream->flush = NO_FLUSH;
        stream->gzip_flag = 0;
        stream->hist_bits = 0;
        stream->internal_state.has_wrap_hdr = 0;
        stream->internal_state.state = ZSTATE_NEW_HDR;
        return;
}

void
isal_deflate_hash(struct isal_zstream *stream, uint8_t *dict, uint32_t dict_len)
{
        /* Reset history to prevent out of bounds matches this works because
         * dictionary must set at least 1 element in the history */
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;
        uint32_t hash_mask = stream->internal_state.hash_mask;

        switch (stream->level) {
        case 3:
                memset(level_buf->lvl3.hash_table, -1, sizeof(level_buf->lvl3.hash_table));
                isal_deflate_hash_lvl3(level_buf->lvl3.hash_table, hash_mask, stream->total_in,
                                       dict, dict_len);
                break;

        case 2:
                memset(level_buf->lvl2.hash_table, -1, sizeof(level_buf->lvl2.hash_table));
                isal_deflate_hash_lvl2(level_buf->lvl2.hash_table, hash_mask, stream->total_in,
                                       dict, dict_len);
                break;
        case 1:
                memset(level_buf->lvl1.hash_table, -1, sizeof(level_buf->lvl1.hash_table));
                isal_deflate_hash_lvl1(level_buf->lvl1.hash_table, hash_mask, stream->total_in,
                                       dict, dict_len);
                break;
        default:
                memset(stream->internal_state.head, -1, sizeof(stream->internal_state.head));
                isal_deflate_hash_lvl0(stream->internal_state.head, hash_mask, stream->total_in,
                                       dict, dict_len);
        }

        stream->internal_state.has_hist = IGZIP_HIST;
}

int
isal_deflate_process_dict(struct isal_zstream *stream, struct isal_dict *dict, uint8_t *dict_data,
                          uint32_t dict_len)
{
        if ((dict == NULL) || (dict_len == 0) || (dict->level > ISAL_DEF_MAX_LEVEL))
                return ISAL_INVALID_STATE;

        if (dict_len > IGZIP_HIST_SIZE) {
                dict_data = dict_data + dict_len - IGZIP_HIST_SIZE;
                dict_len = IGZIP_HIST_SIZE;
        }

        dict->level = stream->level;
        dict->hist_size = dict_len;
        memcpy(dict->history, dict_data, dict_len);
        memset(dict->hashtable, -1, sizeof(dict->hashtable));

        switch (stream->level) {
        case 3:
                dict->hash_size = IGZIP_LVL3_HASH_SIZE;
                isal_deflate_hash_lvl3(dict->hashtable, LVL3_HASH_MASK, 0, dict_data, dict_len);
                break;

        case 2:
                dict->hash_size = IGZIP_LVL2_HASH_SIZE;
                isal_deflate_hash_lvl2(dict->hashtable, LVL2_HASH_MASK, 0, dict_data, dict_len);
                break;
        case 1:
                dict->hash_size = IGZIP_LVL1_HASH_SIZE;
                isal_deflate_hash_lvl1(dict->hashtable, LVL1_HASH_MASK, 0, dict_data, dict_len);
                break;
        default:
                dict->hash_size = IGZIP_LVL0_HASH_SIZE;
                isal_deflate_hash_lvl0(dict->hashtable, LVL0_HASH_MASK, 0, dict_data, dict_len);
        }
        return COMP_OK;
}

int
isal_deflate_reset_dict(struct isal_zstream *stream, struct isal_dict *dict)
{
        struct isal_zstate *state = &stream->internal_state;
        struct level_buf *level_buf = (struct level_buf *) stream->level_buf;
        int ret;

        if ((state->state != ZSTATE_NEW_HDR) ||
            (state->b_bytes_processed != state->b_bytes_valid) || (dict->level != stream->level) ||
            (dict->hist_size == 0) || (dict->hist_size > IGZIP_HIST_SIZE) ||
            (dict->hash_size > IGZIP_LVL3_HASH_SIZE))
                return ISAL_INVALID_STATE;

        ret = check_level_req(stream);
        if (ret)
                return ret;

        memcpy(state->buffer, dict->history, dict->hist_size);
        state->b_bytes_processed = dict->hist_size;
        state->b_bytes_valid = dict->hist_size;
        state->has_hist = IGZIP_DICT_HASH_SET;

        switch (stream->level) {
        case 3:
                memcpy(level_buf->lvl3.hash_table, dict->hashtable,
                       sizeof(level_buf->lvl3.hash_table));
                break;

        case 2:
                memcpy(level_buf->lvl2.hash_table, dict->hashtable,
                       sizeof(level_buf->lvl2.hash_table));
                break;
        case 1:
                memcpy(level_buf->lvl1.hash_table, dict->hashtable,
                       sizeof(level_buf->lvl1.hash_table));
                break;
        default:
                memcpy(stream->internal_state.head, dict->hashtable,
                       sizeof(stream->internal_state.head));
        }

        return COMP_OK;
}

int
isal_deflate_set_dict(struct isal_zstream *stream, uint8_t *dict, uint32_t dict_len)
{
        struct isal_zstate *state = &stream->internal_state;

        if (state->state != ZSTATE_NEW_HDR || state->b_bytes_processed != state->b_bytes_valid)
                return ISAL_INVALID_STATE;

        if (dict_len <= 0)
                return COMP_OK;

        if (dict_len > IGZIP_HIST_SIZE) {
                dict = dict + dict_len - IGZIP_HIST_SIZE;
                dict_len = IGZIP_HIST_SIZE;
        }

        memcpy(state->buffer, dict, dict_len);
        state->b_bytes_processed = dict_len;
        state->b_bytes_valid = dict_len;

        state->has_hist = IGZIP_DICT_HIST;

        return COMP_OK;
}

int
isal_deflate_stateless(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        uint8_t *next_in = stream->next_in;
        const uint32_t avail_in = stream->avail_in;
        const uint32_t total_in = stream->total_in;

        uint8_t *next_out = stream->next_out;
        const uint32_t avail_out = stream->avail_out;
        const uint32_t total_out = stream->total_out;
        const uint32_t gzip_flag = stream->gzip_flag;
        const uint32_t has_wrap_hdr = state->has_wrap_hdr;

        int level_check;
        uint64_t stored_len;

        /* Final block has already been written */
        state->block_next = stream->total_in;
        state->block_end = stream->total_in;
        state->has_eob_hdr = 0;
        init(&state->bitbuf);
        state->state = ZSTATE_NEW_HDR;
        state->crc = 0;
        state->has_level_buf_init = 0;
        set_dist_mask(stream);

        if (stream->flush == NO_FLUSH)
                stream->end_of_stream = 1;

        if (stream->flush != NO_FLUSH && stream->flush != FULL_FLUSH)
                return INVALID_FLUSH;

        level_check = check_level_req(stream);
        if (level_check) {
                if (stream->level == 1 && stream->level_buf == NULL) {
                        /* Default to internal buffer if invalid size is supplied */
                        stream->level_buf = state->buffer;
                        stream->level_buf_size = sizeof(state->buffer) + sizeof(state->head);
                } else
                        return level_check;
        }

        set_hash_mask(stream);

        if (state->hash_mask > 2 * avail_in)
                state->hash_mask = (1 << bsr(avail_in)) - 1;

        if (avail_in == 0)
                stored_len = TYPE0_BLK_HDR_LEN;
        else {
                stored_len = TYPE0_BLK_HDR_LEN *
                             ((avail_in + TYPE0_MAX_BLK_LEN - 1) / TYPE0_MAX_BLK_LEN);
                stored_len += avail_in;
        }

        /*
           at least 1 byte compressed data in the case of empty dynamic block which only
           contains the EOB
         */
        if (stream->gzip_flag == IGZIP_GZIP)
                stored_len += gzip_hdr_bytes + gzip_trl_bytes;
        else if (stream->gzip_flag == IGZIP_GZIP_NO_HDR)
                stored_len += gzip_trl_bytes;

        else if (stream->gzip_flag == IGZIP_ZLIB)
                stored_len += zlib_hdr_bytes + zlib_trl_bytes;

        else if (stream->gzip_flag == IGZIP_ZLIB_NO_HDR)
                stored_len += zlib_trl_bytes;

        if (avail_out >= stored_len)
                stream->avail_out = stored_len;

        if (isal_deflate_int_stateless(stream) == COMP_OK) {
                if (avail_out >= stored_len)
                        stream->avail_out += avail_out - stored_len;
                return COMP_OK;
        } else {
                if (avail_out >= stored_len)
                        stream->avail_out += avail_out - stored_len;
                if (stream->flush == FULL_FLUSH) {
                        reset_match_history(stream);
                }
                stream->internal_state.has_eob_hdr = 0;
        }

        if (avail_out < stored_len)
                return STATELESS_OVERFLOW;

        stream->next_in = next_in + avail_in;
        stream->avail_in = 0;
        stream->total_in = avail_in;

        state->block_next = stream->total_in - avail_in;
        state->block_end = stream->total_in;

        stream->next_out = next_out;
        stream->avail_out = avail_out;
        stream->total_out = total_out;

        stream->gzip_flag = gzip_flag;
        state->has_wrap_hdr = has_wrap_hdr;
        init(&stream->internal_state.bitbuf);
        stream->internal_state.count = 0;

        if (stream->gzip_flag == IGZIP_GZIP || stream->gzip_flag == IGZIP_ZLIB)
                write_stream_header_stateless(stream);

        stream->internal_state.state = ZSTATE_TYPE0_HDR;

        write_stored_block(stream);

        stream->total_in = total_in + avail_in;

        if (stream->gzip_flag) {
                stream->internal_state.crc = 0;
                update_checksum(stream, next_in, avail_in);
        }

        if (stream->end_of_stream)
                write_trailer(stream);

        return COMP_OK;
}

static inline uint32_t
get_hist_size(struct isal_zstream *stream, uint8_t *start_in, int32_t buf_hist_start)
{
        struct isal_zstate *state = &stream->internal_state;
        uint32_t history_size;
        uint32_t buffered_history;
        uint32_t buffered_size = state->b_bytes_valid - state->b_bytes_processed;
        uint32_t input_history;

        buffered_history = (state->has_hist) ? state->b_bytes_processed - buf_hist_start : 0;
        input_history = stream->next_in - start_in;

        /* Calculate history required for deflate window */
        history_size = (buffered_history >= input_history) ? buffered_history : input_history;
        if (history_size > IGZIP_HIST_SIZE)
                history_size = IGZIP_HIST_SIZE;

        /* Calculate history required based on internal state */
        if (state->state == ZSTATE_TYPE0_HDR || state->state == ZSTATE_TYPE0_BODY ||
            state->state == ZSTATE_TMP_TYPE0_HDR || state->state == ZSTATE_TMP_TYPE0_BODY) {
                if (stream->total_in - state->block_next > history_size) {
                        history_size = (stream->total_in - state->block_next);
                }
        } else if (stream->avail_in + buffered_size == 0 &&
                   (stream->end_of_stream || stream->flush == FULL_FLUSH)) {
                history_size = 0;
        }
        return history_size;
}

int
isal_deflate(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        int ret = COMP_OK;
        uint8_t *next_in, *start_in, *buf_start_in, *next_in_pre;
        uint32_t avail_in, total_start, hist_size, future_size;
        uint32_t in_size, in_size_initial, out_size, out_size_initial;
        uint32_t processed, buffered_size = state->b_bytes_valid - state->b_bytes_processed;
        uint32_t flush_type = stream->flush;
        uint32_t end_of_stream = stream->end_of_stream;
        uint32_t size = 0;
        int32_t buf_hist_start = 0;
        uint8_t *copy_down_src = NULL;
        uint64_t copy_down_size = 0, copy_start_offset;
        int internal;

        if (stream->flush >= 3)
                return INVALID_FLUSH;

        ret = check_level_req(stream);
        if (ret)
                return ret;

        start_in = stream->next_in;
        total_start = stream->total_in;

        hist_size = get_hist_size(stream, start_in, buf_hist_start);

        if (state->has_hist == IGZIP_NO_HIST) {
                set_dist_mask(stream);
                set_hash_mask(stream);
                if (state->hash_mask > 2 * stream->avail_in &&
                    (stream->flush == FULL_FLUSH || stream->end_of_stream))
                        state->hash_mask = (1 << bsr(2 * stream->avail_in)) - 1;
                stream->total_in -= buffered_size;
                reset_match_history(stream);
                stream->total_in += buffered_size;
                buf_hist_start = state->b_bytes_processed;

        } else if (state->has_hist == IGZIP_DICT_HIST) {
                set_dist_mask(stream);
                set_hash_mask(stream);
                isal_deflate_hash(stream, state->buffer, state->b_bytes_processed);
        } else if (state->has_hist == IGZIP_DICT_HASH_SET) {
                set_dist_mask(stream);
                set_hash_mask(stream);
        }

        in_size = stream->avail_in + buffered_size;
        out_size = stream->total_out;
        do {
                in_size_initial = in_size;
                out_size_initial = out_size;
                buf_start_in = start_in;
                internal = 0;

                /* Setup to compress from internal buffer if insufficient history */
                if (stream->total_in - total_start < hist_size + buffered_size) {
                        /* On entry there should always be sufficient history bufferd */
                        /* assert(state->b_bytes_processed >= hist_size); */

                        internal = 1;
                        /* Shift down internal buffer if it contains more data
                         * than required */
                        if (state->b_bytes_processed > hist_size) {
                                copy_start_offset = state->b_bytes_processed - hist_size;

                                copy_down_src = &state->buffer[copy_start_offset];
                                copy_down_size = state->b_bytes_valid - copy_start_offset;
                                memmove(state->buffer, copy_down_src, copy_down_size);

                                state->b_bytes_valid -= copy_down_src - state->buffer;
                                state->b_bytes_processed -= copy_down_src - state->buffer;
                                buf_hist_start -= copy_down_src - state->buffer;
                                if (buf_hist_start < 0)
                                        buf_hist_start = 0;
                        }

                        size = stream->avail_in;
                        if (size > sizeof(state->buffer) - state->b_bytes_valid)
                                size = sizeof(state->buffer) - state->b_bytes_valid;

                        memcpy(&state->buffer[state->b_bytes_valid], stream->next_in, size);

                        stream->next_in += size;
                        stream->avail_in -= size;
                        stream->total_in += size;
                        state->b_bytes_valid += size;
                        buffered_size += size;

                        /* Save off next_in and avail_in if compression is
                         * performed in internal buffer, total_in can be
                         * recovered from knowledge of the size of the buffered
                         * input */
                        next_in = stream->next_in;
                        avail_in = stream->avail_in;

                        /* If not much data is buffered and there is no need to
                         * flush the buffer, just continue rather than attempt
                         * to compress */
                        if (avail_in == 0 && buffered_size <= IGZIP_HIST_SIZE &&
                            stream->total_in - buffered_size - state->block_next <=
                                    IGZIP_HIST_SIZE &&
                            !stream->end_of_stream && stream->flush == NO_FLUSH)
                                continue;

                        if (avail_in) {
                                stream->flush = NO_FLUSH;
                                stream->end_of_stream = 0;
                        }

                        stream->next_in = &state->buffer[state->b_bytes_processed];
                        stream->avail_in = buffered_size;
                        stream->total_in -= buffered_size;

                        buf_start_in = state->buffer;

                } else if (buffered_size) {
                        /* The user provided buffer has sufficient data, reset
                         * the user supplied buffer to included any data already
                         * buffered */
                        stream->next_in -= buffered_size;
                        stream->avail_in += buffered_size;
                        stream->total_in -= buffered_size;
                        state->b_bytes_valid = 0;
                        state->b_bytes_processed = 0;
                        buffered_size = 0;
                }

                next_in_pre = stream->next_in;
                isal_deflate_int(stream, buf_start_in);
                processed = stream->next_in - next_in_pre;
                hist_size = get_hist_size(stream, buf_start_in, buf_hist_start);

                /* Restore compression to unbuffered input when compressing to internal buffer */
                if (internal) {
                        state->b_bytes_processed += processed;
                        buffered_size -= processed;

                        stream->flush = flush_type;
                        stream->end_of_stream = end_of_stream;
                        stream->total_in += buffered_size;

                        stream->next_in = next_in;
                        stream->avail_in = avail_in;
                }

                in_size = stream->avail_in + buffered_size;
                out_size = stream->total_out;

        } while (internal && stream->avail_in > 0 && stream->avail_out > 0 &&
                 (in_size_initial != in_size || out_size_initial != out_size));

        /* Buffer history if data was pulled from the external buffer and future
         * calls to deflate will be required */
        if (!internal && (state->state != ZSTATE_END && state->state != ZSTATE_TRL)) {
                /* If the external buffer was used, sufficient history must
                 * exist in the user input buffer */
                /* assert(stream->total_in - total_start >= */
                /*        hist_size + buffered_size); */

                stream->next_in -= buffered_size;
                stream->avail_in += buffered_size;
                stream->total_in -= buffered_size;

                memmove(state->buffer, stream->next_in - hist_size, hist_size);
                state->b_bytes_processed = hist_size;
                state->b_bytes_valid = hist_size;
                buffered_size = 0;
        }

        /* Buffer input data if it is necessary for continued execution */
        if (stream->avail_in > 0 && (stream->avail_out > 0 || stream->level == 3)) {
                /* Determine how much data to buffer */
                future_size = sizeof(state->buffer) - state->b_bytes_valid;
                if (stream->avail_in < future_size)
                        /* Buffer all data if it fits as it will need to be buffered
                         * on the next call anyways*/
                        future_size = stream->avail_in;
                else if (ISAL_LOOK_AHEAD < future_size)
                        /* Buffer a minimum look ahead required for level 3 */
                        future_size = ISAL_LOOK_AHEAD;

                memcpy(&state->buffer[state->b_bytes_valid], stream->next_in, future_size);

                state->b_bytes_valid += future_size;
                buffered_size += future_size;
                stream->next_in += future_size;
                stream->total_in += future_size;
                stream->avail_in -= future_size;
        }

        return ret;
}

// Helper function to avoid code duplication.
static void
_zlib_header_in_buffer(struct isal_zstream *stream, uint8_t *buffer)
{
        uint8_t hist_bits, info, level, cmf, flg;
        uint8_t dict_flag = 0;

        if (stream->hist_bits == 0) // default hist_bits
                hist_bits = ISAL_DEF_MAX_HIST_BITS;
        else
                hist_bits = stream->hist_bits;
        if (hist_bits > 8)
                info = hist_bits - 8;
        else
                info = 0; // For low window sizes ensure correct cmf flag.
        if (stream->level == 0)
                level = 0; // Fastest algorithm
        else
                level = 1; // ISA-L levels 1-3 are fast algorithms.

        cmf = DEFLATE_METHOD | (info << 4);
        flg = (level << 6) | dict_flag;
        flg += 31 - ((256 * cmf + flg) % 31);
        buffer[0] = cmf;
        buffer[1] = flg;
        return;
}

static int
write_stream_header_stateless(struct isal_zstream *stream)
{
        uint32_t hdr_bytes;
        // Create a 10-byte buffer. Since the gzip header is almost fixed (9 of 10
        // bytes are fixed) use it to initialize the buffer.
        uint8_t buffer[10] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff };
        uint32_t next_flag;

        if (stream->internal_state.has_wrap_hdr)
                return COMP_OK;

        if (stream->gzip_flag == IGZIP_ZLIB) {
                hdr_bytes = zlib_hdr_bytes;
                _zlib_header_in_buffer(stream, buffer);
                next_flag = IGZIP_ZLIB_NO_HDR;
        } else {
                hdr_bytes = gzip_hdr_bytes;
                if (stream->level == 0)
                        buffer[8] = 0x04; // Fastest algorithm in xfl flag
                next_flag = IGZIP_GZIP_NO_HDR;
        }

        if (hdr_bytes >= stream->avail_out)
                return STATELESS_OVERFLOW;

        stream->avail_out -= hdr_bytes;
        stream->total_out += hdr_bytes;

        memcpy(stream->next_out, buffer, hdr_bytes);

        stream->next_out += hdr_bytes;
        stream->internal_state.has_wrap_hdr = 1;
        stream->gzip_flag = next_flag;

        return COMP_OK;
}

static void
write_stream_header(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        int bytes_to_write;
        uint32_t hdr_bytes;
        // Create a 10-byte buffer. Since the gzip header is almost fixed (9 of 10
        // bytes are fixed) use it to initialize the buffer.
        uint8_t buffer[10] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff };

        if (stream->internal_state.has_wrap_hdr)
                return;

        if (stream->gzip_flag == IGZIP_ZLIB) {
                hdr_bytes = zlib_hdr_bytes;
                _zlib_header_in_buffer(stream, buffer);
        } else {
                if (stream->level == 0)
                        buffer[8] = 0x04; // Fastest algorithm in xfl flag
                hdr_bytes = gzip_hdr_bytes;
        }

        bytes_to_write = hdr_bytes;
        bytes_to_write -= state->count;

        if (bytes_to_write > stream->avail_out)
                bytes_to_write = stream->avail_out;

        memcpy(stream->next_out, buffer + state->count, bytes_to_write);
        state->count += bytes_to_write;

        if (state->count == hdr_bytes) {
                state->count = 0;
                state->has_wrap_hdr = 1;
        }

        stream->avail_out -= bytes_to_write;
        stream->total_out += bytes_to_write;
        stream->next_out += bytes_to_write;
}

static int
write_deflate_header_stateless(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        struct isal_hufftables *hufftables = stream->hufftables;
        uint64_t hdr_extra_bits = hufftables->deflate_hdr[hufftables->deflate_hdr_count];
        uint32_t count;

        if (hufftables->deflate_hdr_count + 8 >= stream->avail_out)
                return STATELESS_OVERFLOW;

        memcpy(stream->next_out, hufftables->deflate_hdr, hufftables->deflate_hdr_count);

        if (stream->end_of_stream == 0) {
                if (hufftables->deflate_hdr_count > 0)
                        *stream->next_out -= 1;
                else
                        hdr_extra_bits -= 1;
        } else
                state->has_eob_hdr = 1;

        stream->avail_out -= hufftables->deflate_hdr_count;
        stream->total_out += hufftables->deflate_hdr_count;
        stream->next_out += hufftables->deflate_hdr_count;

        set_buf(&state->bitbuf, stream->next_out, stream->avail_out);

        write_bits(&state->bitbuf, hdr_extra_bits, hufftables->deflate_hdr_extra_bits);

        count = buffer_used(&state->bitbuf);
        stream->next_out = buffer_ptr(&state->bitbuf);
        stream->avail_out -= count;
        stream->total_out += count;

        state->state = ZSTATE_BODY;

        return COMP_OK;
}

static int
write_deflate_header_unaligned_stateless(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        struct isal_hufftables *hufftables = stream->hufftables;
        unsigned int count;
        uint64_t bit_count;
        uint8_t *header_next;
        uint8_t *header_end;
        uint64_t header_bits;

        if (state->bitbuf.m_bit_count == 0)
                return write_deflate_header_stateless(stream);

        if (hufftables->deflate_hdr_count + 16 >= stream->avail_out)
                return STATELESS_OVERFLOW;

        set_buf(&state->bitbuf, stream->next_out, stream->avail_out);

        header_next = hufftables->deflate_hdr;
        header_end = header_next +
                     (hufftables->deflate_hdr_count / sizeof(header_bits)) * sizeof(header_bits);

        header_bits = load_le_u64(header_next);

        if (stream->end_of_stream == 0)
                header_bits--;
        else
                state->has_eob_hdr = 1;

        header_next += sizeof(header_bits);

        /* Write out Complete Header bits */
        for (; header_next <= header_end; header_next += sizeof(header_bits)) {
                write_bits(&state->bitbuf, header_bits, 32);
                header_bits >>= 32;
                write_bits(&state->bitbuf, header_bits, 32);
                header_bits = load_le_u64(header_next);
        }
        bit_count = (hufftables->deflate_hdr_count & 0x7) * 8 + hufftables->deflate_hdr_extra_bits;

        if (bit_count > MAX_BITBUF_BIT_WRITE) {
                write_bits(&state->bitbuf, header_bits, MAX_BITBUF_BIT_WRITE);
                header_bits >>= MAX_BITBUF_BIT_WRITE;
                bit_count -= MAX_BITBUF_BIT_WRITE;
        }

        write_bits(&state->bitbuf, header_bits, bit_count);

        /* check_space flushes extra bytes in bitbuf. Required because
         * write_bits_always fails when the next commit makes the buffer
         * length exceed 64 bits */
        check_space(&state->bitbuf, FORCE_FLUSH);

        count = buffer_used(&state->bitbuf);
        stream->next_out = buffer_ptr(&state->bitbuf);
        stream->avail_out -= count;
        stream->total_out += count;

        state->state = ZSTATE_BODY;

        return COMP_OK;
}

/* Toggle end of stream only works when deflate header is aligned */
static void
write_header(struct isal_zstream *stream, uint8_t *deflate_hdr, uint32_t deflate_hdr_count,
             uint32_t extra_bits_count, uint32_t next_state, uint32_t toggle_end_of_stream)
{
        struct isal_zstate *state = &stream->internal_state;
        uint32_t hdr_extra_bits = deflate_hdr[deflate_hdr_count];
        uint32_t count;
        state->state = ZSTATE_HDR;

        if (state->bitbuf.m_bit_count != 0) {
                if (stream->avail_out < 8)
                        return;
                set_buf(&state->bitbuf, stream->next_out, stream->avail_out);
                flush(&state->bitbuf);
                count = buffer_used(&state->bitbuf);
                stream->next_out = buffer_ptr(&state->bitbuf);
                stream->avail_out -= count;
                stream->total_out += count;
        }

        if (stream->gzip_flag == IGZIP_GZIP || stream->gzip_flag == IGZIP_ZLIB)
                write_stream_header(stream);

        count = deflate_hdr_count - state->count;

        if (count != 0) {
                if (count > stream->avail_out)
                        count = stream->avail_out;

                memcpy(stream->next_out, deflate_hdr + state->count, count);

                if (toggle_end_of_stream && state->count == 0 && count > 0) {
                        /* Assumes the final block bit is the first bit */
                        *stream->next_out ^= 1;
                        state->has_eob_hdr = !state->has_eob_hdr;
                }

                stream->next_out += count;
                stream->avail_out -= count;
                stream->total_out += count;
                state->count += count;

                count = deflate_hdr_count - state->count;
        } else if (toggle_end_of_stream && deflate_hdr_count == 0) {
                /* Assumes the final block bit is the first bit */
                hdr_extra_bits ^= 1;
                state->has_eob_hdr = !state->has_eob_hdr;
        }

        if ((count == 0) && (stream->avail_out >= 8)) {

                set_buf(&state->bitbuf, stream->next_out, stream->avail_out);

                write_bits(&state->bitbuf, hdr_extra_bits, extra_bits_count);

                state->state = next_state;
                state->count = 0;

                count = buffer_used(&state->bitbuf);
                stream->next_out = buffer_ptr(&state->bitbuf);
                stream->avail_out -= count;
                stream->total_out += count;
        }
}

static void
write_trailer(struct isal_zstream *stream)
{
        struct isal_zstate *state = &stream->internal_state;
        unsigned int bytes = 0;
        uint32_t crc = state->crc;

        set_buf(&state->bitbuf, stream->next_out, stream->avail_out);

        if (!state->has_eob_hdr) {
                /* If the final header has not been written, write a
                 * final block. This block is a static huffman block
                 * which only contains the end of block symbol. The code
                 * that happens to do this is the fist 10 bits of
                 * 0x003 */
                if (stream->avail_out < 8)
                        return;

                state->has_eob_hdr = 1;
                write_bits(&state->bitbuf, 0x003, 10);
                if (is_full(&state->bitbuf)) {
                        stream->next_out = buffer_ptr(&state->bitbuf);
                        bytes = buffer_used(&state->bitbuf);
                        stream->avail_out -= bytes;
                        stream->total_out += bytes;
                        return;
                }
        }

        if (state->bitbuf.m_bit_count) {
                /* the flush() will pad to the next byte and write up to 8 bytes
                 * to the output stream/buffer.
                 */
                if (stream->avail_out < 8)
                        return;

                flush(&state->bitbuf);
        }

        stream->next_out = buffer_ptr(&state->bitbuf);
        bytes = buffer_used(&state->bitbuf);

        switch (stream->gzip_flag) {
        case IGZIP_GZIP:
        case IGZIP_GZIP_NO_HDR:
                if (stream->avail_out - bytes >= gzip_trl_bytes) {
                        store_le_u64(stream->next_out, ((uint64_t) stream->total_in << 32) | crc);
                        stream->next_out += gzip_trl_bytes;
                        bytes += gzip_trl_bytes;
                        state->state = ZSTATE_END;
                }
                break;

        case IGZIP_ZLIB:
        case IGZIP_ZLIB_NO_HDR:
                if (stream->avail_out - bytes >= zlib_trl_bytes) {
                        store_be_u32(stream->next_out,
                                     (crc & 0xFFFF0000) | ((crc & 0xFFFF) + 1) % ADLER_MOD);
                        stream->next_out += zlib_trl_bytes;
                        bytes += zlib_trl_bytes;
                        state->state = ZSTATE_END;
                }
                break;

        default:
                state->state = ZSTATE_END;
        }

        stream->avail_out -= bytes;
        stream->total_out += bytes;
}
