#ifndef EXCELFUNCTION_h__
#define EXCELFUNCTION_h__

#include <string>
#include <sstream>
#include <vector>
#include <iterator>
#include <algorithm>
#include "ExcelDoNotEdit/excel_addin_export.h"
#include "ExcelDoNotEdit/excel_addin_utils.h"
#include "ExcelDoNotEdit/xlcall.h"
#include "ExcelFunction.h"
#include <assert.h>

struct ExcelFunction
{
	std::string s1;
	std::string s2;
	std::string s3;
	std::string s4;
	std::string s5;
	std::string s6;
	std::vector<std::vector<char>> vvc7;

	std::vector<char> v1;
	std::vector<char> v2;
	std::vector<char> v3;
	std::vector<char> v4;
	std::vector<char> v5;
	std::vector<char> v6;
	std::vector<char*> v7;

	char* c1;
	char* c2;
	char* c3;
	char* c4;
	char* c5;
	char* c6;
	char* c7[MAX_XL_PARAMS];

	unsigned int m_no_args;

	ExcelFunction(std::vector < std::string >& function_details)
	{
		construct(function_details[0], function_details[1], 
			function_details[2], function_details[3], std::vector<std::string>(function_details.begin() + 4, function_details.end()));
	}

	void construct(
		// the cpp entry point function which executes when the excel function is called
		const std::string& entry_point_function,
		// the category which this function will appear under in excel
		const std::string& function_wizard_category,
		// description of what the function does
		const std::string& function_description,
		// name of each parameter
		const std::string& parameter_names,
		// description of each parameter
		std::vector <std::string > const & parameter_help)
	{

		// if assert fails need to check number parameters = number of descriptions
		assert(parameter_help.size() == (size_t)(std::count(parameter_names.begin(), parameter_names.end(), ',') + 1));

		s1 = entry_point_function;
		s3 = entry_point_function.substr(0, entry_point_function.size() - 1);
		s4 = parameter_names;
		s5 = function_wizard_category;
		s6 = function_description;

		auto vectorise = [](const std::string& s, std::vector<char>& v, char*& c)
		{
			v = std::vector<char>(s.begin(), s.end());
			v.push_back('\0');
			c = v.data();
		};

		vectorise(s1, v1, c1);
		vectorise(s3, v3, c3);
		vectorise(s4, v4, c4);
		vectorise(s5, v5, c5);
		vectorise(s6, v6, c6);

		m_no_args = construct_param_help(parameter_help);

		// assign each parameter help
		for (unsigned int i = 0; i < m_no_args; i++)
			c7[i] = v7[i];

		// now know no_args so can do this bit
		std::stringstream ss;
		for (unsigned int i = 0; i < m_no_args + 1; i++)
			ss << "Q";
		ss << "!";
		s2 = ss.str();
		vectorise(s2, v2, c2);

	}

	int construct_param_help(std::vector < std::string >const & parameter_help)
	{
		auto one_help = [&](const std::string& help, size_t no_args)
		{
			std::vector<char> v = to_char_vector(help);
			v.push_back('\0');

			// where vector of strings is stored
			vvc7.push_back(v);
			// pointer to added string
			char* cptr = &(vvc7[no_args][0]);
			// store the char pointer
			v7.push_back(cptr);
		};


		for (size_t i = 0; i < parameter_help.size() ; ++i)
			one_help(parameter_help[i], i);

		return  (int) parameter_help.size();
	}

	template<typename... ArgTypes>
	ExcelFunction(
		// the cpp entry point function which executes when the excel function is called
		const std::string& entry_point_function,
		// the category which this function will appear under in excel
		const std::string& function_wizard_category,
		// description of what the function does
		const std::string& function_description,
		// name of each parameter
		const std::string& parameter_names,
		// description of each parameter
		ArgTypes&... parameter_help)
	{

		assert(sizeof...(parameter_help) == (std::count(parameter_names.begin(), parameter_names.end(), ',') + 1));

		s1 = entry_point_function;
		s3 = entry_point_function.substr(0, entry_point_function.size() - 1);
		s4 = parameter_names;
		s5 = function_wizard_category;
		s6 = function_description;

		auto vectorise = [](const std::string& s, std::vector<char>& v, char*& c)
		{
			v = std::vector<char>(s.begin(), s.end());
			v.push_back('\0');
			c = v.data();
		};

		vectorise(s1, v1, c1);
		vectorise(s3, v3, c3);
		vectorise(s4, v4, c4);
		vectorise(s5, v5, c5);
		vectorise(s6, v6, c6);

		m_no_args = construct_param_help(0,  parameter_help...);

		// assign each parameter help
		for (unsigned int i = 0; i < m_no_args; i++)
			c7[i] = v7[i];

		// now know no_args so can do this bit
		std::stringstream ss;
		for (unsigned int i = 0; i < m_no_args + 1; i++)
			ss << "Q";
		ss << "!";
		s2 = ss.str();
		vectorise(s2, v2, c2);
	}

	template<class T>
	static std::vector<char> to_char_vector(const T& t) {
		return std::vector<char>();
	}

	template< >
	static std::vector<char> to_char_vector<std::string>(const std::string& s) {
		return std::vector<char>(s.begin(), s.end());
	}

	// base case
	template<class T>
	int construct_param_help(
		int no_args,
		const T& t)
	{
		std::vector<char> v = to_char_vector(std::string(t));
		v.push_back('\0');

		// where vector of strings is stored
		vvc7.push_back(v);
		// pointer to added string
		char* cptr = &(vvc7[no_args][0]);
		// store the char pointer
		v7.push_back(cptr);

		return no_args + 1;
	}

	// recursive case
	template<class T, class... ArgTypes>
	int construct_param_help(
		int no_args,
		const T& current, ArgTypes&... rest)
	{
		no_args = construct_param_help(no_args, current);
		no_args = construct_param_help(no_args, rest...);
		return no_args;
	}

	registration_info get_registration_info()
	{
		registration_info reg;

		reg.dll_export = c1;
		reg.return_and_parameter_types = c2;
		reg.function_name_in_excel = c3;
		reg.parameter_names = c4;
		reg.function_wizard_category = c5;
		reg.function_description = c6;
		reg.num_parameters = m_no_args;

		for (unsigned int i = 0; i < m_no_args; i++) 
			reg.parameter_description[i] = c7[i];

		return reg;
	}

};

#endif // EXCELFUNCTION_h__