#include "Database.h"
#include "Game.h"
#include "dConfig.h"
#include "dLogger.h"
using namespace std;

#pragma warning (disable:4251) //Disables SQL warnings

sql::Driver* Database::driver;
sql::Connection* Database::con;
sql::Properties Database::props;
std::string Database::database;

void Database::Connect(const string& host, const string& database, const string& username, const string& password) {

	//To bypass debug issues:
	const char* szDatabase = database.c_str();
	const char* szUsername = username.c_str();
	const char* szPassword = password.c_str();

	driver = sql::mariadb::get_driver_instance();

	sql::Properties properties;
	// The mariadb connector is *supposed* to handle unix:// and pipe:// prefixes to hostName, but there are bugs where
	// 1) it tries to parse a database from the connection string (like in tcp://localhost:3001/darkflame) based on the
	//    presence of a /
	// 2) even avoiding that, the connector still assumes you're connecting with a tcp socket
	// So, what we do in the presence of a unix socket or pipe is to set the hostname to the protocol and localhost,
	// which avoids parsing errors while still ensuring the correct connection type is used, and then setting the appropriate
	// property manually (which the URL parsing fails to do)
	const std::string UNIX_PROTO = "unix://";
	const std::string PIPE_PROTO = "pipe://";
    if (host.find(UNIX_PROTO) == 0) {
		properties["hostName"] = "unix://localhost";
		properties["localSocket"] = host.substr(UNIX_PROTO.length()).c_str();
    } else if (host.find(PIPE_PROTO) == 0) {
		properties["hostName"] = "pipe://localhost";
		properties["pipe"] = host.substr(PIPE_PROTO.length()).c_str();
    } else {
		properties["hostName"] = host.c_str();
    }
	properties["user"] = szUsername;
	properties["password"] = szPassword;
	properties["autoReconnect"] = "true";

	Database::props = properties;
	Database::database = database;

	Database::Connect();
}

void Database::Connect() {
	// `connect(const Properties& props)` segfaults in windows debug, but
	// `connect(const SQLString& host, const SQLString& user, const SQLString& pwd)` doesn't handle pipes/unix sockets correctly
	if (Database::props.find("localSocket") != Database::props.end() || Database::props.find("pipe") != Database::props.end()) {
		con = driver->connect(Database::props);
	} else {
		con = driver->connect(Database::props["hostName"].c_str(), Database::props["user"].c_str(), Database::props["password"].c_str());
	}
	con->setSchema(Database::database.c_str());
}

void Database::Destroy(std::string source, bool log) {
	if (!con) return;

	if (log) {
		if (source != "") Game::logger->Log("Database", "Destroying MySQL connection from %s!", source.c_str());
		else Game::logger->Log("Database", "Destroying MySQL connection!");
	}

	con->close();
	delete con;
} //Destroy

sql::Statement* Database::CreateStmt() {
	sql::Statement* toReturn = con->createStatement();
	return toReturn;
} //CreateStmt

sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) {
	const char* test = query.c_str();
	size_t size = query.length();
	sql::SQLString str(test, size);

	if (!con) {
		Connect();
		Game::logger->Log("Database", "Trying to reconnect to MySQL");
	}

	if (!con->isValid() || con->isClosed()) {
		delete con;

		con = nullptr;

		Connect();
		Game::logger->Log("Database", "Trying to reconnect to MySQL from invalid or closed connection");
	}

	auto* stmt = con->prepareStatement(str);

	return stmt;
} //CreatePreppedStmt

void Database::Commit() {
	Database::con->commit();
}

bool Database::GetAutoCommit() {
	// TODO This should not just access a pointer.  A future PR should update this
	// to check for null and throw an error if the connection is not valid.
	return con->getAutoCommit();
}

void Database::SetAutoCommit(bool value) {
	// TODO This should not just access a pointer.  A future PR should update this
	// to check for null and throw an error if the connection is not valid.
	Database::con->setAutoCommit(value);
}