<?php

$mod->setDefaultCV("statistics", "statsstring", "^2Kills: <KILLS> ^1Deaths: <DEATHS> ^3K/D: <KPD> ^2time: <ONLINETIME> min ^3K/M: <KPM> ^2HS: <HEADSHOTS> ^7TKs: <TEAMKILLS>");
$mod->setDefaultCV("statistics", "whisperstats", 1);
$mod->setDefaultCV("statistics", "savestats", 0);

try {
    switch (strtolower($mod->getCV("statistics", "savestats"))) {
        case "sqlite":
            $stats = new stats_sqlite;
            break;
        case "mysql":
            $stats = new stats_mysql;
            break;
        default:
            $stats = new stats;
            break;
    }
}
catch (Exception $e) {
    $logging->write(MOD_ERROR, "Statistics: " . $e->getMessage());
}

$stats->initPlayers();

$mod->registerCommand("aliases", '~^aliases \S.*$~i', "command_aliases", $stats);
$mod->registerCommand("session", false, "command_session", $stats);
$mod->registerCommand("stats", false, "command_stats", $stats);
$mod->registerCommand("serverstats", false, "command_serverstats", $stats);
$mod->registerCommand("resetplayerstats", false, "command_resetplayerstats", $stats);
$mod->registerCommand("resetmystats", false, "command_resetmystats", $stats);

$mod->registerEvent("playerJoined", "event_playerJoined", $stats);
$mod->registerEvent("playerNameChange", "event_playerNameChange", $stats);
$mod->registerEvent("everyTime", "event_everyTime", $stats);
$mod->registerEvent("playerQuit", "event_playerQuit", $stats);

class stats {
    protected $mod;
    protected $players;
    protected $logging;

    private $aliases = array();
    private $stats = array();

    public function __construct()
    {
        $this->mod = &$GLOBALS['mod'];
        $this->players = &$GLOBALS['players'];
        $this->logging = &$GLOBALS['logging'];
    }

    public function initPlayers()
    {
        foreach (array_keys($this->players) as $guid) {
            $this->addAlias($guid, $this->players[$guid]->getName());
        }
    }

    public function event_everyTime()
    {
    }

    public function event_playerJoined($guid)
    {
        $this->addAlias($guid, $this->players[$guid]->getName());
    }

    public function event_playerNameChange($params)
    {
        list ($guid, $oldname) = $params;

        $this->addAlias($guid, $this->players[$guid]->getName());
    }

    public function event_playerQuit($guid)
    {
        $this->saveStats($guid);
    }

    public function command_aliases($guid, $parameters)
    {
        $search = implode(" ", $parameters);
        $player = $this->mod->findPlayerGuid($search);

        if (!$player) {
            $this->players[$guid]->say($this->mod->getLngString("playerNotFound", array("<SEARCH>"), array($search)));
            return;
        }

        $names = $this->getAliases($player);

        $str = "";

        foreach ($names as $name) {
            $str .= "^3" . $name . "^7, ";
        }

        $str = substr($str, 0, - 2);

        $this->players[$guid]->say($this->mod->getLngString("statsAliases") . $str);
    }

    public function command_stats($guid, $parameters)
    {
        $this->writeStatsStringToChat($guid, $this->getGlobalStats($guid));
    }

    public function command_session($guid, $parameters)
    {
        $this->writeStatsStringToChat($guid, $this->getSessionStats($guid));
    }

    public function command_serverstats($guid, $parameters)
    {
        $this->writeStatsStringToChat($guid, $this->getOverallStats());
    }

    public function command_resetplayerstats($guid, $parameters) {
	    $parameters = implode(" ", $parameters);

	    $player = $this->mod->findPlayerGuid($parameters);
	    if (!$player) {
	        $this->players[$guid]->say($this->mod->getLngString("playerNotFound", array("<SEARCH>"), array($parameters)));
	        return false;
	    }

	    $this->resetStats($player);
        $this->players[$guid]->say($this->mod->getLngString("PlayerStatsResetted", "<PLAYER_NAME>", $this->players[$player]->getName()));
    }

    public function command_resetmystats($guid, $parameters) {
        $this->resetStats($guid);
        $this->players[$guid]->say($this->mod->getLngString("YourStatsResetted"));
    }

    protected function addAlias($guid, $alias)
    {
        if (!array_key_exists($guid, $this->aliases) || !in_array($alias, $this->aliases[$guid])) {
            $this->aliases[$guid][] = $alias;
        }
    }

    protected function getAliases ($guid)
    {
        return $this->aliases[$guid];
    }

    protected function getGlobalStats($guid)
    {
        if (array_key_exists($guid, $this->stats)) {
            return self::addStats($this->stats[$guid], $this->getStatsOfPlayer($guid));
        }
        return $this->getStatsOfPlayer($guid);
    }

    protected function getSessionStats($guid)
    {
        return $this->getStatsOfPlayer($guid);
    }

    protected function getOverallStats()
    {
        $overall = self::getEmptyStat();
        foreach ($this->stats as $stat) {
            $overall = self::addStats($overall, $stat);
        }
        $overalll["lastseen"] = 0;
        return self::addStats($overall, $this->getOverallSessionStats());
    }

    protected function getOverallSessionStats()
    {
        $overall = self::getEmptyStat();
        foreach (array_keys($this->players) as $guid) {
            $overall = self::addStats($overall, $this->getStatsOfPlayer($guid));
        }
        $overalll["lastseen"] = 0;
        return $overall;
    }

    protected function saveStats($guid)
    {
        if (array_key_exists($guid, $this->stats)) {
            $this->stats[$guid] = self::addStats($this->stats[$guid], $this->getStatsOfPlayer($guid));
        }else {
            $this->stats[$guid] = $this->getStatsOfPlayer($guid);
        }
    }

    protected function writeStatsStringToChat ($guid, $stats)
    {
        $search = array(
            "<PLAYER_NAME>",
            "<KILLS>",
            "<DEATHS>",
            "<KPD>",
            "<KPM>",
            "<HPK>",
            "<TEAMKILLS>",
            "<TEAMDEATHS>",
            "<SELFKILLS>",
            "<ONLINETIME>",
            "<HEADSHOTS>",
            );

        $name = $this->players[$guid]->getName();
        $kills = $stats["kills"];
        $deaths = $stats["deaths"];
        $time = $stats["time"] / 60;
        $headshots = $stats["headshots"];
        $teamkills = $stats["teamkills"];
        $teamdeaths = $stats["teamdeaths"];
        $selfkills = $stats["selfkills"];

        $replace = array($name,
            $kills,
            $deaths,
            ($deaths == 0) ? "--" : round($kills / $deaths, 2),
            round($kills / $time, 2),
            ($kills == 0) ? "--" : round($headshots / $kills, 2),
            $teamkills,
            $teamdeaths,
            $selfkills,
            round($time),
            $headshots,
        );

        $say = str_replace($search, $replace, $this->mod->getCV("statistics", "statsstring"));

        if ($this->mod->getCV("statistics", "whisperstats")) {
            $this->players[$guid]->say($say);
        }
        else {
            $this->mod->rconSay($say);
        }
    }

    protected function getStatsOfPlayer ($guid)
    {
        $stats = self::getEmptyStat();
        $pl = &$this->players[$guid];

        $stats["kills"] = $pl->kills;
        $stats["deaths"] = $pl->deaths;
        $stats["headshots"] = $pl->headshots;
        $stats["teamkills"] = $pl->teamkills;
        $stats["teamdeaths"] = $pl->teamdeaths;
        $stats["selfkills"] = $pl->selfkills;
        $stats["time"] = time() - $pl->joined;
        $stats["lastseen"] = time();

        return $stats;
    }

    protected function resetStats($guid) {
        $this->stats[$guid] = self::getEmptyStat();
    }

    protected static function addStats($stats)
    {
        $return = self::getEmptyStat();
        $keys = array_keys(self::getEmptyStat());
        $lastseen = array();
        foreach (func_get_args() as $stat) {
            foreach ($keys as $key) {
                $return[$key] += $stat[$key];
                $lastseen[] = $stat["lastseen"];
            }
        }
        $return["lastseen"] = max($lastseen);
        return $return;
    }

    protected static function getEmptyStat()
    {
        return array("kills" => 0, "deaths" => 0, "headshots" => 0, "teamkills" => 0, "teamdeaths" => 0, "selfkills" => 0, "time" => 0, "lastseen" => 0);
    }
}

class stats_mysql extends stats {
    private $mysql;

    public function __construct()
    {
        parent::__construct();

        $this->checkExtension();

        $this->DBConnect();

        $this->checkTables();
    }

    private function checkExtension()
    {
        if (!extension_loaded('mysql')) {
            if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
                @dl("php_mysql.dll");
            }else {
                @dl("mysql.so");
            }
        }

        if (!function_exists("mysql_connect")) {
            throw new Exception("MySQL extension for PHP is not installed! Please install the MySQL extension to use this feature");
        }
    }

    private function checkTables() {
        $prefix = strtolower($this->mod->getCV("statistics_mysql", "prefix"));
        $tables[$prefix."user"] = "CREATE TABLE `:pfx:user` (
                                      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
                                      `guid` varchar(32) NOT NULL,
                                      `kills` int(10) unsigned NOT NULL DEFAULT '0',
                                      `deaths` int(10) unsigned NOT NULL DEFAULT '0',
                                      `headshots` int(10) unsigned NOT NULL DEFAULT '0',
                                      `teamkills` int(10) unsigned NOT NULL DEFAULT '0',
                                      `teamdeaths` int(10) unsigned NOT NULL DEFAULT '0',
                                      `selfkills` int(10) unsigned NOT NULL DEFAULT '0',
                                      `time` int(10) unsigned NOT NULL DEFAULT '0',
                                      `lastseen` int(10) unsigned NOT NULL DEFAULT '0',
                                      PRIMARY KEY (`id`),
                                      UNIQUE KEY `guid` (`guid`)
                                    ) ENGINE=InnoDB  DEFAULT CHARSET=latin1;";
        $tables[$prefix."nicks"] = "CREATE TABLE `:pfx:nicks` (
                                      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
                                      `user` int(11) unsigned NOT NULL,
                                      `nick` varchar(30) NOT NULL,
                                      PRIMARY KEY (`id`),
                                      KEY `user` (`user`)
                                    ) ENGINE=InnoDB  DEFAULT CHARSET=latin1;";

        $sql = "SHOW TABLES";
        $tablesInDB = $this->mysql->prepare($sql)->execute()->single_column();
        $tablesInDB = array_map("strtolower", $tablesInDB);
        foreach ($tables as $table => $query) {
            if (in_array($table, $tablesInDB)) continue;
            $this->mysql->prepare($query)->execute();
        }
    }

    private function DBConnect()
    {
        $host = $this->mod->getCV("statistics_mysql", "host");
        $user = $this->mod->getCV("statistics_mysql", "user");
        $pw = $this->mod->getCV("statistics_mysql", "password");
        $db = $this->mod->getCV("statistics_mysql", "database");
        $prefix = $this->mod->getCV("statistics_mysql", "prefix");

        $this->mysql = new mysql($host, $user, $pw, $db, $prefix);
    }

    protected function addAlias($guid, $name)
    {
        if (!$this->userExists($guid)) {
            $this->createNewUser($guid);
            $sql = "INSERT INTO :pfx:nicks (user, nick)
                    SELECT id, :1 AS nick FROM :pfx:user WHERE guid = :2";
            $this->mysql->prepare($sql)->execute($name, $guid);
            return;
        }
        $sql = "SELECT n.nick FROM :pfx:nicks n
                LEFT JOIN :pfx:user u ON u.id = n.user
                WHERE n.nick = :1 AND u.guid = :2";
        if (!$this->mysql->prepare($sql)->execute($name, $guid)->num_rows()) {
            $sql = "INSERT INTO :pfx:nicks (user, nick)
                    (SELECT id, :1 AS nick FROM :pfx:user WHERE guid = :2)";
            $this->mysql->prepare($sql)->execute($name, $guid);
        }
    }

    protected function getAliases($guid)
    {
        $sql = "SELECT n.nick nick
                FROM :pfx:nicks n
                LEFT JOIN :pfx:user u ON u.id = n.user
                WHERE u.guid = :1";
        return $this->mysql->prepare($sql)->execute($guid)->single_column("nick");
    }

    private function createNewUser($guid)
    {
        $sql = "INSERT INTO :pfx:user (guid) VALUES (:1)";
        $this->mysql->prepare($sql)->execute($guid);
    }

    private function userExists($guid)
    {
        $sql = "SELECT id FROM :pfx:user WHERE guid = :1";
        return (bool) $this->mysql->prepare($sql)->execute($guid)->num_rows();
    }

    protected function saveStats($guid)
    {
        $current = $this->getStatsOfPlayer($guid);
        $sql = "UPDATE :pfx:user
                SET kills = kills + ;2,
                    deaths = deaths + ;3,
                    headshots = headshots + ;4,
                    teamkills = teamkills + ;5,
                    teamdeaths = teamdeaths + ;6,
                    selfkills = selfkills + ;7,
                    time = time + ;8,
                    lastseen = UNIX_TIMESTAMP()
                WHERE guid = :1";
        $this->mysql->prepare($sql)->execute($guid,
            $current["kills"],
            $current["deaths"],
            $current["headshots"],
            $current["teamkills"],
            $current["teamdeaths"],
            $current["selfkills"],
            $current["time"]);
    }

    protected function getGlobalStats($guid)
    {
        $sql = "SELECT kills, deaths, headshots, teamkills, teamdeaths, selfkills, time, lastseen FROM :pfx:user WHERE guid = :1";
        $stats = $this->mysql->prepare($sql)->execute($guid)->fetch_assoc();
        return self::addStats($this->getStatsOfPlayer($guid), $stats);
    }

    protected function getOverallStats()
    {
        $sql = "SELECT sum(kills)      AS kills,
                       sum(deaths)     AS deaths,
                       sum(headshots)  AS headshots,
                       sum(teamkills)  AS teamkills,
                       sum(teamdeaths) AS teamdeaths,
                       sum(selfkills)  AS selfkills,
                       sum(time)       AS time,
                       0               AS lastseen
                FROM :pfx:user";
        return self::addStats($this->mysql->prepare($sql)->execute()->fetch_assoc(), $this->getOverallSessionStats());
    }

    protected function resetStats($guid) {
        $sql = "UPDATE :pfx:user
                SET kills = 0,
                    deaths = 0,
                    headshots = 0,
                    teamkills = 0,
                    teamdeaths = 0,
                    selfkills = 0,
                    time = 0,
                    lastseen = UNIX_TIMESTAMP()
                WHERE guid = :1";
        $this->mysql->prepare($sql)->execute($guid);
    }
}

class stats_sqlite extends stats {
    private $DBHandle;

    public function __construct()
    {
        parent::__construct();

        $this->checkExtension();

        $this->openDB();
    }

    private function checkExtension()
    {
        if (!extension_loaded('sqlite')) {
            if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
                @dl("php_sqlite.dll");
            }else {
                @dl("sqlite.so");
            }
        }

        if (!function_exists("sqlite_open")) {
            throw new Exception("SQLite extension for PHP is not installed! Please install the SQLite (v2.X) extension to use this feature");
        }
    }

    private function openDB()
    {
        $this->DBHandle = sqlite_open($this->mod->getConfigDir() . "/plugins/stats.db", 0666, $sqliteerror);
        if (!$this->DBHandle) {
            throw new Exception("Error opening stats.db: $sqliteerror");
        }
    }

    protected function addAlias($guid, $name)
    {
        $user = $this->getUser($guid);
        if (!$user) {
            $this->addUser($guid);
            $user = $this->getUser($guid);
        }

        $id = $user["id"];
        // Existiert der Nick bereits? Wenn nein, eintragen
        $query = "SELECT id FROM nicks WHERE user = $id AND name='" . sqlite_escape_string($name) . "'";

        if (sqlite_num_rows(sqlite_query($this->DBHandle, $query)) == 0) {
            $query = "INSERT INTO nicks (user, name) VALUES ($id, '" . sqlite_escape_string($name) . "')";
            sqlite_query($this->DBHandle, $query);
        }
    }

    protected function getAliases($guid)
    {
        $sql = "SELECT name
                FROM nicks AS n
                JOIN user AS u ON n.user = u.id
                WHERE u.guid = \"" . $guid . "\"";

        $result = sqlite_fetch_all(sqlite_query($this->DBHandle, $sql));
        $return = array();
        foreach ($result as $row) {
            $return[] = $row["name"];
        }
        return $return;
    }

    private function getUser($guid)
    {
        $result = sqlite_query($this->DBHandle, "SELECT * FROM user WHERE guid = '$guid'");
        if (sqlite_num_rows($result) == 1) {
            return sqlite_fetch_array($result);
        }
        return false;
    }

    private function addUser($guid)
    {
        sqlite_query($this->DBHandle, "INSERT INTO user
                                       (guid, kills, deaths, headshots, teamkills, teamdeaths, selfkills, time, lastseen)
                                       VALUES
                                       ('$guid', 0, 0, 0, 0, 0, 0, 0, " . time() . ")");
    }

    protected function getGlobalStats($guid)
    {
        $sql = "SELECT kills, deaths, headshots, teamkills, teamdeaths, selfkills, time, lastseen FROM user WHERE guid = '$guid'";
        $result = sqlite_query($this->DBHandle, $sql);
        return self::addStats(sqlite_fetch_array($result, SQLITE_ASSOC), $this->getStatsOfPlayer($guid));
    }

    protected function getOverallStats()
    {
        $sql = "SELECT sum(kills)      AS kills,
                       sum(deaths)     AS deaths,
                       sum(headshots)  AS headshots,
                       sum(teamkills)  AS teamkills,
                       sum(teamdeaths) AS teamdeaths,
                       sum(selfkills)  AS selfkills,
                       sum(time)       AS time,
                       0               AS lastseen
                FROM user";
        $result = sqlite_query($this->DBHandle, $sql);
        return self::addStats(sqlite_fetch_array($result, SQLITE_ASSOC), $this->getOverallSessionStats());
    }

    protected function saveStats($guid)
    {
        $current = $this->getStatsOfPlayer($guid);
        $sql = "UPDATE user
                SET kills = kills + $current[kills],
                    deaths = deaths + $current[deaths],
                    headshots = headshots + $current[headshots],
                    teamkills = teamkills + $current[teamkills],
                    teamdeaths = teamdeaths + $current[teamdeaths],
                    selfkills = selfkills + $current[selfkills],
                    time = time + $current[time],
                    lastseen = " . time() . "
                WHERE guid = '$guid'";
        sqlite_query($this->DBHandle, $sql);
    }

    protected function resetStats($guid) {
        $sql = "UPDATE user
                SET kills = 0,
                    deaths = 0,
                    headshots = 0,
                    teamkills = 0,
                    teamdeaths = 0,
                    selfkills = 0,
                    time = 0,
                    lastseen = " . time() . "
                WHERE guid = '$guid'";
        sqlite_query($this->DBHandle, $sql);
    }
}