#include "errors.h"

#include <sqlite3.h>

#include <utility>
#include <tuple>
#include <type_traits>

namespace sqlite {
	namespace detail {
		template<class>
		using void_t = void;
		template<class T, class = void>
		struct is_callable : std::false_type {};
		template<class Functor, class ...Arguments>
		struct is_callable<Functor(Arguments...), void_t<decltype(std::declval<Functor>()(std::declval<Arguments>()...))>> : std::true_type {};
		template<class Functor, class ...Functors>
		class FunctorOverload: public Functor, public FunctorOverload<Functors...> {
			public:
				template<class Functor1, class ...Remaining>
				FunctorOverload(Functor1 &&functor, Remaining &&... remaining):
					Functor(std::forward<Functor1>(functor)),
					FunctorOverload<Functors...>(std::forward<Remaining>(remaining)...) {}
				using Functor::operator();
				using FunctorOverload<Functors...>::operator();
		};
		template<class Functor>
		class FunctorOverload<Functor>: public Functor {
			public:
				template<class Functor1>
				FunctorOverload(Functor1 &&functor):
					Functor(std::forward<Functor1>(functor)) {}
				using Functor::operator();
		};
		template<class Functor>
		class WrapIntoFunctor: public Functor {
			public:
				template<class Functor1>
				WrapIntoFunctor(Functor1 &&functor):
					Functor(std::forward<Functor1>(functor)) {}
				using Functor::operator();
		};
		template<class ReturnType, class ...Arguments>
		class WrapIntoFunctor<ReturnType(*)(Arguments...)> {
			ReturnType(*ptr)(Arguments...);
			public:
				WrapIntoFunctor(ReturnType(*ptr)(Arguments...)): ptr(ptr) {}
				ReturnType operator()(Arguments... arguments) { return (*ptr)(std::forward<Arguments>(arguments)...); }
		};
		inline void store_error_log_data_pointer(std::shared_ptr<void> ptr) {
			static std::shared_ptr<void> stored;
			stored = std::move(ptr);
		}
		template<class T>
		std::shared_ptr<typename std::decay<T>::type> make_shared_inferred(T &&t) {
			return std::make_shared<typename std::decay<T>::type>(std::forward<T>(t));
		}
	}
	template<class Handler>
	typename std::enable_if<!detail::is_callable<Handler(const sqlite_exception&)>::value>::type
	error_log(Handler &&handler);
	template<class Handler>
	typename std::enable_if<detail::is_callable<Handler(const sqlite_exception&)>::value>::type
	error_log(Handler &&handler);
	template<class ...Handler>
	typename std::enable_if<sizeof...(Handler)>=2>::type
	error_log(Handler &&...handler) {
		return error_log(detail::FunctorOverload<detail::WrapIntoFunctor<typename std::decay<Handler>::type>...>(std::forward<Handler>(handler)...));
	}
	template<class Handler>
	typename std::enable_if<!detail::is_callable<Handler(const sqlite_exception&)>::value>::type
	error_log(Handler &&handler) {
		return error_log(std::forward<Handler>(handler), [](const sqlite_exception&) {});
	}
	template<class Handler>
	typename std::enable_if<detail::is_callable<Handler(const sqlite_exception&)>::value>::type
	error_log(Handler &&handler) {
		auto ptr = detail::make_shared_inferred([handler = std::forward<Handler>(handler)](int error_code, const char *errstr) mutable {
			  switch(error_code & 0xFF) {
#define SQLITE_MODERN_CPP_ERROR_CODE(NAME,name,derived)             \
				  case SQLITE_ ## NAME: switch(error_code) {                \
					  derived                                                 \
				    default: handler(errors::name(errstr, "", error_code)); \
				  };break;
#define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE,SUB,base,sub)                    \
					  case SQLITE_ ## BASE ## _ ## SUB:                                       \
				      handler(errors::base ## _ ## sub(errstr, "", error_code)); \
				      break;
#include "lists/error_codes.h"
#undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED
#undef SQLITE_MODERN_CPP_ERROR_CODE
				  default: handler(sqlite_exception(errstr, "", error_code)); \
			  }
			});

		sqlite3_config(SQLITE_CONFIG_LOG, (void(*)(void*,int,const char*))[](void *functor, int error_code, const char *errstr) {
				(*static_cast<decltype(ptr.get())>(functor))(error_code, errstr);
			}, ptr.get());
		detail::store_error_log_data_pointer(std::move(ptr));
	}
}
