/*
    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/>.
*/
#if ! defined(LIBMAUS2_PARALLEL_THREADPOOL_BGZF_BGZFDECOMPRESSCONTEXT_HPP)
#define LIBMAUS2_PARALLEL_THREADPOOL_BGZF_BGZFDECOMPRESSCONTEXT_HPP

#include <zlib.h>
#include <memory>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <atomic>
#include <libmaus2/exception/LibMausException.hpp>

namespace libmaus2
{
	namespace parallel
	{
		namespace threadpool
		{
			namespace bgzf
			{
				/* zlib decompression context */
				struct BgzfDecompressContext
				{
					typedef BgzfDecompressContext this_type;
					typedef std::shared_ptr<this_type> shared_ptr_type;

					private:
					BgzfDecompressContext & operator=(BgzfDecompressContext const & O) = delete;

					std::shared_ptr<z_stream> stream;

					public:
					BgzfDecompressContext()
					: 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");
						}
					}

					~BgzfDecompressContext()
					{
						inflateEnd(stream.get());
					}

					void decompress(
						char * in,
						size_t n_in,
						char * out,
						size_t n_out
					)
					{
						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
