/* test_deflate.cc -- Test deflate/inflate round-trip with various levels, sizes, and strategies */

#include "zbuild.h"
#ifdef ZLIB_COMPAT
#  include "zlib.h"
#else
#  include "neozip.h"
#endif

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include <gtest/gtest.h>

#include "compressible_data_p.h"
#include "test_shared.h"

#define MAX_SIZE (1024 * 1024)

class deflate_variant : public testing::TestWithParam<std::tuple<size_t, int, int, int>> {
public:
    static void SetUpTestSuite() {
        inbuf = gen_compressible_data(MAX_SIZE);
        ASSERT_TRUE(inbuf != NULL);
    }
    static void TearDownTestSuite() {
        free(inbuf);
        inbuf = NULL;
    }
    void SetUp() override {
        compbuf = NULL;
        decompbuf = NULL;
    }
    void TearDown() override {
        free(compbuf);
        free(decompbuf);
    }

    static uint8_t *inbuf;
    uint8_t *compbuf;
    uint8_t *decompbuf;
};

uint8_t *deflate_variant::inbuf = NULL;

TEST_P(deflate_variant, round_trip) {
    size_t size = std::get<0>(GetParam());
    int level = std::get<1>(GetParam());
    int window_bits = std::get<2>(GetParam());
    int strategy = std::get<3>(GetParam());
    PREFIX3(stream) c_stream, d_stream;
    int err;

    /* Allocate output buffer */
    unsigned long compbuf_size = PREFIX(deflateBound)(NULL, (unsigned long)size);
    compbuf = (uint8_t *)malloc((size_t)compbuf_size);
    ASSERT_TRUE(compbuf != NULL);

    /* Compress */
    memset(&c_stream, 0, sizeof(c_stream));
    err = PREFIX(deflateInit2)(&c_stream, level, Z_DEFLATED,
                               window_bits, MAX_MEM_LEVEL, strategy);
    ASSERT_EQ(err, Z_OK) << "deflateInit2 failed";

    c_stream.next_in = inbuf;
    c_stream.avail_in = (uint32_t)size;
    c_stream.next_out = compbuf;
    c_stream.avail_out = (uint32_t)compbuf_size;

    err = PREFIX(deflate)(&c_stream, Z_FINISH);
    ASSERT_EQ(err, Z_STREAM_END) <<
        "size: " << size << "\n" <<
        "level: " << level << "\n" <<
        "window_bits: " << window_bits << "\n" <<
        "strategy: " << strategy;

    size_t comp_size = (size_t)c_stream.total_out;

    err = PREFIX(deflateEnd)(&c_stream);
    EXPECT_EQ(err, Z_OK);

    /* Decompress and verify round-trip */
    decompbuf = (uint8_t *)malloc(size);
    ASSERT_TRUE(decompbuf != NULL);

    memset(&d_stream, 0, sizeof(d_stream));
    err = PREFIX(inflateInit2)(&d_stream, window_bits);
    ASSERT_EQ(err, Z_OK) << "inflateInit2 failed";

    d_stream.next_in = compbuf;
    d_stream.avail_in = (uint32_t)comp_size;
    d_stream.next_out = decompbuf;
    d_stream.avail_out = (uint32_t)size;

    err = PREFIX(inflate)(&d_stream, Z_FINISH);
    ASSERT_EQ(err, Z_STREAM_END) <<
        "size: " << size << "\n" <<
        "level: " << level << "\n" <<
        "window_bits: " << window_bits << "\n" <<
        "strategy: " << strategy;

    EXPECT_EQ(d_stream.total_in, comp_size);
    EXPECT_EQ(d_stream.total_out, size);
    EXPECT_EQ(memcmp(inbuf, decompbuf, size), 0) << "round-trip data mismatch";

    err = PREFIX(inflateEnd)(&d_stream);
    EXPECT_EQ(err, Z_OK);
}

static std::string deflate_test_name(const testing::TestParamInfo<deflate_variant::ParamType> &info) {
    size_t size = std::get<0>(info.param);
    int level = std::get<1>(info.param);
    int window_bits = std::get<2>(info.param);
    int strategy = std::get<3>(info.param);

    char size_str[32];
    if (size >= 1048576 && size % 1048576 == 0)
        snprintf(size_str, sizeof(size_str), "%zuM", size / 1048576);
    else if (size >= 1024 && size % 1024 == 0)
        snprintf(size_str, sizeof(size_str), "%zuK", size / 1024);
    else
        snprintf(size_str, sizeof(size_str), "%zu", size);

    const char *wrap_str = (window_bits < 0) ? "raw" : "zlib";

    const char *strategy_str;
    switch (strategy) {
        case Z_FILTERED:     strategy_str = "filtered"; break;
        case Z_HUFFMAN_ONLY: strategy_str = "huffman"; break;
        case Z_RLE:          strategy_str = "rle"; break;
        case Z_FIXED:        strategy_str = "fixed"; break;
        default:             strategy_str = "default"; break;
    }

    char name[64];
    snprintf(name, sizeof(name), "%s_level%d_%s_%s", size_str, level, wrap_str, strategy_str);
    return name;
}

INSTANTIATE_TEST_SUITE_P(deflate, deflate_variant,
    testing::Combine(
        testing::Values(1024, 16384, 131072, 1048576),          /* size */
        testing::Values(1, 3, 6, 9),                            /* level */
        testing::Values(MAX_WBITS, -MAX_WBITS),                 /* window_bits */
        testing::Values(Z_DEFAULT_STRATEGY, Z_FILTERED,
                        Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED)         /* strategy */
    ),
    deflate_test_name);
