#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

#include <mysql.h>

void testStoreResult(std::string query);

int main(int argc, char **argv)
{
	std::string cursorQuery = "CALL testCursor()";
	std::string noCursorQuery = "CALL testNoCursor()";
	
	std::cout<< "Query: " << cursorQuery << std::endl;
	testStoreResult(cursorQuery);
	
	std::cout<< "Query: " << noCursorQuery << std::endl;
	testStoreResult(noCursorQuery);
	
	return 0;
}

void testStoreResult(std::string query)
{
	int status;
	int num_results = 0;

	MYSQL* mysql = mysql_init(NULL);

	if (!mysql_real_connect(mysql, "127.0.0.1", "maxscale", "password", "db1", 3306, 0, 0))
	{
		std::cout << "Failed to connect. Error " << mysql_errno(mysql) << " (" << mysql_sqlstate(mysql) << "): " << mysql_error(mysql) << std::endl;
	}

	MYSQL_STMT* mysql_stmt = mysql_stmt_init(mysql);

	if (mysql_stmt_prepare(mysql_stmt, query.c_str(), query.length()))
	{
		std::cout << "Failed to prepare statement. Error " << mysql_stmt_errno(mysql_stmt) << " (" << mysql_stmt_sqlstate(mysql_stmt) << "): " << mysql_stmt_error(mysql_stmt) << std::endl;
	}

	std::vector<MYSQL_BIND>params;

	params = std::vector <MYSQL_BIND>(0);

	if (mysql_stmt_bind_param(mysql_stmt, params.data()))
	{
		std::cout << "Failed to bind parameters. Error " << mysql_stmt_errno(mysql_stmt) << " (" << mysql_stmt_sqlstate(mysql_stmt) << "): " << mysql_stmt_error(mysql_stmt) << std::endl;
	}

	my_bool set_max_length = 1;

	if (mysql_stmt_attr_set(mysql_stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &set_max_length))
	{
		std::cout << "Failed to set max length. Error " << mysql_stmt_errno(mysql_stmt) << " (" << mysql_stmt_sqlstate(mysql_stmt) << "): " << mysql_stmt_error(mysql_stmt) << std::endl;
	}

	if (mysql_stmt_execute(mysql_stmt))
	{
		std::cout << "Failed to execute statement. Error " << mysql_stmt_errno(mysql_stmt) << " (" << mysql_stmt_sqlstate(mysql_stmt) << "): " << mysql_stmt_error(mysql_stmt) << std::endl;
	}

	do
	{
		int int_res_buffer;
		int num_fields = mysql_stmt_field_count(mysql_stmt);

		if (num_fields > 0)
		{
			num_results++;
		
			MYSQL_RES* res_metadata = mysql_stmt_result_metadata(mysql_stmt);

			if (!res_metadata)
			{
				std::cout << "Failed to get metadata. Error " << mysql_stmt_errno(mysql_stmt) << " (" << mysql_stmt_sqlstate(mysql_stmt) << "): " << mysql_stmt_error(mysql_stmt) << std::endl;
				break;
			}
			
			MYSQL_FIELD* fields = mysql_fetch_fields(res_metadata);

			MYSQL_BIND* res_bind = (MYSQL_BIND *) malloc(sizeof (MYSQL_BIND) * num_fields);
			
			if (!res_bind)
			{
				std::cout << "Failed to allocate bind array" << std::endl;
				break;
			}
			
			my_bool* is_null = (my_bool *) malloc(sizeof (my_bool) * num_fields);
			
			if (!is_null)
			{
				std::cout << "Failed to allocate NULL array" << std::endl;
				break;
			}			
			
			for (int i = 0; i < num_fields; ++i)
			{
				res_bind[i].buffer_type = fields[i].type;
				res_bind[i].is_null = &is_null[i];

				switch (fields[i].type)
				{
					case MYSQL_TYPE_LONG:
						res_bind[i].buffer = (char *) &int_res_buffer;
						res_bind[i].buffer_length = sizeof (int_res_buffer);
						break;

					default:
						std::cout <<  "ERROR: unexpected type: " <<  fields[i].type << std::endl;
						exit(1);
				}
			}

			if (mysql_stmt_bind_result(mysql_stmt, res_bind))
			{
				std::cout << "Failed to bind result. Error " << mysql_stmt_errno(mysql_stmt) << " (" << mysql_stmt_sqlstate(mysql_stmt) << "): " << mysql_stmt_error(mysql_stmt) << std::endl;
			}

			if (mysql_stmt_store_result(mysql_stmt))
			{
				std::cout << "Failed to store result. Error " << mysql_stmt_errno(mysql_stmt) << " (" << mysql_stmt_sqlstate(mysql_stmt) << "): " << mysql_stmt_error(mysql_stmt) << std::endl;
			}
			
			int num_rows = 0;
			
			while (1)
			{
				status = mysql_stmt_fetch(mysql_stmt);

				if (status == 1 || status == MYSQL_NO_DATA)
					break;

				num_rows++;

				for (int i = 0; i < num_fields; ++i)
				{
					switch (res_bind[i].buffer_type)
					{
						case MYSQL_TYPE_LONG:
							if (*res_bind[i].is_null)
								std::cout << " val[" << i << "] = NULL;" << std::endl;
							else
								std::cout << " val[" << i << "] = " << (long) *((int *) res_bind[i].buffer) << std::endl;
							break;

						default:
							std::cout << "unexpected type: " << res_bind[i].buffer_type << std::endl;
					}
				}
			}				
			
			std::cout << "Result set #" << num_results << " had " << num_rows << " rows" << std::endl; 
			
			free(is_null);
			free(res_bind);
			mysql_free_result(res_metadata);
		}

		status = mysql_stmt_next_result(mysql_stmt);
	} while (status == 0);

	std::cout << "Received " << num_results << " result sets" << std::endl;

	if (mysql_stmt_close(mysql_stmt))
	{
		std::cout << "Failed to close statement. Error " << mysql_errno(mysql) << " (" << mysql_sqlstate(mysql) << "): " << mysql_error(mysql) << std::endl;
	}

	mysql_close(mysql);
}
