/*
 * This file provides various utility functions and structs which are used to minimize code duplication in the implementation of atc.h in atc.cpp.
 */

#pragma once

#include <iostream>
#include <type_traits>
#include <string>
#include <functional>

#include "data_type.h"
#include "results.h"
#include "options.h"
#include "DataType.hpp"
#include "Results.hpp"
#include "Options.hpp"

/// Converts a C-style string (char*) to a C++ std::string while checking that the passed pointer is not NULL
inline std::string convert_string(const char* c_str) {
	if (c_str == NULL)
		throw std::invalid_argument("Received null pointer as a required string argument!");
	else
		return std::string(c_str);
}

/// Converts a size_t array (size_t length and size_t* pointer) to a C++ std::vector\<size_t\>
inline std::vector<size_t> convert_sizes(uint8_t modes, const size_t* sizes) {
	if (sizes == NULL)
		throw std::invalid_argument("Received null pointer as sizes argument!");
	else
		return std::vector<size_t>(sizes, sizes + modes);
}

/// Converts a C data type enum value to the corresponding C++ data type enum value
atc::DataType convert_data_type(atc_data_type data_type);

/// Converts a C++ data type enum value to the corresponding C data type enum value
atc_data_type convert_data_type(atc::DataType data_type);

/**
 * Constructs a C++ options container containing copied values from the C options container.
 * In the case of arrays, the contents are copied into new std::vector, not the pointers.
 * @param options The C container to be copied.
 * @param modes The length of certain array parameters in \p options in case they are not null pointers.
 */
atc::Options convert_options(const atc_options* options, uint8_t modes = 0);

/**
 * Moves the contents of results to a C results container
 * This is done by moving cpp_results into a part of a new heap-allocated atc_results_impl struct
 * and then connecting the interface of the C container to its hidden data
 */
atc_results convert_results(atc::Results&& cpp_results);

/// Warn the user about an encountered problem, usually used as a replacement for exceptions.
inline void warn(const std::string& message) {
	std::cout << "ATC warning (C wrapper): " << message << std::endl;
}

// Some template metaprogramming here to make the return type of wrap_evaluation dynamic

template<typename T>
struct wrap_evaluation_typer {};

template<>
struct wrap_evaluation_typer<void> {
	typedef bool return_type;
	static const return_type error_value = false;
};

template<>
struct wrap_evaluation_typer<atc::Results> {
	typedef atc_results return_type;
	static constexpr auto& convert = convert_results;
	static const return_type error_value; // Needs to be defined outside of class because the type is not integral or an enum
};

template<>
struct wrap_evaluation_typer<atc::DataType> {
	typedef atc_data_type return_type;
	static constexpr auto convert = [] (const atc::DataType& res) {return convert_data_type(res);}; // static_cast etc. is necessary to disambiguate overloaded functions
	static const return_type error_value = ATC_DATA_TYPE_ERROR;
};

template<>
struct wrap_evaluation_typer<size_t> {
	typedef size_t return_type;
	static constexpr auto convert = [] (size_t&& res) {return res;};
	static const return_type error_value = 0;
};

/**
 * This function does two things:
 * - Convert the return value of the given C++ function to the corresponding C type.
 * - Catch all exceptions thrown by ATC and replace them with a warning while returning an error value, since C doesn't have exceptions.
 * @param func A function which can be called to execute the desired C++ interface call and return the requested output in C++ format.
 * @return If no error occurs, convert(func()) is returned, with convert being the corresponding type conversion function. If an error occurs, the error value corresponding to the return type (depends on T) is returned instead.
 */
#ifdef __cpp_lib_is_invocable
template<typename F, typename F_return_type = typename std::invoke_result<F>::type> // C++17 feature, unfortunately not always supported in "C++17 compilers" ...
#else
template<typename F, typename F_return_type = typename std::result_of<F()>::type> // Deprecated in C++17, removed in C++20. Luckily we know that F does not have any arguments, so we can still implement this with C++14 features ...
#endif
typename wrap_evaluation_typer<F_return_type>::return_type wrap_evaluation(const F& func) {
	
	try {
		if constexpr (std::is_same<F_return_type, void>::value) {
			// If the provided function has a void return type, manually handle its conversion to bool
			func();
			// If control flow reaches this point, the function executed successfully, so we can return true.
			// Otherwise we break out of this try block and will return the boolean error value, being false.
			return true;
		} else {
			return wrap_evaluation_typer<F_return_type>::convert(func());
		}
	} catch (const std::exception& e) {
		warn("ATC threw an exception with the following message: " + std::string(e.what()));
	} catch (...) {
		warn("ATC threw an unknown non-exception throwable, please notify the ATC developer to cover this case too!");
	}
	
	// If control flow reaches this point, we encountered a problem in any case
	return wrap_evaluation_typer<F_return_type>::error_value;
	
}
