/**
 * SQLX - Newer version of SQL stuff
 */

#if defined _sqlx_included
  #endinput
#endif
#define _sqlx_included

//eh..
#define SQL_NumRows SQL_NumResults

#if AMXX_VERSION_NUM >= 175
 #pragma reqclass sqlx
 #if !defined AMXMODX_NOAUTOLOAD
  #pragma defclasslib sqlx mysql
 #endif //!defined AMXMODX_NOAUTOLOAD
#endif //AMXX_VERSION_NUM

enum Handle
{
   Empty_Handle
};

/**
 * Creates a connection information tuple.
 * This tuple must be passed into connection routines.
 * Freeing the tuple is not necessary, but is a good idea if you 
 *  create many of them.  You can cache these handles globally.
 * !!NOTE!! I have seen most people think that this connects to the DB.
 *   Nowhere does it say this, and in fact it does not.  It only caches
 *   the connection information, the host/user/pass/etc.
 *
 * The optional timeout parameter specifies how long connections should wait before
 * giving up.  If 0, the default (which is undefined) is used.
 *
 */
native Handle:SQL_MakeDbTuple(const host[], const user[], const pass[], const db[], timeout=0);


/**
 * Frees an SQL handle.
 * The handle can be to anything (tuple, connection, query, results, etc).
 * If you free a database connection, it closes the connection as well.
 */
native SQL_FreeHandle(Handle:h);


/**
 * Opens a database connection.
 * Returns an SQL handle, which must be freed.
 * Returns Empty_Handle on failure.
 */
native Handle:SQL_Connect(Handle:cn_tuple, &errcode, error[], maxlength);


/**
 * Prepares a query.
 * The query must always be freed.
 * This does not actually do the query!
 */
native Handle:SQL_PrepareQuery(Handle:db, const fmt[], any:...);


/**
 * Back-quotes characters in a string for database querying.
 * Note: The buffer's maximum size should be 2*strlen(string) to catch
 * all scenarios.
 *
 * @param db				Database handle, for localization.
 * @param buffer			Buffer to copy to.
 * @param buflen			Maximum size of the buffer.
 * @param string			String to backquote (should not overlap buffer).
 * @return					Length of new string, or -1 on failure.
 */
native SQL_QuoteString(Handle:db, buffer[], buflen, const string[]);

/**
 * Back-quotes characters in a string for database querying.
 * Note: The buffer's maximum size should be 2*strlen(string) to catch
 * all scenarios.
 *
 * @param db				Database handle, for localization.
 * @param buffer			Buffer to copy to.
 * @param buflen			Maximum size of the buffer.
 * @param fmt				Format of string to backquote (should not overlap buffer).
 * @param ...				Format arguments.
 * @return					Length of new string, or -1 on failure.
 */
native SQL_QuoteStringFmt(Handle:db, buffer[], buflen, const fmt[], any:...);


#define TQUERY_CONNECT_FAILED	-2
#define TQUERY_QUERY_FAILED	-1
#define TQUERY_SUCCESS		0
/**
 * Prepares and executes a threaded query.
 * This will not interrupt gameplay in the event of a poor/lossed 
 *  connection, however, the interface is more complicated and 
 *  asynchronous.  Furthermore, a new connection/disconnection is 
 *  made for each query to simplify driver support.
 * The handler should look like:
 *
 * @param failstate - One of the three TQUERY_ defines.
 * @param query - Handle to the query, do not free it.
 * @param error - An error message, if any.
 * @param errnum - An error code, if any.
 * @param data - Data array you passed in.
 * @param size - Size of the data array you passed in.
 * @param queuetime - Amount of gametime that passed while the query was resolving.
 * 
 * public QueryHandler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime)
 *
 * Note! The handle you pass in is a DB Tuple, NOT an active connection!
 * Note! The handle does not need to be freed.
 * Also note: This function is not guaranteed to be in another thread
 *  (in fact - it's not).  You're seeing data "after the fact", 
 *  and as such to execute another query you should run 
 *  SQL_ThreadQuery again with new data.
 */
native SQL_ThreadQuery(Handle:db_tuple, const handler[], const query[], const data[]="", dataSize=0);


/**
 * Executes a query.
 * Returns 1 if the query succeeded.
 * Returns 0 if the query failed.
 * NOTE: You can call this multiple times as long as its parent
 *  connection is kept open.  Each time the result set will be freed
 *  from the previous call.
 */
native SQL_Execute(Handle:query);

/**
 * Gets information about a failed query error.
 * Returns the errorcode.
 */
native SQL_QueryError(Handle:query, error[], maxlength);


/**
 * Returns 1 if there are more results to be read,
 *  0 otherwise.
 */
native SQL_MoreResults(Handle:query);


/**
 * Tells whether a specific column in the current row
 *  is NULL or not.
 */
native SQL_IsNull(Handle:query, column);

/**
 * Retrieves the current result.
 * A successful query starts at the first result,
 *  so you should not call SQL_NextRow() first.
 * Passing no extra params - return int
 * Passing one extra param - return float in 1st extra arg
 * Passing two extra params - return string in 1st arg, max length in 2nd
 * Example:
 *  new num = SQL_ReadResult(query, 0)
 *  new Float:num2
 *  new str[32]
 *  SQL_ReadResult(query, 1, num2)
 *  SQL_ReadResult(query, 2, str, 31)
 */
native SQL_ReadResult(Handle:query, column, {Float,_}:...);


/**
 * Advances to the next result (return value should be ignored).
 */
native SQL_NextRow(Handle:query);


/** 
 * Returns the number of affected rows.
 */
native SQL_AffectedRows(Handle:query);


/**
 * Returns the number of rows total.
 */
native SQL_NumResults(Handle:query);


/**
 * Returns the number of columns total.
 */
native SQL_NumColumns(Handle:query);


/** 
 * Returns the name of a column.
 * Errors on a bad field number.
 */
native SQL_FieldNumToName(Handle:query, num, name[], maxlength);


/**
 * Returns the number of a named column, or -1 if not found.
 */
native SQL_FieldNameToNum(Handle:query, const name[]);


/**
 * Rewinds a result set to the first row.
 */
native SQL_Rewind(Handle:query);


/**
 * Returns the insert id of the last INSERT query. 
 * Returns 0 otherwise.
 */
native SQL_GetInsertId(Handle:query);


/**
 * Returns which driver this plugin is currently bound to.
 */
native SQL_GetAffinity(driver[], maxlen);

/**
 * Sets driver affinity.  You can use this to force a particular 
 *  driver implementation.  This will automatically change all SQL
 *  natives in your plugin to be "bound" to the module in question.
 * If no such module is found, it will return 0.  This isn't necessarily bad - 
 *  the user might have typed the wrong driver.  Unless your plugin is built
 *  to handle different driver types at once, you should let this error pass.
 * Note, that using this while you have open handles to another database 
 *  type will cause problems.  I.e., you cannot open a handle, switch
 *  affinity, then close the handle with a different driver.
 * Switching affinity is an O(n*m) operation, where n is the number of
 *  SQL natives and m is the number of used natives in total.
 * Intuitive programmers will note that this causes problems for threaded queries.
 *  You will have to either force your script to work under one affinity, or to 
 *  pack the affinity type into the query data, check it against the current, then
 *  set the new affinity if necessary.  Then, restore the old for safety.
 */
native SQL_SetAffinity(const driver[]);

/**
 * Returns the original query string that a query handle used.
 */
native SQL_GetQueryString(Handle:query, buffer[], maxlength);

/**
 * For queries which return multiple result sets, this advances to the next 
 * result set if one is available.  Otherwise, the current result set is 
 * destroyed and will no longer be accessible.
 *
 * This function will always return false on SQLite, and when using threaded 
 * queries in MySQL.  Nonetheless, it has the same effect of removing the last 
 * result set.
 *
 * @param query		Query Handle.
 * @return			True on success, false on failure.
 */
native bool:SQL_NextResultSet(Handle:query);

/**
 * This function can be used to find out if a table in a Sqlite database exists.
 * (updated for newer API)
 */
stock bool:sqlite_TableExists(Handle:db, const table[])
{
	new Handle:query = SQL_PrepareQuery(
					db,
					"SELECT name FROM sqlite_master WHERE type='table' AND name='%s' LIMIT 1;", 
					table);
					
	if (!SQL_Execute(query) || !SQL_NumResults(query))
	{
		SQL_FreeHandle(query);
		return false;
	}
	
	SQL_FreeHandle(query);

	return true;
}

/**
 * Use this for executing a query where you don't care about the result.
 * Returns 0 on failure, 1 on success
 */
stock SQL_SimpleQuery(Handle:db, const query[], error[]="", maxlength=0, &rows=0)
{
	new Handle:hQuery = SQL_PrepareQuery(db, "%s", query);
	
	if (!SQL_Execute(hQuery))
	{
		SQL_QueryError(hQuery, error, maxlength);
		SQL_FreeHandle(hQuery);
		return 0;
	}
	
	rows = SQL_NumResults(hQuery);
	
	SQL_FreeHandle(hQuery);
	
	return 1;
}

/**
 * Use this for executing a query where you don't care about the result.
 * Returns 0 on failure, 1 on success
 */
stock SQL_SimpleQueryFmt(Handle:db, error[]="", maxlength=0, &rows=0, const fmt[], any:...)
{
	static query_buf[2048];
	vformat(query_buf, 2047, fmt, 6);
	
	new Handle:hQuery = SQL_PrepareQuery(db, "%s", query_buf);
	
	if (!SQL_Execute(hQuery))
	{
		SQL_QueryError(hQuery, error, maxlength);
		SQL_FreeHandle(hQuery);
		return 0;
	}
	
	rows = SQL_NumResults(hQuery);
	
	SQL_FreeHandle(hQuery);
	
	return 1;
}

/**
 * Use this for executing a query and not caring about the error.
 * Returns -1 on error, >=0 on success (with number of affected rows)
 */
stock SQL_QueryAndIgnore(Handle:db, const queryfmt[], any:...)
{
	static query[4096];
	new Handle:hQuery;
	new ret;
	
	vformat(query, sizeof(query)-1, queryfmt, 3);
	
	hQuery = SQL_PrepareQuery(db, "%s", query);
	
	if (SQL_Execute(hQuery))
	{
		ret = SQL_AffectedRows(hQuery);
	} else {
		ret = -1;
	}
	
	SQL_FreeHandle(hQuery);
	
	return ret;
}

stock Handle:SQL_MakeStdTuple(timeout = 0)
{
	static host[64], user[32], pass[32], db[128];
	static get_type[12], set_type[12];
	
	get_cvar_string("amx_sql_host", host, 63);
	get_cvar_string("amx_sql_user", user, 31);
	get_cvar_string("amx_sql_pass", pass, 31);
	get_cvar_string("amx_sql_type", set_type, 11);
	get_cvar_string("amx_sql_db", db, 127);
	
	SQL_GetAffinity(get_type, 12);
	
	if (!equali(get_type, set_type))
	{
		if (!SQL_SetAffinity(set_type))
		{
			log_amx("Failed to set affinity from %s to %s.", get_type, set_type);
		}
	}
	
	return SQL_MakeDbTuple(host, user, pass, db, timeout);
}
