/*
    libmaus2
    Copyright (C) 2009-2015 German Tischler
    Copyright (C) 2011-2015 Genome Research Limited

    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_BAMBAM_PARALLEL_SCRAMCRAMENCODING_HPP)
#define LIBMAUS2_BAMBAM_PARALLEL_SCRAMCRAMENCODING_HPP

#include <libmaus2/exception/LibMausException.hpp>
#include <libmaus2/util/DynamicLoading.hpp>
#include <libmaus2/bambam/parallel/CramInterface.h>

namespace libmaus2
{
	namespace bambam
	{
		namespace parallel
		{
			struct ScramCramEncoding
			{
				typedef ScramCramEncoding this_type;
				typedef std::unique_ptr<this_type> unique_ptr_type;
				typedef std::shared_ptr<this_type> shared_ptr_type;

				#if defined(LIBMAUS2_HAVE_DL_FUNCS)
				libmaus2::util::DynamicLibrary scramlib;
				typedef void * (*alloc_func_t)(void *, char const *, size_t const, cram_data_write_function_t);
				libmaus2::util::DynamicLibraryFunction<alloc_func_t>::unique_ptr_type Palloc_func;
				typedef void (*dealloc_func_t)(void *);
				libmaus2::util::DynamicLibraryFunction<dealloc_func_t>::unique_ptr_type Pdealloc_func;
				typedef int (*enque_func_t)(void *,void *,size_t const,char const **,size_t const *,size_t const *,size_t const,int const,cram_enque_compression_work_package_function_t,cram_data_write_function_t, cram_compression_work_package_finished_t);
				libmaus2::util::DynamicLibraryFunction<enque_func_t>::unique_ptr_type Penque_func;
				typedef int (*dispatch_func_t)(void *);
				libmaus2::util::DynamicLibraryFunction<dispatch_func_t>::unique_ptr_type Pdispatch_func;
				typedef int (*set_cram_profile_func_t)(void *, char const *);
				libmaus2::util::DynamicLibraryFunction<set_cram_profile_func_t>::unique_ptr_type Pset_cram_profile_func;
				#endif

				ScramCramEncoding()
				#if defined(LIBMAUS2_HAVE_DL_FUNCS)
				: scramlib("libmaus2_scram_mod.so"), Palloc_func()
				#endif
				{
					#if ! defined(LIBMAUS2_HAVE_DL_FUNCS)
					{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "ScramCramEncoding: no support for dl functions" << std::endl;
					lme.finish();
					throw lme;
					}
					#endif

					#if ! defined(LIBMAUS2_HAVE_IO_NEW_CRAM_INTERFACE)
					{
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "ScramCramEncoding: no support for new CRAM encoding interface" << std::endl;
					lme.finish();
					throw lme;
					}
					#endif

					#if defined(LIBMAUS2_HAVE_DL_FUNCS)
					libmaus2::util::DynamicLibraryFunction<alloc_func_t>::unique_ptr_type Talloc_func(
						new libmaus2::util::DynamicLibraryFunction<alloc_func_t>(scramlib,"scram_cram_allocate_encoder")
					);
					Palloc_func = std::move(Talloc_func);
					libmaus2::util::DynamicLibraryFunction<dealloc_func_t>::unique_ptr_type Tdealloc_func(
						new libmaus2::util::DynamicLibraryFunction<dealloc_func_t>(scramlib,"scram_cram_deallocate_encoder")
					);
					Pdealloc_func = std::move(Tdealloc_func);
					libmaus2::util::DynamicLibraryFunction<enque_func_t>::unique_ptr_type Tenque_func(
						new libmaus2::util::DynamicLibraryFunction<enque_func_t>(scramlib,"scram_cram_enque_compression_block")
					);
					Penque_func = std::move(Tenque_func);
					libmaus2::util::DynamicLibraryFunction<dispatch_func_t>::unique_ptr_type Tdispatch_func(
						new libmaus2::util::DynamicLibraryFunction<dispatch_func_t>(scramlib,"scram_cram_process_work_package")
					);
					Pdispatch_func = std::move(Tdispatch_func);

					libmaus2::util::DynamicLibraryFunction<set_cram_profile_func_t>::unique_ptr_type Tset_cram_profile_func(
						new libmaus2::util::DynamicLibraryFunction<set_cram_profile_func_t>(scramlib,"scram_cram_set_cram_profile")
					);
					Pset_cram_profile_func = std::move(Tset_cram_profile_func);
					#endif
				}

				void * cram_allocate_encoder(void *userdata, char const *header, size_t const headerlength, cram_data_write_function_t writefunc)
				{
					#if defined(LIBMAUS2_HAVE_DL_FUNCS)
					return Palloc_func->func(userdata,header,headerlength,writefunc);
					#else
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "ScramCramEncoding::cram_allocate_encoder: no support for new CRAM encoding interface" << std::endl;
					lme.finish();
					throw lme;
					#endif
				}

				int cram_set_profile(void * context, std::string const & profile)
				{
					#if defined(LIBMAUS2_HAVE_DL_FUNCS)
					return Pset_cram_profile_func->func(context,profile.c_str());
					#else
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "ScramCramEncoding::scram_cram_set_cram_profile: no support for new CRAM encoding interface" << std::endl;
					lme.finish();
					throw lme;
					#endif
				}

				void cram_deallocate_encoder(void * context)
				{
					#if defined(LIBMAUS2_HAVE_DL_FUNCS)
					Pdealloc_func->func(context);
					#else
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "ScramCramEncoding::cram_deallocate_encoder: no support for new CRAM encoding interface" << std::endl;
					lme.finish();
					throw lme;
					#endif
				}

				int cram_enque_compression_block(
					void *userdata,
					void *context,
					size_t const inblockid,
					char const **block,
					size_t const *blocksize,
					size_t const *blockelements,
					size_t const numblocks,
					int const final,
					cram_enque_compression_work_package_function_t workenqueuefunction,
					cram_data_write_function_t writefunction,
					cram_compression_work_package_finished_t workfinishedfunction
				)
				{
					#if defined(LIBMAUS2_HAVE_DL_FUNCS)
					return Penque_func->func(userdata,context,inblockid,block,blocksize,blockelements,numblocks,final,workenqueuefunction,writefunction,workfinishedfunction);
					#else
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "ScramCramEncoding::cram_enque_compression_block: no support for new CRAM encoding interface" << std::endl;
					lme.finish();
					throw lme;
					#endif
				}

				int cram_process_work_package(void *workpackage)
				{
					#if defined(LIBMAUS2_HAVE_DL_FUNCS)
					return Pdispatch_func->func(workpackage);
					#else
					libmaus2::exception::LibMausException lme;
					lme.getStream() << "ScramCramEncoding::cram_process_work_package: no support for new CRAM encoding interface" << std::endl;
					lme.finish();
					throw lme;
					#endif
				}
			};
		}
	}
}
#endif
