/*
 * Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
 *
 * Thanks to the original authors: MaNGOS <http://www.mangosproject.org/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/// \addtogroup Trinityd
/// @{
/// \file

#include "Common.h"
#include "Language.h"
#include "Log.h"
#include "World.h"
#include "ScriptCalls.h"
#include "GlobalEvents.h"
#include "ObjectMgr.h"
#include "WorldSession.h"
#include "SystemConfig.h"
#include "Config/ConfigEnv.h"
#include "Util.h"
#include "AccountMgr.h"
#include "CliRunnable.h"
#include "MapManager.h"
#include "PlayerDump.h"
#include "Player.h"

//CliCommand and CliCommandHolder are defined in World.h to avoid cyclic deps

//func prototypes must be defined

void CliHelp(char*,pPrintf);
void CliInfo(char*,pPrintf);
void CliBan(char*,pPrintf);
void CliBanList(char*,pPrintf);
void CliRemoveBan(char*,pPrintf);
void CliSetGM(char*,pPrintf);
void CliListGM(char*,pPrintf);
void CliVersion(char*,pPrintf);
void CliExit(char*,pPrintf);
void CliIdleRestart(char*,pPrintf zprintf);
void CliRestart(char*,pPrintf zprintf);
void CliIdleShutdown(char*,pPrintf zprintf);
void CliShutdown(char*,pPrintf zprintf);
void CliBroadcast(char*,pPrintf);
void CliCreate(char*,pPrintf);
void CliDelete(char*,pPrintf);
void CliCharDelete(char *,pPrintf);
void CliLoadScripts(char*,pPrintf);
void CliKick(char*,pPrintf);
void CliTele(char*,pPrintf);
void CliMotd(char*,pPrintf);
void CliCorpses(char*,pPrintf);
void CliSetLogLevel(char*,pPrintf);
void CliUpTime(char*,pPrintf);
void CliSetAddon(char*,pPrintf);
void CliWritePlayerDump(char*,pPrintf);
void CliLoadPlayerDump(char*,pPrintf);
void CliSave(char*,pPrintf);
void CliSend(char*,pPrintf);
void CliPLimit(char*,pPrintf);
void CliSetPassword(char*,pPrintf);
/// Table of known commands
const CliCommand Commands[]=
{
    {"help", & CliHelp,"Display this help message"},
    {"broadcast", & CliBroadcast,"Announce in-game message"},
    {"create", & CliCreate,"Create account"},
    {"delete", & CliDelete,"Delete account and characters"},
    {"chardelete", & CliCharDelete,"Delete character"},
    {"info", & CliInfo,"Display Server infomation"},
    {"uptime", & CliUpTime, "Displays the server uptime"},
    {"motd", & CliMotd,"Change or display motd"},
    {"kick", & CliKick,"Kick user"},
    {"ban", & CliBan,"Ban account|ip"},
    {"listbans", & CliBanList,"List bans"},
    {"unban", & CliRemoveBan,"Remove ban from account|ip"},
    {"setgm", & CliSetGM,"Edit user privileges"},
    {"setpass", & CliSetPassword,"Set password for account"},
    {"setaddon", & CliSetAddon,"Set user expansion addon level allowed"},
    {"listgm", & CliListGM,"Display user privileges"},
    {"loadscripts", & CliLoadScripts,"Load script library"},
    {"setloglevel", & CliSetLogLevel,"Set Log Level"},
    {"corpses", & CliCorpses,"Manually call corpses erase global even code"},
    {"version", & CliVersion,"Display server version"},
    {"idlerestart", & CliIdleRestart,"Restart server with some delay when there are no active connections remaining"},
    {"restart", & CliRestart,"Restart server with some delay"},
    {"idleshutdown", & CliIdleShutdown,"Shutdown server with some delay when there are no active connections remaining"},
    {"shutdown", & CliShutdown,"Shutdown server with some delay"},
    {"exit", & CliExit,"Shutdown server NOW"},
    {"writepdump", &CliWritePlayerDump,"Write a player dump to a file"},
    {"loadpdump", &CliLoadPlayerDump,"Load a player dump from a file"},
    {"saveall", &CliSave,"Save all players"},
    {"send", &CliSend,"Send message to a player"},
    {"tele", &CliTele,"Teleport player to location"},
    {"plimit", &CliPLimit,"Show or set player login limitations"}
};
/// \todo Need some pragma pack? Else explain why in a comment.
#define CliTotalCmds sizeof(Commands)/sizeof(CliCommand)

#if PLATFORM == PLATFORM_WINDOWS
int utf8printf(const char* str,...)
{
    UTF8PRINTF(stdout,str,1);
    return 0;
}
#define UTF8ZPRINTF utf8printf
#else
#define UTF8ZPRINTF printf
#endif

/// Create a character dump file
void CliWritePlayerDump(char*command,pPrintf zprintf)
{
    char * file = strtok(command, " ");
    char * p2 = strtok(NULL, " ");
    if(!file || !p2)
    {
        zprintf("Syntax is: writepdump $filename $playerNameOrGUID\r\n");
        return;
    }

    std::string name;
    if(!consoleToUtf8(p2,name))                             // convert from console encoding to utf8
        return;

    if(!normalizePlayerName(name))
    {
        zprintf("Syntax is: writepdump $filename $playerNameOrGUID\r\n");
        return;
    }

    uint32 guid = objmgr.GetPlayerGUIDByName(name);
    if(!guid)
        guid = atoi(p2);

    if(!guid)
    {
        zprintf("Syntax is: writepdump $filename $playerNameOrGUID\r\n");
        return;
    }

    PlayerDumpWriter().WriteDump(file, guid);
}

/// Load a character from a dump file
void CliLoadPlayerDump(char*command,pPrintf zprintf)
{
    char * file = strtok(command, " ");
    char * acc = strtok(NULL, " ");
    if (!file ||!acc)
    {
        zprintf("Syntax is: loadpdump $filename $account ($newname) ($newguid)\r\n");
        return;
    }

    uint32 account_id = accmgr.GetId(acc);
    if(!account_id)
    {
        account_id = atoi(acc);
        if(account_id)
        {
            std::string acc_name;
            if(!accmgr.GetName(account_id,acc_name))
            {
                zprintf("Failed to load the character! Account not exist.\r\n");
                return;
            }
        }
        else
        {
            zprintf("Failed to load the character! Account not exist.\r\n");
            return;
        }
    }

    char * name_str = strtok(NULL, " ");
    char * guid_str = name_str ? strtok(NULL, " ") : NULL;

    uint32 guid = guid_str ? atoi(guid_str) : 0;

    std::string name;
    if(name_str)
    {
        if(!consoleToUtf8(name_str,name))                   // convert from console encoding to utf8
            return;

        if(!normalizePlayerName(name))
        {
            zprintf("Syntax is: loadpdump $filename $account ($newname) ($newguid)\r\n");
            return;
        }
    }

    if(PlayerDumpReader().LoadDump(file, account_id, name, guid))
        zprintf("Character loaded successfully!\r\n");
    else
        zprintf("Failed to load the character!\r\n");
}

/// Reload the scripts and notify the players
void CliLoadScripts(char*command,pPrintf zprintf)
{
    char const *del=strtok(command," ");
    if (!del)
        del="";
    if(!LoadScriptingModule(del))                           // Error report is already done by LoadScriptingModule
        return;

    sWorld.SendWorldText(LANG_SCRIPTS_RELOADED);
}

/// Delete a user account and all associated characters in this realm
/// \todo This function has to be enhanced to respect the login/realm split (delete char, delete account chars in realm, delete account chars in realm then delete account
void CliDelete(char*command,pPrintf zprintf)
{
    ///- Get the account name from the command line
    char *account_name_str=strtok(command," ");
    if(!account_name_str)
    {
        // \r\n is used because this function can also be called from RA
        zprintf("Syntax is: delete $account\r\n");
        return;
    }

    std::string account_name;
    if(!consoleToUtf8(account_name_str,account_name))       // convert from console encoding to utf8
        return;

    AccountOpResult result = accmgr.DeleteAccount(accmgr.GetId(account_name));
    switch(result)
    {
        case AOR_OK:
            zprintf("We deleted account: %s\r\n",account_name.c_str());
            break;
        case AOR_NAME_NOT_EXIST:
            zprintf("User %s does not exist\r\n",account_name.c_str());
            break;
        case AOR_DB_INTERNAL_ERROR:
            zprintf("User %s NOT deleted (probably sql file format was updated)\r\n",account_name.c_str());
            break;
        default:
            zprintf("User %s NOT deleted (unknown error)\r\n",account_name.c_str());
            break;
    }
}

void CliCharDelete(char*command,pPrintf zprintf)
{
    char *character_name_str = strtok(command," ");

    if(!character_name_str)
    {
        zprintf("Syntax is: chardelete $character_name\r\n");
        return;
    }

    std::string character_name;
    if(!consoleToUtf8(character_name_str,character_name))   // convert from console encoding to utf8
        return;

    if(!normalizePlayerName(character_name))
    {
        zprintf("Syntax is: chardelete $character_name\r\n");
        return;
    }

    Player *player = objmgr.GetPlayer(character_name.c_str());

    uint64 character_guid;
    uint32 account_id;

    if(player)
    {
        character_guid = player->GetGUID();
        account_id = player->GetSession()->GetAccountId();
        player->GetSession()->KickPlayer();
    }
    else
    {
        character_guid = objmgr.GetPlayerGUIDByName(character_name);
        if(!character_guid)
        {
            zprintf("Player %s not found!\r\n",character_name.c_str());
            return;
        }

        account_id = objmgr.GetPlayerAccountIdByGUID(character_guid);
    }

    Player::DeleteFromDB(character_guid, account_id, true); 
    zprintf("Player %s (Guid: %u AccountId: %u) deleted\r\n",character_name.c_str(),GUID_LOPART(character_guid),account_id);
}

/// Broadcast a message to the World
void CliBroadcast(char *text,pPrintf zprintf)
{
    std::string textUtf8;
    if(!consoleToUtf8(text,textUtf8))                       // convert from console encoding to utf8
        return;

    sWorld.SendWorldText(LANG_SYSTEMMESSAGE,textUtf8.c_str());
    zprintf("Broadcasting to the world: %s\r\n",textUtf8.c_str());
}

/// Print the list of commands and associated description
void CliHelp(char*,pPrintf zprintf)
{
    for (unsigned int x=0;x<CliTotalCmds;x++)
        zprintf("%-13s - %s.\r\n",Commands[x].cmd ,Commands[x].description);
}

/// Exit the realm
void CliExit(char*,pPrintf zprintf)
{
    zprintf( "Exiting daemon...\r\n" );
    World::m_stopEvent = true;
}

/// Restart the server (with some delay) as soon as no active connections remain on the server
void CliIdleRestart(char* command,pPrintf zprintf)
{
    char *args = strtok(command," ");

    if(!args)
    {
        zprintf("Syntax is: idlerestart $seconds|cancel\r\n");
        return;
    }

    if(std::string(args)=="cancel")
    {
        sWorld.ShutdownCancel();
    }
    else
    {

        uint32 time = atoi(args);

        ///- Prevent interpret wrong arg value as 0 secs shutdown time
        if(time==0 && (args[0]!='0' || args[1]!='\0') || time < 0)
        {
            zprintf("Syntax is: idlerestart $seconds|cancel\r\n");
            return;
        }

        sWorld.ShutdownServ(time,SHUTDOWN_MASK_RESTART|SHUTDOWN_MASK_IDLE);
    }
}

/// Restart the server with some delay
void CliRestart(char* command,pPrintf zprintf)
{
    char *args = strtok(command," ");

    if(!args)
    {
        zprintf("Syntax is: restart $seconds|cancel\r\n");
        return;
    }

    if(std::string(args)=="cancel")
    {
        sWorld.ShutdownCancel();
    }
    else
    {
        int32 time = atoi(args);

        ///- Prevent interpret wrong arg value as 0 secs shutdown time
        if(time==0 && (args[0]!='0' || args[1]!='\0') || time < 0)
        {
            zprintf("Syntax is: restart $seconds|cancel\r\n");
            return;
        }

        sWorld.ShutdownServ(time,SHUTDOWN_MASK_RESTART);
    }
}

/// Shutdown the server (with some delay) as soon as no active connections remain on the server
void CliIdleShutdown(char* command,pPrintf zprintf)
{
    char *args = strtok(command," ");

    if(!args)
    {
        zprintf("Syntax is: idleshutdown $seconds|cancel\r\n");
        return;
    }

    if(std::string(args)=="cancel")
    {
        sWorld.ShutdownCancel();
    }
    else
    {

        uint32 time = atoi(args);

        ///- Prevent interpret wrong arg value as 0 secs shutdown time
        if(time==0 && (args[0]!='0' || args[1]!='\0') || time < 0)
        {
            zprintf("Syntax is: idleshutdown $seconds|cancel\r\n");
            return;
        }

        sWorld.ShutdownServ(time,SHUTDOWN_MASK_IDLE);
    }
}

/// Shutdown the server with some delay
void CliShutdown(char* command,pPrintf zprintf)
{
    char *args = strtok(command," ");

    if(!args)
    {
        zprintf("Syntax is: shutdown $seconds|cancel\r\n");
        return;
    }

    if(std::string(args)=="cancel")
    {
        sWorld.ShutdownCancel();
    }
    else
    {
        int32 time = atoi(args);

        ///- Prevent interpret wrong arg value as 0 secs shutdown time
        if(time==0 && (args[0]!='0' || args[1]!='\0') || time < 0)
        {
            zprintf("Syntax is: shutdown $seconds|cancel\r\n");
            return;
        }

        sWorld.ShutdownServ(time);
    }
}

/// Display info on users currently in the realm
void CliInfo(char*,pPrintf zprintf)
{
    uint32 activeClientsNum = sWorld.GetActiveSessionCount();
    uint32 queuedClientsNum = sWorld.GetQueuedSessionCount();
    uint32 maxActiveClientsNum = sWorld.GetMaxActiveSessionCount();
    uint32 maxQueuedClientsNum = sWorld.GetMaxQueuedSessionCount();
    std::string timeStr = secsToTimeString(sWorld.GetUptime(),true);

    zprintf("Online players: %u (max: %u) queued: %u (max: %u) Uptime: %s\r\n",activeClientsNum,maxActiveClientsNum,queuedClientsNum,maxQueuedClientsNum,timeStr.c_str());

    ///- Get the list of accounts ID logged to the realm
    QueryResult *resultDB = CharacterDatabase.Query("SELECT name,account FROM characters WHERE online > 0");

    if (!resultDB)
        return;

    ///- Display the list of account/characters online
    zprintf("=====================================================================\r\n");
    zprintf("|    Account    |       Character      |       IP        | GM | Exp |\r\n");
    zprintf("=====================================================================\r\n");

    ///- Circle through accounts
    do
    {
        Field *fieldsDB = resultDB->Fetch();
        std::string name = fieldsDB[0].GetCppString();
        uint32 account = fieldsDB[1].GetUInt32();

        ///- Get the username, last IP and GM level of each account
        // No SQL injection. account is uint32.
        //                                                      0         1        2        3
        QueryResult *resultLogin = loginDatabase.PQuery("SELECT username, last_ip, gmlevel, expansion FROM account WHERE id = '%u'",account);

        if(resultLogin)
        {
            Field *fieldsLogin = resultLogin->Fetch();
            zprintf("|%15s| %20s | %15s |%4d|%5d|\r\n",
                fieldsLogin[0].GetString(),name.c_str(),fieldsLogin[1].GetString(),fieldsLogin[2].GetUInt32(),fieldsLogin[3].GetUInt32());

            delete resultLogin;
        }
        else
            zprintf("|<Error>        | %20s |<Error>          |<Er>|<Err>|\r\n",name.c_str());

    }while(resultDB->NextRow());

    delete resultDB;

    zprintf("=====================================================================\r\n");
}

/// Display a list of banned accounts and ip addresses
void CliBanList(char*,pPrintf zprintf)
{
    bool found = false;
    ///- Get the list of banned accounts and display them
    QueryResult *result = loginDatabase.Query("SELECT id,username FROM account WHERE id IN (SELECT id FROM account_banned WHERE active = 1)");
    if(result)
    {
        found = true;

        zprintf("Currently Banned Accounts:\r\n");
        zprintf("===============================================================================\r\n");
        zprintf("|    Account    |   BanDate    |   UnbanDate  |  Banned By    |   Ban Reason  |\r\n");
        do
        {
            zprintf("-------------------------------------------------------------------------------\r\n");
            Field *fields = result->Fetch();
            // No SQL injection. id is uint32.
            QueryResult *banInfo = loginDatabase.PQuery("SELECT bandate,unbandate,bannedby,banreason FROM account_banned WHERE id = %u AND active = 1 ORDER BY unbandate", fields[0].GetUInt32());
            if (banInfo)
            {
                Field *fields2 = banInfo->Fetch();
                do
                {
                    time_t t_ban = fields2[0].GetUInt64();
                    tm* aTm_ban = localtime(&t_ban);
                    zprintf("|%-15.15s|", fields[1].GetString());
                    zprintf("%02d-%02d-%02d %02d:%02d|", aTm_ban->tm_year%100, aTm_ban->tm_mon+1, aTm_ban->tm_mday, aTm_ban->tm_hour, aTm_ban->tm_min);
                    if ( fields2[0].GetUInt64() == fields2[1].GetUInt64() )
                        zprintf("   permanent  |");
                    else
                    {
                        time_t t_unban = fields2[1].GetUInt64();
                        tm* aTm_unban = localtime(&t_unban);
                        zprintf("%02d-%02d-%02d %02d:%02d|",aTm_unban->tm_year%100, aTm_unban->tm_mon+1, aTm_unban->tm_mday, aTm_unban->tm_hour, aTm_unban->tm_min);
                    }
                    zprintf("%-15.15s|%-15.15s|\r\n",fields2[2].GetString(),fields2[3].GetString());
                }while ( banInfo->NextRow() );
                delete banInfo;
            }
        }while( result->NextRow() );
        zprintf("===============================================================================\r\n");
        delete result;
    }

    ///- Get the list of banned IP addresses and display them
    result = loginDatabase.Query( "SELECT ip,bandate,unbandate,bannedby,banreason FROM ip_banned WHERE (bandate=unbandate OR unbandate>UNIX_TIMESTAMP()) ORDER BY unbandate" );
    if(result)
    {
        found = true;

        zprintf("Currently Banned IPs:\r\n");
        zprintf("===============================================================================\r\n");
        zprintf("|      IP       |   BanDate    |   UnbanDate  |  Banned By    |   Ban Reason  |\r\n");
        do
        {
            zprintf("-------------------------------------------------------------------------------\r\n");
            Field *fields = result->Fetch();
            time_t t_ban = fields[1].GetUInt64();
            tm* aTm_ban = localtime(&t_ban);
            zprintf("|%-15.15s|", fields[0].GetString());
            zprintf("%02d-%02d-%02d %02d:%02d|", aTm_ban->tm_year%100, aTm_ban->tm_mon+1, aTm_ban->tm_mday, aTm_ban->tm_hour, aTm_ban->tm_min);
            if ( fields[1].GetUInt64() == fields[2].GetUInt64() )
                zprintf("   permanent  |");
            else
            {
                time_t t_unban = fields[2].GetUInt64();
                tm* aTm_unban = localtime(&t_unban);
                zprintf("%02d-%02d-%02d %02d:%02d|", aTm_unban->tm_year%100, aTm_unban->tm_mon+1, aTm_unban->tm_mday, aTm_unban->tm_hour, aTm_unban->tm_min);
            }
            zprintf("%-15.15s|%-15.15s|\r\n", fields[3].GetString(), fields[4].GetString());
        }while( result->NextRow() );
        zprintf("===============================================================================\r\n");
        delete result;
    }

    if(!found)
        zprintf("We do not have banned users\r\n");
}

/// Ban an IP address or a user account
void CliBan(char*command,pPrintf zprintf)
{
    ///- Get the command parameter
    char* type_str = strtok((char*)command, " ");
    char* nameOrIP_str = strtok(NULL, " ");
    char* duration_str = strtok(NULL," ");
    char* reason_str = strtok(NULL,"");

    if(!type_str||!nameOrIP_str||!duration_str||!reason_str)// ?!? input of single char "0"-"9" wouldn't detect when with: || !atoi(duration)
    {
        zprintf("Syntax: ban account|ip|character $AccountOrIpOrCharacter $duration[s|m|h|d] $reason \r\n");
        return;
    }

    std::string type;
    if(!consoleToUtf8(type_str,type))                       // convert from console encoding to utf8
        return;

    std::string nameOrIP;
    if(!consoleToUtf8(nameOrIP_str,nameOrIP))               // convert from console encoding to utf8
        return;

    std::string duration;
    if(!consoleToUtf8(duration_str,duration))               // convert from console encoding to utf8
        return;

    std::string reason;
    if(!consoleToUtf8(reason_str,reason))                   // convert from console encoding to utf8
        return;

    switch (sWorld.BanAccount(type, nameOrIP, duration, reason, "Set by console."))
    {
        case BAN_SUCCESS:
            if(atoi(duration_str)>0)
                zprintf("%s is banned for %s. Reason: %s.\r\n",nameOrIP.c_str(),secsToTimeString(TimeStringToSecs(duration_str),true,false).c_str(),reason.c_str());
            else
                zprintf("%s is banned permanently. Reason: %s.\r\n",nameOrIP.c_str(),reason.c_str());
            break;
        case BAN_NOTFOUND:
            zprintf("%s %s not found\r\n", type.c_str(), nameOrIP.c_str());
            break;
        case BAN_SYNTAX_ERROR:
            zprintf("Syntax: ban account|ip|character $AccountOrIpOrCharacter $duration[s|m|h|d] $reason \r\n");
            break;
    }
}

/// Display %TrinIty version
void CliVersion(char*,pPrintf zprintf)
{
                                                            //<--maybe better append to info cmd
    zprintf( "%s (world-daemon)\r\n", _FULLVERSION );
}

/// Unban an IP adress or a user account
void CliRemoveBan(char *command,pPrintf zprintf)
{
    ///- Get the command parameter
    char *type_str = strtok(command," ");
    char *nameorip_str = strtok(NULL," ");
    if(!nameorip_str||!type_str)
    {
        zprintf("Syntax is: unban account|ip|character $nameorip\r\n");
        return;
    }

    std::string type;
    if(!consoleToUtf8(type_str,type))                       // convert from console encoding to utf8
        return;

    std::string nameorip;
    if(!consoleToUtf8(nameorip_str,nameorip))               // convert from console encoding to utf8
        return;

    if (!sWorld.RemoveBanAccount(type, nameorip))
        zprintf("%s %s not found\r\n", type.c_str(), nameorip.c_str());
    else
        zprintf("We removed ban from %s: %s\r\n",type_str,nameorip.c_str());
}

/// Display the list of GMs
void CliListGM(char*,pPrintf zprintf)
{

    ///- Get the accounts with GM Level >0
    Field *fields;

    QueryResult *result = loginDatabase.Query( "SELECT username,gmlevel FROM account WHERE gmlevel > 0" );
    if(result)
    {

        zprintf("Current gamemasters:\r\n");
        zprintf("========================\r\n");
        zprintf("|    Account    |  GM  |\r\n");
        zprintf("========================\r\n");

        ///- Circle through them. Display username and GM level
        do
        {
            fields = result->Fetch();
            zprintf("|%15s|", fields[0].GetString());
            zprintf("%6s|\r\n",fields[1].GetString());
        }while( result->NextRow() );

        zprintf("========================\r\n");
        delete result;
    }
    else
    {
        zprintf("No gamemasters\r\n");
    }
}

/// Set the GM level of an account
void CliSetGM(char *command,pPrintf zprintf)
{
    ///- Get the command line arguments
    char *szAcc = strtok(command," ");
    char *szLevel =  strtok(NULL," ");

    if(!szAcc||!szLevel)                                    //wrong syntax 'setgm' without name
    {
        zprintf("Syntax is: setgm $account $number (0 - normal, 3 - gamemaster)>\r\n");
        return;
    }

    //wow it's ok,let's hope it was integer given
    int lev=atoi(szLevel);                                  //get int anyway (0 if error)

    std::string safe_account_name;
    if(!consoleToUtf8(szAcc,safe_account_name))             // convert from console encoding to utf8
        return;

    ///- Convert Account name to Upper Format
    AccountMgr::normilizeString(safe_account_name);

    ///- Escape the account name to allow quotes in names
    loginDatabase.escape_string(safe_account_name);

    ///- Try to find the account, then update the GM level
    // No SQL injection (account name is escaped)
    QueryResult *result = loginDatabase.PQuery("SELECT id FROM account WHERE username = '%s'",safe_account_name.c_str());

    if (result)
    {
        Field *fields = result->Fetch();
        uint32 account_id = fields[0].GetUInt32();
        delete result;

        WorldSession* session = sWorld.FindSession(account_id);
        if(session)
            session->SetSecurity(lev);

        // No SQL injection (account name is escaped)
        loginDatabase.PExecute("UPDATE account SET gmlevel = '%d' WHERE username = '%s'",lev,safe_account_name.c_str());
        zprintf("We set %s gmlevel %d\r\n",safe_account_name.c_str(),lev);
    }
    else
    {
        zprintf("No account %s found\r\n",safe_account_name.c_str());
    }
}

/// Set password for account
void CliSetPassword(char *command,pPrintf zprintf)
{
    ///- Get the command line arguments
    char *szAcc = strtok(command," ");
    char *szPassword1 =  strtok(NULL," ");
    char *szPassword2 =  strtok(NULL," ");

    if(!szAcc||!szPassword1 || !szPassword2)
    {
        zprintf("Syntax is: setpass $account $password $password\r\n");
        return;
    }

    std::string account_name;
    if(!consoleToUtf8(szAcc,account_name))                  // convert from console encoding to utf8
        return;

    std::string pass1;
    if(!consoleToUtf8(szPassword1,pass1))                   // convert from console encoding to utf8
        return;

    std::string pass2;
    if(!consoleToUtf8(szPassword2,pass2))                   // convert from console encoding to utf8
        return;

    uint32 acc_id = accmgr.GetId(szAcc);
    if (!acc_id)
    {
        zprintf("Account '%s' does not exist!\r\n", account_name.c_str());
        return;
    }

    if (pass1 != pass2)
    {
        zprintf("Password does not match the confirm password, password not changed!\r\n");
        return;
    }

    AccountOpResult result = accmgr.ChangePassword(acc_id, pass1);

    switch(result)
    {
        case AOR_OK:
            zprintf("The password was changed for account '%s' (ID: %u).\r\n",account_name.c_str(),acc_id);
            break;
        case AOR_PASS_TOO_LONG:
            zprintf("Password can't be longer than 16 characters (client limit), password not changed!\r\n");
            break;
        case AOR_NAME_NOT_EXIST:
            zprintf("Account '%s' does not exist!\r\n", account_name.c_str());
            break;
        case AOR_DB_INTERNAL_ERROR:
            zprintf("Password not changed! (probably sql file format was updated)\r\n");
            break;
        default:
            zprintf("Password not changed! (unknown error\r\n");
            break;
    }
}

/// Create an account
void CliCreate(char *command,pPrintf zprintf)
{
    //I see no need in this function (why would an admin personally create accounts
    //instead of using account registration page or accessing db directly?)
    //but still let it be

    ///- %Parse the command line arguments
    char *szAcc = strtok(command, " ");
    char *szPassword = strtok(NULL, " ");
    if(!szAcc || !szPassword)
    {
        zprintf("Syntax is: create $username $password\r\n");
        return;
    }

    std::string account_name;
    if(!consoleToUtf8(szAcc,account_name))                   // convert from console encoding to utf8
        return;

    std::string password;
    if(!consoleToUtf8(szPassword,password))                 // convert from console encoding to utf8
        return;

    AccountOpResult result = accmgr.CreateAccount(account_name, password);
    switch(result)
    {
        case AOR_OK:
            zprintf("User %s with password %s created successfully\r\n",account_name.c_str(),password.c_str());
            break;
        case AOR_NAME_TOO_LONG:
            zprintf("Username %s is too long\r\n", account_name.c_str());
            break;
        case AOR_NAME_ALREDY_EXIST:
            zprintf("User %s already exists\r\n",account_name.c_str());
            break;
        case AOR_DB_INTERNAL_ERROR:
            zprintf("User %s with password %s NOT created (probably sql file format was updated)\r\n",account_name.c_str(),password.c_str());
            break;
        default:
            zprintf("User %s with password %s NOT created (unknown error)\r\n",account_name.c_str(),password.c_str());
            break;
    }
}

/// Command parser and dispatcher
void ParseCommand( pPrintf zprintf, char* input)
{
    unsigned int x;
    bool bSuccess=false;
    if (!input)
        return;

    unsigned int l=strlen(input);
    char *supposedCommand=NULL,* arguments=(char*)("");
    if(l)
    {
        ///- Get the command and the arguments
        supposedCommand = strtok(input," ");
        if (supposedCommand)
        {
            if (l>strlen(supposedCommand))
                arguments=&input[strlen(supposedCommand)+1];

            ///- Circle through the command table and, if found, put the command in the queue
            for ( x=0;x<CliTotalCmds;x++)
                if(!strcmp(Commands[x].cmd,supposedCommand))
            {
                sWorld.QueueCliCommand(new CliCommandHolder(&Commands[x], arguments, zprintf));
                bSuccess=true;
                break;
            }

            ///- Display an error message if the command is unknown
            if(x==CliTotalCmds)
                zprintf("Unknown command: %s\r\n", input);
        }
    }
    if (!bSuccess)
        zprintf("TC>");
}

/// Kick a character out of the realm
void CliKick(char*command,pPrintf zprintf)
{
    char *kickName = strtok(command, " ");

    if (!kickName)
    {
        zprintf("Syntax is: kick $charactername\r\n");
        return;
    }

    std::string name;
    if(!consoleToUtf8(kickName,name))                       // convert from console encoding to utf8
        return;
    
    if(!normalizePlayerName(name))
        return;

    sWorld.KickPlayer(name);
}

/// Teleport a character to location
void CliTele(char*command,pPrintf zprintf)
{
    char *charName = strtok(command, " ");
    char *locName = strtok(NULL, " ");

    if (!charName || !locName)
    {
        zprintf("Syntax is: tele $charactername $location\r\n");
        return;
    }

    std::string name = charName;
    if(!consoleToUtf8(charName,name))                       // convert from console encoding to utf8
        return;
    
    if(!normalizePlayerName(name))
        return;

    std::string location;
    if(!consoleToUtf8(locName,location))                    // convert from console encoding to utf8
        return;

    WorldDatabase.escape_string(location);
    QueryResult *result = WorldDatabase.PQuery("SELECT position_x,position_y,position_z,orientation,map FROM game_tele WHERE name = '%s'",location.c_str());
    if (!result)
    {
        zprintf(objmgr.GetTrinityStringForDBCLocale(LANG_COMMAND_TELE_NOTFOUND),"\r\n");
        return;
    }

    Field *fields = result->Fetch();
    float x = fields[0].GetFloat();
    float y = fields[1].GetFloat();
    float z = fields[2].GetFloat();
    float ort = fields[3].GetFloat();
    int mapid = fields[4].GetUInt16();
    delete result;

    if(!MapManager::IsValidMapCoord(mapid,x,y,z,ort))
    {
        zprintf(objmgr.GetTrinityStringForDBCLocale(LANG_INVALID_TARGET_COORD),"\r\n",x,y,mapid);
        return;
    }

    Player *chr = objmgr.GetPlayer(name.c_str());
    if (chr)
    {

        if(chr->IsBeingTeleported()==true)
        {
            zprintf(objmgr.GetTrinityStringForDBCLocale(LANG_IS_TELEPORTED),"\r\n",chr->GetName());
            return;
        }

        if(chr->isInFlight())
        {
            zprintf(objmgr.GetTrinityStringForDBCLocale(LANG_CHAR_IN_FLIGHT),"\r\n",chr->GetName());
            return;
        }

        zprintf(objmgr.GetTrinityStringForDBCLocale(LANG_TELEPORTING_TO),"\r\n",chr->GetName(),"", location.c_str());

        chr->SaveRecallPosition();

        chr->TeleportTo(mapid,x,y,z,chr->GetOrientation());
    }
    else if (uint64 guid = objmgr.GetPlayerGUIDByName(name.c_str()))
    {
        zprintf(objmgr.GetTrinityStringForDBCLocale(LANG_TELEPORTING_TO),"\r\n",name.c_str(), objmgr.GetTrinityStringForDBCLocale(LANG_OFFLINE), location.c_str());
        Player::SavePositionInDB(mapid,x,y,z,ort,MapManager::Instance().GetZoneId(mapid,x,y),guid);
    }
    else
        zprintf(objmgr.GetTrinityStringForDBCLocale(LANG_NO_PLAYER),"\r\n",name.c_str());
}

/// Display/Define the 'Message of the day' for the realm
void CliMotd(char*command,pPrintf zprintf)
{

    if (strlen(command) == 0)
    {
        zprintf("Current Message of the day: \r\n%s\r\n", sWorld.GetMotd());
        return;
    }
    else
    {
        std::string commandUtf8;
        if(!consoleToUtf8(command,commandUtf8))             // convert from console encoding to utf8
            return;

        sWorld.SetMotd(commandUtf8);
        zprintf("Message of the day changed to:\r\n%s\r\n", commandUtf8.c_str());
    }
}

/// Comment me
/// \todo What is CorpsesErase for?
void CliCorpses(char*,pPrintf)
{
    CorpsesErase();
}

/// Set the level of logging
void CliSetLogLevel(char*command,pPrintf zprintf)
{
    char *NewLevel = strtok(command, " ");
    if (!NewLevel)
    {
        zprintf("Syntax is: setloglevel $loglevel\r\n");
        return;
    }
    sLog.SetLogLevel(NewLevel);
}

/// Display the server uptime
void CliUpTime(char*,pPrintf zprintf)
{
    uint32 uptime = sWorld.GetUptime();
    std::string suptime = secsToTimeString(uptime,true,(uptime > 86400));
    zprintf("Server has been up for: %s\r\n", suptime.c_str());
}

/// Set/Unset the TBC flag for an account
void CliSetAddon(char *command,pPrintf zprintf)
{
    ///- Get the command line arguments
    char *szAcc = strtok(command," ");
    char *szExp =  strtok(NULL," ");

    if(!szAcc||!szExp)
    {
        zprintf("Syntax is: setbc $account $number (0 - normal, 1 - tbc, 2 - wotlk)>\r\n");
        return;
    }

    int lev=atoi(szExp);                                    //get int anyway (0 if error)

    if(lev < 0)
    {
        zprintf("Syntax is: setbc $account $number (0 - normal, 1 - tbc, 2 - wotlk)>\r\n");
        return;
    }

    ///- Escape the account name to allow quotes in names
    std::string safe_account_name;
    if(!consoleToUtf8(szAcc,safe_account_name))             // convert from console encoding to utf8
        return;

    ///- Convert Account name to Upper Format
    AccountMgr::normilizeString(safe_account_name);

    ///- Escape the account name to allow quotes in names
    loginDatabase.escape_string(safe_account_name);

    // No SQL injection (account name is escaped)
    QueryResult *result = loginDatabase.PQuery("SELECT 1 FROM account WHERE username = '%s'",safe_account_name.c_str());

    if (result)
    {
        // No SQL injection (account name is escaped)
        loginDatabase.PExecute("UPDATE account SET expansion = '%d' WHERE username = '%s'",lev,safe_account_name.c_str());
        zprintf("We set %s to expansion allowed %d\r\n",safe_account_name.c_str(),lev);

        delete result;
    }
    else
    {
        zprintf("No account %s found\r\n",safe_account_name.c_str());
    }
}

/// Save all players
void CliSave(char*,pPrintf zprintf)
{
    ///- Save players
    ObjectAccessor::Instance().SaveAllPlayers();
    zprintf( objmgr.GetTrinityStringForDBCLocale(LANG_PLAYERS_SAVED) );

    ///- Send a message
    sWorld.SendWorldText(LANG_PLAYERS_SAVED);
}

/// Send a message to a player in game
void CliSend(char *playerN,pPrintf zprintf)
{
    ///- Get the command line arguments
    char* name_str = strtok((char*)playerN, " ");
    char* msg_str = strtok(NULL, "");

    if(!name_str || !msg_str)
    {
        zprintf("Syntax: send $player $message (Player name is case sensitive)\r\n");
        return;
    }

    std::string name;
    if(!consoleToUtf8(name_str,name))                       // convert from console encoding to utf8
        return;

    std::string msg;
    if(!consoleToUtf8(msg_str,msg))             // convert from console encoding to utf8
        return;

    if(!normalizePlayerName(name))
    {
        zprintf("Syntax: send $player $message (Player name is case sensitive)\r\n");
        return;
    }

    ///- Find the player and check that he is not logging out.
    Player *rPlayer = objmgr.GetPlayer(name.c_str());
    if(!rPlayer)
    {
        zprintf("Player %s not found!\r\n", name.c_str());
        return;
    }

    if (rPlayer->GetSession()->isLogingOut())
    {
        zprintf("Cannot send message while player %s is logging out!\r\n",name.c_str());
        return;
    }

    ///- Send the message
    //Use SendAreaTriggerMessage for fastest delivery.
    rPlayer->GetSession()->SendAreaTriggerMessage("%s", msg.c_str());
    rPlayer->GetSession()->SendAreaTriggerMessage("|cffff0000[Message from administrator]:|r");

    //Confirmation message
    zprintf("Message '%s' sent to %s\r\n",msg.c_str(), name.c_str());
}

void CliPLimit(char *args,pPrintf zprintf)
{
    if(*args)
    {
        char* param = strtok((char*)args, " ");
        if(!param || !*param)
            return;

        int l = strlen(param);

        if(     strncmp(param,"player",l) == 0 )
            sWorld.SetPlayerLimit(-SEC_PLAYER);
        else if(strncmp(param,"moderator",l) == 0 )
            sWorld.SetPlayerLimit(-SEC_MODERATOR);
        else if(strncmp(param,"gamemaster",l) == 0 )
            sWorld.SetPlayerLimit(-SEC_GAMEMASTER);
        else if(strncmp(param,"administrator",l) == 0 )
            sWorld.SetPlayerLimit(-SEC_ADMINISTRATOR);
        else if(strncmp(param,"reset",l) == 0 )
            sWorld.SetPlayerLimit(sConfig.GetIntDefault("PlayerLimit", DEFAULT_PLAYER_LIMIT));
        else
        {
            int val = atoi(param);
            if(val < -SEC_ADMINISTRATOR) val = -SEC_ADMINISTRATOR;

            sWorld.SetPlayerLimit(val);
        }

        // kick all low security level players
        if(sWorld.GetPlayerAmountLimit() > SEC_PLAYER)
            sWorld.KickAllLess(sWorld.GetPlayerSecurityLimit());
    }

    uint32 pLimit = sWorld.GetPlayerAmountLimit();
    AccountTypes allowedAccountType = sWorld.GetPlayerSecurityLimit();
    char const* secName = "";
    switch(allowedAccountType)
    {
        case SEC_PLAYER:        secName = "Player";        break;
        case SEC_MODERATOR:     secName = "Moderator";     break;
        case SEC_GAMEMASTER:    secName = "Gamemaster";    break;
        case SEC_ADMINISTRATOR: secName = "Administrator"; break;
        default:                secName = "<unknown>";     break;
    }

    zprintf("Player limits: amount %u, min. security level %s.\r\n",pLimit,secName);
}

/// @}

#ifdef linux
// Non-blocking keypress detector, when return pressed, return 1, else always return 0
int kb_hit_return()
{
    struct timeval tv;
    fd_set fds;
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);
    select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
    return FD_ISSET(STDIN_FILENO, &fds);
}
#endif

/// %Thread start
void CliRunnable::run()
{
    ///- Init new SQL thread for the world database (one connection call enough)
    WorldDatabase.ThreadStart();                                // let thread do safe mySQL requests

    char commandbuf[256];

    ///- Display the list of available CLI functions then beep
    sLog.outString();
    /// \todo Shoudn't we use here also the sLog singleton?
    CliHelp(NULL,&UTF8ZPRINTF);

    if(sConfig.GetBoolDefault("BeepAtStart", true))
    {
        printf("\a");                                       // \a = Alert
    }

    // print this here the first time
    // later it will be printed after command queue updates
    printf("TC>");

    ///- As long as the World is running (no World::m_stopEvent), get the command line and handle it
    while (!World::m_stopEvent)
    {
        fflush(stdout);
        #ifdef linux
        while (!kb_hit_return() && !World::m_stopEvent)
            // With this, we limit CLI to 10commands/second
            usleep(100);
        if (World::m_stopEvent)
            break;
        #endif
        char *command = fgets(commandbuf,sizeof(commandbuf),stdin);
        if (command != NULL)
        {
            for(int x=0;command[x];x++)
                if(command[x]=='\r'||command[x]=='\n')
            {
                command[x]=0;
                break;
            }
            //// \todo Shoudn't we use here also the sLog singleton?
            ParseCommand(&UTF8ZPRINTF,command);
        }
        else if (feof(stdin))
        {
            World::m_stopEvent = true;
        }
    }

    ///- End the database thread
    WorldDatabase.ThreadEnd();                                  // free mySQL thread resources
}
