/*
    libmaus2
    Copyright (C) 2016-2021 German Tischler

    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/lz/ZlibInterface.hpp>
#include <cstring>
#include <zlib.h>

struct ZlibFunctions
{
	int (*p_inflateReset)(z_stream *);
	int (*p_inflateEnd)(z_stream *);
	int (*p_inflateInit)(z_stream *, char const *, int);
	int (*p_inflateInit2)(z_stream *, int, char const *, int);
	int (*p_inflate)(z_stream *, int);
	int (*p_deflateReset)(z_stream *);
	int (*p_deflateEnd)(z_stream *);
	int (*p_deflateInit)(z_stream *, int, char const *, int);
	int (*p_deflateInit2)(z_stream *, int, int, int, int, int, char const *, int);
	int (*p_deflate)(z_stream *, int);
	unsigned long (*p_deflateBound)(z_stream *, unsigned long);
	unsigned long (*p_crc32)(unsigned long crc, unsigned char const * buf, unsigned int length);

	ZlibFunctions() :
		p_inflateReset(reinterpret_cast< int (*)(z_stream *) >(::inflateReset)),
		p_inflateEnd(reinterpret_cast< int (*)(z_stream *) >(::inflateEnd)),
		p_inflateInit(reinterpret_cast< int (*)(z_stream *, char const *, int) >(::inflateInit_)),
		p_inflateInit2(reinterpret_cast< int (*)(z_stream *, int, char const *, int) >(::inflateInit2_)),
		p_inflate(reinterpret_cast< int (*)(z_stream *, int) >(::inflate)),
		p_deflateReset(reinterpret_cast< int (*)(z_stream *) >(::deflateReset)),
		p_deflateEnd(reinterpret_cast< int (*)(z_stream *) >(::deflateEnd)),
		p_deflateInit(reinterpret_cast< int (*)(z_stream *, int, char const *, int) >(::deflateInit_)),
		p_deflateInit2(reinterpret_cast< int (*)(z_stream *, int, int, int, int, int, char const *, int) >(::deflateInit2_)),
		p_deflate(reinterpret_cast< int (*)(z_stream *, int) >(::deflate)),
		p_deflateBound(reinterpret_cast< unsigned long (*)(z_stream *, unsigned long) >(deflateBound)),
		p_crc32(reinterpret_cast<unsigned long (*)(unsigned long crc, unsigned char const * buf, unsigned int length)>(::crc32))
	{

	}
};

libmaus2::lz::ZlibInterface::ZlibInterface()
:
	context(
		reinterpret_cast<void *>(new z_stream),
		[](auto p){delete reinterpret_cast<z_stream *>(p);}
	),
	intf(
		reinterpret_cast<void *>(new ZlibFunctions),
		[](auto p){delete reinterpret_cast<ZlibFunctions *>(p);}
	)
{

}

libmaus2::lz::ZlibInterface::~ZlibInterface() {}

libmaus2::lz::ZlibInterface::unique_ptr_type libmaus2::lz::ZlibInterface::construct()
{
	unique_ptr_type tptr(new this_type);
	return tptr;
}

void libmaus2::lz::ZlibInterface::eraseContext()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	memset(strm,0,sizeof(z_stream));
}


void libmaus2::lz::ZlibInterface::setNextIn(unsigned char * p)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	strm->next_in = p;
}

void libmaus2::lz::ZlibInterface::setAvailIn(uint64_t const s)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	strm->avail_in = s;
}

void libmaus2::lz::ZlibInterface::setTotalIn(uint64_t const s)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	strm->total_in = s;
}

void libmaus2::lz::ZlibInterface::setNextOut(unsigned char * p)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	strm->next_out = p;
}

void libmaus2::lz::ZlibInterface::setAvailOut(uint64_t const s)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	strm->avail_out = s;
}

void libmaus2::lz::ZlibInterface::setTotalOut(uint64_t const s)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	strm->total_out = s;
}

unsigned char * libmaus2::lz::ZlibInterface::getNextOut()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->next_out;
}

uint64_t libmaus2::lz::ZlibInterface::getAvailOut() const
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->avail_out;
}

uint64_t libmaus2::lz::ZlibInterface::getTotalOut() const
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->total_out;
}

unsigned char * libmaus2::lz::ZlibInterface::getNextIn()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->next_in;
}

uint64_t libmaus2::lz::ZlibInterface::getAvailIn() const
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->avail_in;
}

uint64_t libmaus2::lz::ZlibInterface::getTotalIn() const
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->total_in;
}

void libmaus2::lz::ZlibInterface::setZAlloc(libmaus2::lz::ZlibInterface::alloc_function alloc)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	strm->zalloc = alloc;
}

libmaus2::lz::ZlibInterface::alloc_function libmaus2::lz::ZlibInterface::getZAlloc()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->zalloc;
}

void libmaus2::lz::ZlibInterface::setZFree(libmaus2::lz::ZlibInterface::free_function f)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	strm->zfree = f;
}

libmaus2::lz::ZlibInterface::free_function libmaus2::lz::ZlibInterface::getZFree()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->zfree;
}

void libmaus2::lz::ZlibInterface::setOpaque(void * p)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	strm->opaque = p;
}

void * libmaus2::lz::ZlibInterface::getOpaque()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->opaque;
}

char const * libmaus2::lz::ZlibInterface::getMsg() const
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return strm->msg;
}

int libmaus2::lz::ZlibInterface::z_inflateReset()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_inflateReset(strm);
}

int libmaus2::lz::ZlibInterface::z_inflateEnd()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_inflateEnd(strm);
}

int libmaus2::lz::ZlibInterface::z_inflateInit()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_inflateInit(strm, ZLIB_VERSION, static_cast<int>(sizeof(z_stream)));
}

int libmaus2::lz::ZlibInterface::z_inflateInit2(int windowbits)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_inflateInit2(strm, windowbits, ZLIB_VERSION, static_cast<int>(sizeof(z_stream)));
}

int libmaus2::lz::ZlibInterface::z_inflate(int flush)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_inflate(strm,flush);
}

int libmaus2::lz::ZlibInterface::z_deflateReset()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_deflateReset(strm);
}

int libmaus2::lz::ZlibInterface::z_deflateEnd()
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_deflateEnd(strm);
}

int libmaus2::lz::ZlibInterface::z_deflateInit(int level)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_deflateInit(strm, level, ZLIB_VERSION, static_cast<int>(sizeof(z_stream)));
}

int libmaus2::lz::ZlibInterface::z_deflateInit2(int level, int method, int windowBits, int memLevel, int strategy)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_deflateInit2(strm, level, method, windowBits, memLevel, strategy, ZLIB_VERSION, static_cast<int>(sizeof(z_stream)));
}

int libmaus2::lz::ZlibInterface::z_deflate(int flush)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_deflate(strm,flush);
}

unsigned long libmaus2::lz::ZlibInterface::z_deflateBound(unsigned long in)
{
	z_stream * strm = reinterpret_cast<z_stream *>(context.get());
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_deflateBound(strm,in);
}

unsigned long libmaus2::lz::ZlibInterface::z_crc32(unsigned long crc, unsigned char const * buf, unsigned int length)
{
	return reinterpret_cast<ZlibFunctions *>(intf.get())->p_crc32(crc,buf,length);
}
