/*
    libmaus2
    Copyright (C) 2020 German Tischler-Höhle

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <libmaus2/parallel/threadpool/bgzf/BgzfDecompressContext.hpp>

#if defined(LIBMAUS2_HAVE_LIBDEFLATE)
#include <libdeflate.h>
#else
#include <zlib.h>
#endif

libmaus2::parallel::threadpool::bgzf::BgzfDecompressContext::BgzfDecompressContext()
{
	#if defined(LIBMAUS2_HAVE_LIBDEFLATE)
	auto * ldecompressor = libdeflate_alloc_decompressor();

	if ( ! ldecompressor )
		throw libmaus2::exception::LibMausException("[E] BgzfDecompressContext: libdeflate_alloc_decompressor failed");

	decompressor = std::shared_ptr<struct libdeflate_decompressor>(
		ldecompressor,
		[](auto p) {libdeflate_free_decompressor(p);}
	);
	#else
	stream = std::shared_ptr<z_stream>(new z_stream);
	std::memset(stream.get(),0,sizeof(z_stream));
	stream->zalloc = nullptr;
	stream->zfree = nullptr;
	stream->opaque = nullptr;
	stream->avail_in = 0;
	stream->next_in = nullptr;
	int const r = inflateInit2(stream.get(),-15);
	if ( r != Z_OK )
	{
		throw libmaus2::exception::LibMausException("[E] BgzfDecompressContext: inflateInit2 failed");
	}
	#endif
}

libmaus2::parallel::threadpool::bgzf::BgzfDecompressContext::~BgzfDecompressContext()
{
	#if !defined(LIBMAUS2_HAVE_LIBDEFLATE)
	inflateEnd(stream.get());
	#endif
}

void libmaus2::parallel::threadpool::bgzf::BgzfDecompressContext::decompress(
	char * in,
	size_t n_in,
	char * out,
	size_t n_out
)
{
	#if defined(LIBMAUS2_HAVE_LIBDEFLATE)
	std::size_t u_out = 0;
	auto const r = libdeflate_deflate_decompress(decompressor.get(),in,n_in,out,n_out,&u_out);

	bool const ok =
		(r == LIBDEFLATE_SUCCESS)
		&&
		(u_out == n_out);

	if ( !ok )
	{
		throw ::libmaus2::exception::LibMausException("[E] BgzfDecompressContext::decompress: libdeflate_deflate_decompress");
	}
	#else
	assert ( stream );
	if ( inflateReset(stream.get()) != Z_OK )
		throw libmaus2::exception::LibMausException("[E] BgzfDecompressContext::decompress: inflateReset failed");

	stream->avail_in = n_in;
	stream->next_in = reinterpret_cast<Bytef*>(in);
	stream->avail_out = n_out;
	stream->next_out = reinterpret_cast<Bytef*>(out);

	int const r = inflate(stream.get(),Z_FINISH);

	bool const r_ok = (r == Z_STREAM_END);
	bool const out_ok = (stream->avail_out == 0);
	bool const in_ok = (stream->avail_in == 0);
	bool const ok = r_ok && out_ok && in_ok;

	if ( !ok )
	{
		throw ::libmaus2::exception::LibMausException("[E] BgzfDecompressContext::decompress: inflate failed");
	}
	#endif
}

uint32_t libmaus2::parallel::threadpool::bgzf::BgzfDecompressContext::crc32(
	char const * in,
	size_t const n_in
)
{
	#if defined(LIBMAUS2_HAVE_LIBDEFLATE)
	return libdeflate_crc32(0 /* init value */, in, n_in);
	#else
	uint32_t lcrc = ::crc32(0,0,0);
	lcrc = ::crc32(lcrc,reinterpret_cast<Bytef const *>(in),n_in);
	return lcrc;
	#endif
}