/**
 * @file 
 * All the functions dealing with the NWNx database. Based on the APS include
 * with optimisations, renamed to avoid naming clashes with the APS system.
 * @author Primogenitor, motu99, fluffyamoeba
 * @date created on 2009-01-25
 */
 
 #include "prc_inc_switch"
 
const int PRC_SQL_ERROR = 0;
const int PRC_SQL_SUCCESS = 1;
const string XCHST_DB = "xchst_db";

//////////////////////////////////////////////////
/*             Function prototypes              */
//////////////////////////////////////////////////

// creates and stores a 1024 Byte string for database-fetches on the module
void PRC_SQLInit();

// executes an SQL command direclty
void PRC_SQLExecDirect(string sSQL);

// fetches data from a previous SQL fetch request; returns TRUE is fetch was successful
int PRC_SQLFetch();

// gets the actual data from a fetch; if a table with more than 1 column was requested, return the table element at column iCol
string PRC_SQLGetData(int iCol);

// SQLite and MYSQL use different syntaxes
string PRC_SQLGetTick();

// Problems can arise with SQL commands if variables or values have single quotes
// in their names. These functions are a replace these quote with the tilde character
string ReplaceSingleChars(string sString, string sTarget, string sReplace);

// only needed for SQLite; commits (actually stores) the changed to the database
void PRC_SQLiteCommit();
// starts the pseudo heartbeat for committing SQLite DB
void StartSQLiteCommitHB();
//  pseudo heartbeat for committing SQLite DB; interval given in switch PRC_DB_SQLITE_INTERVAL
// default is 600 seconds (= 10 min)
void SQLiteCommitHB();

// Set oObject's persistent object with sVarName to sValue
// Optional parameters:
//   iExpiration: Number of days the persistent variable should be kept in database (default: 0=forever)
//   sTable: Name of the table where variable should be stored (default: pwobjdata)
void PRC_SetPersistentObject(object oObject, string sVarName, object oObject2, int iExpiration = 0, string sTable = "pwobjdata");

// Get oObject's persistent object sVarName
// Optional parameters:
//   sTable: Name of the table where object is stored (default: pwobjdata)
// * Return value on error: 0
object PRC_GetPersistentObject(object oObject, string sVarName, object oOwner = OBJECT_INVALID, string sTable = "pwobjdata");

// Portable presistent chest system (X-Chest) nwnx database support
// 
void CreateXChestDB_SQLite();
void CreateXChestDB_MySQL();
void DeleteXChestDB();

//////////////////////////////////////////////////
/*             Function definitions             */
//////////////////////////////////////////////////

void PRC_SQLInit()
{
    int i;

    // Placeholder for ODBC persistence
    string sMemory;

    for (i = 0; i < 8; i++)     // reserve 8*128 bytes
        sMemory +=
            "................................................................................................................................";

    SetLocalString(GetModule(), "NWNX!ODBC!SPACER", sMemory);
}

void PRC_SQLExecDirect(string sSQL)
{
    SetLocalString(GetModule(), "NWNX!ODBC!EXEC", sSQL);
}

int PRC_SQLFetch()
{
    string sRow;
    object oModule = GetModule();

    // initialize the fetch string to a large 1024 Byte string; this will also trigger the fetch operation in NWNX
    SetLocalString(oModule, "NWNX!ODBC!FETCH", GetLocalString(oModule, "NWNX!ODBC!SPACER"));

    // get the result from the fetch
    sRow = GetLocalString(oModule, "NWNX!ODBC!FETCH");
    if (GetStringLength(sRow) > 0)
    {
        // store the result on the module and signal success
        SetLocalString(oModule, "NWNX_ODBC_CurrentRow", sRow);
        return PRC_SQL_SUCCESS;
    }
    else
    {
        // set the result string on the module to empty and signal failure
        SetLocalString(oModule, "NWNX_ODBC_CurrentRow", "");
        return PRC_SQL_ERROR;
    }
}

/** @todo check mySQL manual, not sure if this is needed - fluffyamoeba */
string PRC_SQLGetTick()
{
    if(GetPRCSwitch(PRC_DB_SQLITE))
	{
		return "";
	}
    else
	{
		return "`";
	}
}

string PRC_SQLGetData(int iCol)
{
    string sResultSet = GetLocalString(GetModule(), "NWNX_ODBC_CurrentRow");
    int iPos = FindSubString(sResultSet, "¬");
    if (iCol == 1)
    {
        // only one column returned ? Then we are finished
        if (iPos == -1)    return sResultSet;
        // more than one column returned ? Then return first column
        else            return GetStringLeft(sResultSet, iPos);
    }
     // more than one column requested, but only one returned ? Something went wrong; return empty string
    else if (iPos == -1)    return "";

    // find column in current row
    int iCount = 0;
    int nLength = GetStringLength(sResultSet);

    // loop through columns until found
    while (iCount++ != iCol)
    {
        if (iCount == iCol)
            return GetStringLeft(sResultSet, iPos);

        // pull off the previous column and find new column marker
        nLength -= (iPos + 1);
        sResultSet = GetStringRight(sResultSet, nLength);
        iPos = FindSubString(sResultSet, "¬");

        // special case: last column in row
        if (iPos == -1)    return sResultSet;
    }
    
    return sResultSet;
}

string ReplaceSingleChars(string sString, string sTarget, string sReplace)
{
    if (FindSubString(sString, sTarget) == -1) // not found
        return sString;

    int i;
    string sReturn = "";
    string sChar;

    // Loop over every character and replace special characters
    for (i = 0; i < GetStringLength(sString); i++)
    {
        sChar = GetSubString(sString, i, 1);
        if (sChar == sTarget)
            sReturn += sReplace;
        else
            sReturn += sChar;
    }
    return sReturn;
}

// only needed for SQLite; commits (actually stores) the changed to the database
void PRC_SQLiteCommit()
{
    PRC_SQLExecDirect("COMMIT");
    PRC_SQLExecDirect("BEGIN IMMEDIATE");
}

// starts the pseudo heartbeat for committing SQLite DB
void StartSQLiteCommitHB()
{
    if (GetPRCSwitch(PRC_DB_SQLITE))
    {    
        int nInterval = GetPRCSwitch(PRC_DB_SQLITE_INTERVAL);
        DelayCommand(nInterval ? IntToFloat(nInterval) : 600.0f, SQLiteCommitHB());
    }
}

//  pseudo heartbeat for committing SQLite DB; interval given in switch PRC_DB_SQLITE_INTERVAL
// default is 600 seconds (= 10 min)
void SQLiteCommitHB()
{
    // check if we are still using SQLite
    if (GetPRCSwitch(PRC_DB_SQLITE))
    {
        // do the commit
        PRC_SQLExecDirect("COMMIT");
        PRC_SQLExecDirect("BEGIN IMMEDIATE");
        
        // continue pseudo heartbeat
        int nInterval = GetPRCSwitch(PRC_DB_SQLITE_INTERVAL);
        DelayCommand(nInterval ? IntToFloat(nInterval) : 600.0f, SQLiteCommitHB());
    }
}

void PRC_SetPersistentObject(object oOwner, string sVarName, object oObject, int iExpiration = 0, string sTable = "pwobjdata")
{
    string sPlayer;
    string sTag;

    if (GetIsPC(oOwner))
    {
        sPlayer = ReplaceSingleChars(GetPCPlayerName(oOwner), "'", "~");
        sTag = ReplaceSingleChars(GetName(oOwner), "'", "~");
    }
    else
    {
        sPlayer = "~";
        sTag = GetTag(oOwner);
    }
    sVarName = ReplaceSingleChars(sVarName, "'", "~");

    string sSQL = "SELECT player FROM " + sTable + " WHERE player='" + sPlayer +
        "' AND tag='" + sTag + "' AND name='" + sVarName + "'";
    PRC_SQLExecDirect(sSQL);

    if (PRC_SQLFetch() == PRC_SQL_SUCCESS)
    {
        // row exists
        sSQL = "UPDATE " + sTable + " SET val=%s,expire=" + IntToString(iExpiration) +
            " WHERE player='" + sPlayer + "' AND tag='" + sTag + "' AND name='" + sVarName + "'";
        SetLocalString(GetModule(), "NWNX!ODBC!SETSCORCOSQL", sSQL);
        StoreCampaignObject ("NWNX", "-", oObject);
    }
    else
    {
        // row doesn't exist
        sSQL = "INSERT INTO " + sTable + " (player,tag,name,val,expire) VALUES" +
            "('" + sPlayer + "','" + sTag + "','" + sVarName + "',%s," + IntToString(iExpiration) + ")";
        SetLocalString(GetModule(), "NWNX!ODBC!SETSCORCOSQL", sSQL);
        StoreCampaignObject ("NWNX", "-", oObject);
    }
}

object PRC_GetPersistentObject(object oObject, string sVarName, object oOwner = OBJECT_INVALID, string sTable = "pwobjdata")
{
    string sPlayer;
    string sTag;
    object oModule;

    if (GetIsPC(oObject))
    {
        sPlayer = ReplaceSingleChars(GetPCPlayerName(oObject), "'", "~");
        sTag = ReplaceSingleChars(GetName(oObject), "'", "~");
    }
    else
    {
        sPlayer = "~";
        sTag = GetTag(oObject);
    }
    sVarName = ReplaceSingleChars(sVarName, "'", "~");

    string sSQL = "SELECT val FROM " + sTable + " WHERE player='" + sPlayer +
        "' AND tag='" + sTag + "' AND name='" + sVarName + "'";
    SetLocalString(GetModule(), "NWNX!ODBC!SETSCORCOSQL", sSQL);

    if (!GetIsObjectValid(oOwner))
        oOwner = oObject;
    return RetrieveCampaignObject ("NWNX", "-", GetLocation(oOwner), oOwner);
}

//////////////////////////////////////////////////
/*    Functions for portable persistent chest   */
//////////////////////////////////////////////////

void DeleteXChestDB()
{
    PRC_SQLExecDirect("DROP TABLE "+XCHST_DB);
}

void CreateXChestDB_SQLite()
{
    PRC_SQLExecDirect("CREATE TABLE "+XCHST_DB+" (" +
        "player varchar(64) NOT NULL default '~'," +
        "tag varchar(64) NOT NULL default '~'," +
        "name varchar(64) NOT NULL default '~'," +
        "val blob," +
        "expire int(11) default NULL," +
        "last timestamp NOT NULL default current_timestamp," +
        "PRIMARY KEY (player,tag,name)" +
        ")");
}

void CreateXChestDB_MySQL()
{
    PRC_SQLExecDirect("CREATE TABLE "+XCHST_DB+" (" +
        "player varchar(64) NOT NULL default '~'," +
        "tag varchar(64) NOT NULL default '~'," +
        "name varchar(64) NOT NULL default '~'," +
        "val blob," +
        "expire int(11) default NULL," +
        "last timestamp NOT NULL default CURRENT_TIMESTAMP," +
        "PRIMARY KEY  (player,tag,name)" +
        ") ENGINE=MyISAM DEFAULT CHARSET=latin1;");
}

//:: void main (){}