/**
 * @file test_prepare_statement_memory_usage-t.cpp
 * @brief Examines the memory consumption of the prepared statement cache.. 
 * @details This test assesses the memory utilization of prepared statement metadata/backend cache memory.
 */

#include <string>
#include "mysql.h"
#include "tap.h"
#include "command_line.h"
#include "proxysql_utils.h"
#include "utils.h"

enum ComparisonOperator {
	kEqual = 0x00000001,
	kGreaterThan = 0x00000002,
	kLessThan = 0x00000004
};

int get_prepare_stmt_mem_usage(MYSQL* admin, uint64_t& prep_stmt_metadata_mem, uint64_t& prep_stmt_backend_mem) {
	prep_stmt_metadata_mem = prep_stmt_backend_mem = 0;
	MYSQL_QUERY_T(admin, "SELECT variable_name, variable_value FROM stats_memory_metrics WHERE \
		variable_name IN ('prepare_statement_metadata_memory', 'prepare_statement_backend_memory')");
	MYSQL_RES* myres = mysql_store_result(admin);
	while (MYSQL_ROW myrow = mysql_fetch_row(myres)) {
		if (strncmp(myrow[0], "prepare_statement_metadata_memory", sizeof("prepare_statement_metadata_memory") - 1) == 0) {
			prep_stmt_metadata_mem = std::stoull(myrow[1], nullptr, 10);
		} else if (strncmp(myrow[0], "prepare_statement_backend_memory", sizeof("prepare_statement_backend_memory") - 1) == 0) {
			prep_stmt_backend_mem = std::stoull(myrow[1], nullptr, 10);
		} else {
			fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid resultset");
			mysql_free_result(myres);
			return EXIT_FAILURE;
		}
	}
	mysql_free_result(myres);
	return EXIT_SUCCESS;
}

int check_prepare_statement_mem_usage(MYSQL* proxysql_admin, MYSQL* proxysql, const char* query, int prep_stmt_metadata_mem_comp,
	int prep_stmt_backend_mem_comp) {
	uint64_t old_prep_stmt_metadata_mem, old_prep_stmt_backend_mem;
	if (get_prepare_stmt_mem_usage(proxysql_admin, old_prep_stmt_metadata_mem, old_prep_stmt_backend_mem) == EXIT_FAILURE) {
		return EXIT_FAILURE;
	}
	MYSQL_STMT* stmt = mysql_stmt_init(proxysql);
	if (!stmt) {
		diag("mysql_stmt_init(), out of memory\n");
		return EXIT_FAILURE;
	}
	if (mysql_stmt_prepare(stmt, query, strlen(query))) {
		diag("query: %s", query);
		diag("mysql_stmt_prepare at line %d failed: %s", __LINE__, mysql_error(proxysql));
		mysql_stmt_close(stmt);
		return EXIT_FAILURE;
	} else {
		ok(true, "Prepare succeeded: %s", query);
	}
	uint64_t new_prep_stmt_metadata_mem, new_prep_stmt_backend_mem;
	if (get_prepare_stmt_mem_usage(proxysql_admin, new_prep_stmt_metadata_mem, new_prep_stmt_backend_mem) == EXIT_FAILURE) {
		mysql_stmt_close(stmt);
		return EXIT_FAILURE;
	}
	auto fnCompare = [](const uint64_t& val1, const uint64_t& val2, int co) -> bool {
		bool res = false;
		if ((co & kLessThan) == kLessThan) {
			if ((co & kEqual) == kEqual) {
				res = (val1 >= val2);
			} else {
				res = (val1 > val2);
			}
		} else if ((co & kGreaterThan) == kGreaterThan) {
			if ((co & kEqual) == kEqual) {
				res = (val1 <= val2);
			} else {
				res = (val1 < val2);
			}
		} else {
			res = (val1 == val2);
		}
		return res;
		};

	ok(fnCompare(old_prep_stmt_metadata_mem, new_prep_stmt_metadata_mem, prep_stmt_metadata_mem_comp),
		"Memory usage check [%d]. 'prepare_statement_metadata_memory':[%lu] [%lu]", prep_stmt_metadata_mem_comp,
		old_prep_stmt_metadata_mem, new_prep_stmt_metadata_mem);
	
	ok(fnCompare(old_prep_stmt_backend_mem, new_prep_stmt_backend_mem, prep_stmt_backend_mem_comp),
		"Memory usage check [%d]. 'prepare_statement_backend_memory':[%lu] [%lu]", prep_stmt_backend_mem_comp,
		old_prep_stmt_backend_mem, new_prep_stmt_backend_mem);

	mysql_stmt_close(stmt);
	usleep(10000);
	return EXIT_SUCCESS;
}

int main(int argc, char** argv) {

	CommandLine cl;

	if (cl.getEnv()) {
		diag("Failed to get the required environmental variables.");
		return -1;
	}

	plan(4 * // query
		 3 // checks
	);

	// Initialize Admin connection
	MYSQL* proxysql_admin = mysql_init(NULL);
	if (!proxysql_admin) {
		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
		return -1;
	}
	// Connnect to ProxySQL Admin
	if (!mysql_real_connect(proxysql_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) {
		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
		return exit_status();
	}

	// Initialize ProxySQL connection
	MYSQL* proxysql = mysql_init(NULL);
	if (!proxysql) {
		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql));
		return exit_status();
	}

	// Connect to ProxySQL
	if (!mysql_real_connect(proxysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql));
		return exit_status();
	}

	if (check_prepare_statement_mem_usage(proxysql_admin, proxysql, "SELECT 1", kGreaterThan, (kGreaterThan | kEqual)) == EXIT_FAILURE)
		goto __cleanup;

	if (check_prepare_statement_mem_usage(proxysql_admin, proxysql, "SELECT 2", kGreaterThan, (kGreaterThan | kEqual)) == EXIT_FAILURE)
		goto __cleanup;

	if (check_prepare_statement_mem_usage(proxysql_admin, proxysql, "SELECT 1", kGreaterThan, kEqual) == EXIT_FAILURE)
		goto __cleanup;

	if (check_prepare_statement_mem_usage(proxysql_admin, proxysql, "SELECT 2", kGreaterThan, kEqual) == EXIT_FAILURE)
		goto __cleanup;

__cleanup:
	mysql_close(proxysql);
	mysql_close(proxysql_admin);

	return exit_status();
}
