#ifdef DATABASE_MYSQL
#include <stdarg.h>
#include "TSqlDatabase.h"

/*
	Overrided Functions
*/
int TSqlDatabase::Initiate()
{
	return (!this->connect(
		mSettings->getStr("server"),
		mSettings->getStr("user"),
		mSettings->getStr("password"),
		mSettings->getStr("database"),
		mSettings->getStr("sockfile")
		));
}

/*
	Account-Management Functions
*/
bool TSqlDatabase::AccountExists(const CString& pAccountName)
{
	// Invalid Account
	if (pAccountName.isEmpty())
		return false;

	// Query
	CString query = CString() << "SELECT account FROM `" << mSettings->getStr("userlist") << "` WHERE account='" << pAccountName.escape() << "' LIMIT 1";
	std::vector<STRMAP> result;
	this->query(query, &result);

	// Return Exists
	return (result.size() != 0);
}

int TSqlDatabase::CreateAccount(const CString& pAccountName, const CString& pPassword)
{
	// Valid Account?
	if (pAccountName.length() < 4 || pAccountName.length() > 32)
		return CRACCT_INVACCT;

	// Valid Password
	if (pPassword.length() < 4 || pPassword.length() > 16)
		return CRACCT_INVPASS;

	// Account Exists?
	if (AccountExists(pAccountName))
		return CRACCT_EXISTS;

	// Create Account.. yay
	
	// Account Exists
	return CRACCT_SUCCESS;
}

int TSqlDatabase::VerifyAccount(CString& pAccountName, const CString& pPassword, bool pFromServer)
{
	// make sure its not empty.
	if (pAccountName.isEmpty())
		return ACCSTAT_INVALID;

	// definitions
	CString query;
	std::vector<STRMAP> result;
	CString password(pPassword);

	// See if we should try a secure login.
	int ret = ACCSTAT_INVALID;

	// Either the secure login failed or we didn't try a secure login.
	// Try the old login method.
	if (ret == ACCSTAT_INVALID)
	{
		query = CString()
			<< "SELECT password, salt, activated, banned, account"
			<< " FROM `" << mSettings->getStr("userlist") << "`"
			<< " WHERE account='" << pAccountName.escape() << "'"
			<< " AND password=" << "MD5(CONCAT(MD5('" << pPassword.escape() << "'), `salt`))"
			<< " LIMIT 1";

		if (this->query(query, &result) != 0)
		{
			// grab first result
			STRMAP row = result[0];

			// activated?
			if (row["activated"] != "1")
				return ACCSTAT_NONREG;

			// banned?
			if (row["banned"] != "0")
				return ACCSTAT_BANNED;

			// success
			pAccountName = row["account"];
			return ACCSTAT_NORMAL;
		}
	}
	
	return ACCSTAT_INVALID;
}

int TSqlDatabase::VerifyGuild(const CString& pGuildName, const CString& pAccountName, const CString& pNickName)
{
	// make sure it's not empty
	if (pAccountName.isEmpty() || pGuildName.isEmpty())
		return GUILDSTAT_DISALLOWED;
	
	// definitions
	std::vector<STRMAP> result;
	CString query = CString()
		<< "SELECT g.guild_name, g.guild_restrictnick, g.guild_status, m.player_nickname, u.account"
		<< " FROM `" << mSettings->getStr("guild_names").escape() << "` g, `" << mSettings->getStr("guild_members").escape() << "` m, `" << mSettings->getStr("userlist").escape() << "` u"
		<< " WHERE g.guild_name='" << pGuildName.escape() << "'"
		<< " AND u.account='" << pAccountName.escape() << "'"
		<< " AND g.guild_status='1' AND g.guild_id=m.guild_id AND m.player_id=u.id"
		<< " LIMIT 1";

	// query
	if (this->query(query, &result) != 0)
	{
		if (result.size() > 0)
		{
			// grab first result
			STRMAP row = result[0];

			// check guild
			int restrictNick = strtoint(row["guild_restrictnick"]);
			if (restrictNick && pNickName != row["player_nickname"])
				return GUILDSTAT_DISALLOWED;
			return GUILDSTAT_ALLOWED;
		}
	}
	
	return GUILDSTAT_DISALLOWED;
}

int TSqlDatabase::VerifyIpBan(const CString& pIpAddress, int pType)
{
	/*
	for (std::vector<CString>::const_iterator i = mIpBans[pType].begin(); i != mIpBans[pType].end(); ++i)
	{
		if (pIpAddress.match(*i))
			return 1;
	}
	*/

	return 0;
}

/*
	Profile-Management Functions
*/
int TSqlDatabase::GetProfile(const CString& pAccountName, CString& pPacket)
{
	// make sure it's not empty
	if (pAccountName.isEmpty())
		return 0;
	
	// definitions
	std::vector<STRMAP> result;
	CString query = CString()
		<< "SELECT profile_name, profile_age, profile_sex, profile_country, profile_icq, profile_email, profile_url, profile_hangout, profile_quote"
		<< " FROM `" << mSettings->getStr("userlist").escape() << "`"
		<< " WHERE account='" << pAccountName.escape() << "'"
		<< " LIMIT 1";
	pPacket.clear();
	
	// query
	if (this->query(query, &result) != 0)
	{
		if (result.size() > 0)
		{
			// grab first result
			STRMAP row = result[0];

			// return profile vars
			for (STRMAP::iterator i = row.begin(); i != row.end(); ++i)
			{
				CString val = (*i).second;
				if (val.length() > (0xDF+1))
					pPacket >> (char)0xDF << val.subString(0, 0xDF+1);
				else
					pPacket >> (char)val.length() << val;
			}

			// success
			return 1;
		}
	}

	// blank profiles
	for (int i = 0; i < 9; i++)
		pPacket >> (char)0;
	return 0;
}

int TSqlDatabase::SetProfile(const CString& pAccountName, CString& pPacket)
{
	return 0;
}

/*
	Private Functions
*/
bool TSqlDatabase::connect(const CString& pServer, const CString& pUsername, const CString& pPassword, const CString& pDatabase, const CString& pExternal)
{
	// Save Information
	this->mServer	= pServer;
	this->mUsername	= pUsername;
	this->mPassword	= pPassword;
	this->mDatabase	= pDatabase;
	this->mExternal	= pExternal;
	
	// Initiate MySql
	if ((mMySql = mysql_init(mMySql)) == NULL)
		return false;

	// Connect to MySql
	if (!mysql_real_connect(mMySql, mServer.text(), mUsername.text(), mPassword.text(), mDatabase.text(), 0, mExternal.text(), 0))
		return false;
	
	// Return Status
	return ping();
}

bool TSqlDatabase::ping()
{
	return (mysql_ping(mMySql) == 0);
}

const char * TSqlDatabase::error()
{
	return mysql_error(mMySql);
}

int TSqlDatabase::query(const CString& pQuery, std::vector<STRMAP> *pResult)
{
	MYSQL_RES *result;

	// run query
	if (mysql_query(mMySql, pQuery.text()))
		return 0;

	// no result wanted?
	if (pResult != 0)
	{
		// store result
		if (!(result = mysql_store_result(mMySql)))
			return 0;

		// get fields
		MYSQL_FIELD *fields = mysql_fetch_fields(result);

		// result
		MYSQL_ROW row;
		while ((row = mysql_fetch_row(result)))
		{
			STRMAP map;
			result_get(result, row, fields, &map);
			pResult->push_back(map);
		}

		// cleanup
		mysql_free_result(result);
		return pResult->size();
	}
	else
		return 0;
}

bool TSqlDatabase::result_get(MYSQL_RES *result, MYSQL_ROW row, MYSQL_FIELD *pFields, STRMAP *pResult)
{
	// clear map
	pResult->clear();

	// Grab the full value and add it to the vector.
	int fields = mysql_num_fields(result);
	unsigned long *lengths = mysql_fetch_lengths(result);

	// Iterate Fields
	for (int i = 0; i < fields; i++)
	{
		char *val = new char[lengths[i]+1];
		memcpy(val, row[i], lengths[i]);
		val[lengths[i]] = '\0';
		(*pResult)[pFields[i].name] = val;
		delete val;
	}

	return true;
}

void TSqlDatabase::result_output(std::vector<STRMAP> *pResult)
{
	int row = 0;
	for (std::vector<STRMAP>::iterator i = pResult->begin(); i != pResult->end(); ++i)
	{
		printf("Row %i\n", row++);
		for (STRMAP::iterator j = (*i).begin(); j != (*i).end(); ++j)
			printf("	Field %s -> %s\n", (*j).first.text(), (*j).second.text());
	}
}

#endif