#include "PackAndUnpackOpers.h"

using namespace excel_adapter;
using std::string;
using std::vector;
using std::max;

////////////////////////////////////////////////////////////////////
///////////////////// UNPACK FUNCTIONS
////////////////////////////////////////////////////////////////////

void check_type(XLOPER12* in_oper, unsigned int argnumber, DWORD type, string typestring)
{
	if (xloper_type(in_oper) != type)
	{
		std::stringstream stream;
		stream << "Argument " << argnumber << " must be an " << typestring << ".";
		throw std::runtime_error(stream.str());
	}
}

void mat_check_type(XLOPER12* in_oper, unsigned int argnumber, DWORD type, string typestring, unsigned int index)
{
		if (xloper_type(in_oper) != type)
	{
		std::stringstream stream;
		stream << "Element " << index << " of argument " << argnumber << " must be an " << typestring << ".";
		throw std::runtime_error(stream.str());
	}
}

d_matrix unpack_double_matrix(XLOPER12* in_oper, unsigned int argnumber)
{
	check_type(in_oper, argnumber, xltypeMulti, "matrix");

	unsigned int rows = in_oper->val.array.rows;
	unsigned int cols = in_oper->val.array.columns;

	d_matrix input;
	input.resize(rows, cols);

	for (unsigned int i = 0; i < rows * cols; ++i)
	{

		XLOPER12 xloper_element = in_oper->val.array.lparray[i];	
		mat_check_type(&xloper_element, argnumber, xltypeNum, "number", i);
		input[i] = xloper_element.val.num;

	}

	input.fix_dimensions(rows, cols);

	return input;
}

d_vector unpack_double_vector(XLOPER12* in_oper, unsigned int argnumber)
{
	check_type(in_oper, argnumber, xltypeMulti, "vector");

	int rows = in_oper->val.array.rows;
	int cols = in_oper->val.array.columns;

	unsigned int n = (rows == 1 ? cols : rows);

	d_vector input;
	input.resize(n);

	for (unsigned int i = 0; i < n; i++)
	{
		XLOPER12 xloper_element = in_oper->val.array.lparray[i];
		mat_check_type(&xloper_element, argnumber, xltypeNum, "number", i);
		input[i] = xloper_element.val.num;
	}

	input.fix_dimensions(rows, cols);

	return input;
}

int unpack_int(XLOPER12* in_oper, unsigned int argnumber)
{
	check_type(in_oper, argnumber, xltypeNum, "integer");

	if ((int)in_oper->val.num == in_oper->val.num)
		return (int)in_oper->val.num;
	else
		check_type(in_oper, argnumber, xltypeNil, "integer");
	return 0;
}

double unpack_double(XLOPER12* in_oper, unsigned int argnumber)
{
	check_type(in_oper, argnumber, xltypeNum, "number");
	return in_oper->val.num;
}

string unpack_string(XLOPER12* in_oper, unsigned int argnumber)
{
	check_type(in_oper, argnumber, xltypeStr, "string");
	std::string input_str = excel_string_to_utf8(in_oper->val.str);
	return input_str;
}


////////////////////////////////////////////////////////////////////
///////////////////// PACK FUNCTIONS
////////////////////////////////////////////////////////////////////

template <class T>
void pack_single(T in, XLOPER12* xl_function_result)
{
	// Create the array to return
	XLOPER12* xloper_array = new XLOPER12[1];

	// Fill-up the array with the result
	xloper_array[0].xltype = xltypeNum;
	xloper_array[0].val.num = in;

	// Set-up the result XLOPER
	xl_function_result->xltype = xltypeMulti | xlbitDLLFree;
	xl_function_result->val.array.rows = 1;
	xl_function_result->val.array.columns = 1;
	xl_function_result->val.array.lparray = xloper_array;
}

void pack_double_oper(double indouble, XLOPER12* xl_function_result)
{
	pack_single(indouble, xl_function_result);
}

void pack_int_oper(int inint, XLOPER12* xl_function_result)
{
	pack_single(inint, xl_function_result);
}

void pack_double_vector_oper(const d_vector& invector, XLOPER12* xl_function_result)
{
	size_t n = 0, rows = 1, cols = 1;

	// column vector
	if ((invector.get_rows() == 1) && (invector.get_cols() > 1))
	{
		n = invector.get_cols();
		cols = n;
	}
	// row vector
	else
	{
		n = max( invector.get_rows(), invector.size() );
		rows = n;
	}
	// Create the array to return
	XLOPER12* xloper_array = new XLOPER12[n];
	xloper_array->val.array.rows = (RW) rows;
	xloper_array->val.array.columns = (COL) cols;

	// Fill-up the array with the values
	for (size_t i = 0; i < n; ++i)
	{
			xloper_array[i].val.num = invector[i];
			xloper_array[i].xltype = xltypeNum;
	}

	// Set-up the result XLOPER
	xl_function_result->xltype = xltypeMulti | xlbitDLLFree;
	xl_function_result->val.array.rows = (RW) rows;
	xl_function_result->val.array.columns = (COL) cols;
	xl_function_result->val.array.lparray = xloper_array;
}

void pack_double_matrix_oper(const d_matrix& inmatrix, XLOPER12* xl_function_result)
{
	unsigned int rows = (unsigned int) inmatrix.rows();
	unsigned int columns = (unsigned int) inmatrix.cols();

	unsigned int n = rows * columns;

	// Create the array to return
	XLOPER12* xloper_array = new XLOPER12[n];
	xloper_array->val.array.rows = rows;
	xloper_array->val.array.columns = columns;

	// Fill-up the array with the array values
	size_t idx = 0;
	for (unsigned int r = 0; r < rows; ++r)
	{
		for (unsigned int c = 0; c < columns; ++c)
		{
			idx = inmatrix.index(r, c);
			xloper_array[idx].val.num = inmatrix[idx];
			xloper_array[idx].xltype = xltypeNum;
		}
	}

	// Set-up the result XLOPER
	xl_function_result->xltype = xltypeMulti | xlbitDLLFree;
	xl_function_result->val.array.rows = (RW) rows;
	xl_function_result->val.array.columns = (COL) columns;
	xl_function_result->val.array.lparray = xloper_array;
}