<?php
/**
 * GSManager
 *
 * This is a mighty and platform independent software for administrating game servers of various kinds.
 * If you need help with installing or using this software, please visit our website at: www.gsmanager.de
 * If you have licensing enquiries e.g. related to commercial use, please contact us at: sales@gsmanager.de
 *
 * @copyright Greenfield Concept UG (haftungsbeschränkt)
 * @license GSManager EULA <https://www.gsmanager.de/eula.php>
 * @version 1.2.2
**/

namespace GSM\Plugins\Randommapcycle;

use GSM\Daemon\Core\Utils;

/**
 * randommapcycle Class
 *
 * The randommapcycle plugin
 *
 */
class RandomMapCycle extends Utils {
    /**
     * Maps
     *
     * @var array $maps Maps
     */
    private $maps = array();
    /**
     * lastmaps
     *
     * @var array $lastmaps the last maps
     */
    private $lastmaps = array();
    /**
     * Chosen
     *
     * @var array $chosen
     */
    private $chosen = array();
    /**
     * Start
     *
     * @var int $start
     */
    private $start = 0;
    /**
     * last announce
     *
     * @var int $lastannounce
     */
    private $lastAnnounce = 0;
    /**
     * vote in progress
     *
     * @var boolean $voteinprogress
     */
    private $voteinprogress = false;
    /**
     * Skips
     *
     * @var array $skips
     */
    private $skips = array();
    /**
     * Gametypes
     *
     * @var array $gametypes
     */
    private $gametypes = array();
    /**
     * Voters
     *
     * @var array $voters
     */
    private $voters = array();
    /**
     * Votes
     *
     * @var array $votes
     */
    private $votes = array();
    private $job_id;

    /**
     * Inits the plugin
     *
     * This function initiates the plugin. This means that it register commands
     * default values, and events. It's important that every plugin has this function
     * Otherwise the plugin exists but can't be used
     */
    public function initPlugin() {
        parent::initPlugin();

        $this->config->setDefault('randommapcycle', 'enabled', true);
        $this->config->setDefault('randommapcycle', 'type', 2);
        $this->config->setDefault('randommapcycle', 'mapcount', 3);
        $this->config->setDefault('randommapcycle', 'duration', 300);
        $this->config->setDefault('randommapcycle', 'interval', 90);
        $this->config->setDefault('randommapcycle', 'maps', array('*'));
        $this->config->setDefault('randommapcycle', 'gametypes', array('*'));
        $this->config->setDefault('randommapcycle', 'ignorepriorgametype', true);
        $this->config->setDefault('randommapcycle', 'ignorepriormaps', 3);
        $this->config->setDefault('randommapcycle', 'skipquorum', '50%');
        $this->config->setDefault('randommapcycle', 'skipvoting', false);
    }

    /**
     * Function to enable this plugin
     */
    public function enable() {
        parent::enable();

        $this->events->register('nextMap', [$this, 'eventNextMap']);

        /* Fix if the vote command isn't enabled */
        if ($this->commands->getCommand('vote') === false) {
            $this->commands->register('vote', false, false, $this);
        }
        $this->commands->registerSubCommand('vote', 'choice', '~choice \d+~i', [$this, 'commandVmap']);
        $this->commands->registerSubCommand('vote', 'skip', false, [$this, 'commandSkip']);

        $this->readConfig();

        list($this->lastmaps[]) = $this->mod->getLastMaps(1);

        $this->eventNextMap();
    }

    /**
     * Function to disable this plugin
     */
    public function disable() {
        parent::disable();

        $this->events->unregister('nextMap', [$this, 'eventNextMap']);

        $this->commands->unregisterSubCommand('vote', 'choice');
        $this->commands->unregisterSubCommand('vote', 'skip');
    }

    /**
     * executes the readConfig event.
     */
    private function readConfig() {

        $this->maps = array();


        $allowed_maps = $this->config->get('randommapcycle', 'maps');
        if (is_array($allowed_maps) && isset($allowed_maps[0]) && $allowed_maps[0] == '*') {
            $this->maps = $this->mod->getMaps();
        } elseif (is_array($allowed_maps)) {
            $this->maps = $allowed_maps;
        }


        $allowed_gametypes = $this->config->get('randommapcycle', 'gametypes');
        if (is_array($allowed_gametypes) && isset($allowed_gametypes[0]) && $allowed_gametypes[0] == '*') {
            $this->gametypes = $this->mod->getGametypes();
        } elseif (is_array($allowed_gametypes)) {
            $this->gametypes = $allowed_gametypes;
        }
    }

    /**
     * executes the nextMap event.
     */
    public function eventNextMap() {

        $this->readConfig();

        $lastmaps = $this->mod->getLastMaps(2);

        if ($lastmaps[1] == $lastmaps[0]) {
            return;
        }

        $this->lastmaps[] = $lastmaps[0];

        $this->voteinprogress = false;

        //$last_map_count = count($this->lastmaps);

        /** Shift to old entrys */
        while (count($this->lastmaps) > $this->config->get('randommapcycle', 'ignorepriormaps')) {
            array_shift($this->lastmaps);
        }
        $this->lastmaps = array_merge($this->lastmaps);


        if ($this->config->get('randommapcycle', 'type') == 2) {
            $this->logging->debug("[Random MapCycle] Random map set.");
            $this->jobs->addSingleJob(20, array($this, "setRandomMap"));
        }
        if ($this->config->get('randommapcycle', 'type') == 1) {
            $this->jobs->addSingleJob(20, array($this, "startVote"));
        }
    }

    /**
     * announce if the vote is possible to skip.
     */
    public function setRandomMap() {
        $this->readConfig();

        $this->chooseMap(1);

        if (empty($this->chosen)) {
            return false;
        }

        $this->rcon->rconSetNextMap($this->chosen[0]['map'], $this->chosen[0]['gametype'], false);
        $this->skips = array();
        if ($this->config->get('randommapcycle', 'skipvoting')) {

            $this->announceSkipMessage();
        }
    }

    /**
     * the message on the server for the skip vote.
     */
    public function announceSkipMessage() {
        $this->rcon->rconSay($this->language->get('randommapcycle.voteforskip', array('<MAP>', '<GAMETYPE>'), array($this->mod->getLongMapName($this->chosen[0]['map']), $this->mod->getLongGametype($this->chosen[0]['gametype']))));
        $this->job_id = $this->jobs->addSingleJob($this->config->get('randommapcycle', 'interval'), array($this, 'announceSkipMessage'));
    }

    /**
     * execztes the command skip.
     *
     * @param   string      $guid       Guid of executing player
     * @param   string[]    $parameters The chatline splitted by " " without !command
     */
    public function commandSkip($guid, $parameters) {
        if ($this->config->get('randommapcycle', 'type') != 2 || !$this->config->get('randommapcycle', 'skipvoting')) {
            return;
        }

        if (in_array($guid, $this->skips)) {
            $this->players[$guid]->say($this->language->get('randommapcycle.alreadyvoted'));
        } else {
            $this->skips[] = $guid;

            if (substr(trim($this->config->get('randommapcycle', 'skipquorum')), -1) == '%') {
                $needed = round(((int) $this->config->get('randommapcycle', 'skipquorum')) / 100 * count($this->players));
            } else {
                $needed = (int) $this->config->get('randommapcycle', 'skipquorum');
            }

            $skippers = count($this->skips);
            $search = array('<SKIPPERS>', '<NEEDED>', '<MAP>', '<GAMETYPE>');
            $replace = array($skippers, $needed, $this->mod->getLongMapName($this->chosen[0]['map']), $this->mod->getLongGametype($this->chosen[0]['gametype']));
            $say = $this->language->get('randommapcycle.votedskip', $search, $replace);
            $this->rcon->rconSay($say);

            if ($skippers >= $needed) {
                $this->jobs->deleteJob($this->job_id);
                $this->setRandomMap();
            }
        }
    }

    /**
     * choose a map
     *
     * @param int $count
     */
    private function chooseMap($count) {
        $maps = $this->maps;
        $gametypes = $this->gametypes;

        foreach ($maps as $key => $value) {
            if (in_array($value, $this->lastmaps)) {
                unset($maps[$key]);
            }
        }

        if ($this->config->get('randommapcycle', 'ignorepriorgametype')) {
            $current = $this->mod->getCurrentGametype();
            foreach ($gametypes as $key => $value) {
                if ($value == $current) {
                    unset($gametypes[$key]);
                }
            }
        }

        $gametypes = array_merge($gametypes);

        if (count($gametypes) == 0) {
            $gametypes[0] = $this->mod->getCurrentGametype();
        }

        if (count($maps) < $count) {
            /* @todo logmarker */
            $this->logging->warning("[Random MapCycle] Couldn't choose a random map because count(maps) < $count");
            return false;
        }

        $this->chosen = array();
        for ($i = 1; $i <= $count; $i++) {
            $maps = array_merge($maps);
            $rand = rand(0, count($maps) - 1);

            if ($this->mod->getEngine() == 'frostbite3') {
                // $this->rcon->getMaps()->clear();
                $map_gametypes = $this->rcon->getMaps()->getAllowedGametypesOnMap($maps[$rand]);
                foreach ($gametypes as $key => $value) {
                    if (!in_array($value, $map_gametypes)) {
                        unset($gametypes[$key]);
                    }
                }
                $gametypes = array_merge($gametypes);
            }


            $rand2 = rand(0, count($gametypes) - 1);
            $this->chosen[] = array('map' => $maps[$rand], 'gametype' => $gametypes[$rand2]);
            unset($maps[$rand]);
        }
        return true;
    }

    /**
     * Ends a Vote
     */
    public function endVote() {
        $this->voteinprogress = false;

        arsort($this->votes);
        reset($this->votes);
        $won = key($this->votes);
        $this->rcon->rconSay($this->language->get('randommapcycle.wonvote', array('<MAP>', '<GAMETYPE>'), array($this->mod->getLongMapName($this->chosen[$won]['map']), $this->mod->getLongGametype($this->chosen[$won]['gametype']))));
        $this->rcon->rconSetNextMap($this->chosen[$won]['map'], $this->chosen[$won]['gametype'], false);
        $this->jobs->deleteJob($this->job_id);
    }

    /**
     * Starts a Vote
     */
    public function startVote() {
        $this->chooseMap($this->config->get('randommapcycle', 'mapcount'));

        $this->start = time();
        $this->voteinprogress = true;
        $this->votes = array_fill(0, $this->config->get('randommapcycle', 'mapcount'), 0);
        $this->voters = array();

        $this->announceVote();
        $this->jobs->addSingleJob($this->config->get('randommapcycle', 'duration'), array($this, "endVote"));
    }

    /**
     * Announces a Vote
     */
    public function announceVote() {

        $mapstr = array();
        foreach ($this->chosen as $id => $map) {
            $mapstr[] = $this->language->get('randommapcycle.list', array('<ID>', '<MAP>', '<GAMETYPE>'), array($id + 1, $this->mod->getLongMapName($map['map']), $this->mod->getLongGametype($map['gametype'])));
        }
        $mapstr = implode("\n", $mapstr);

        $this->rcon->rconSay($this->language->get('randommapcycle.voteformap') . "\n" . $mapstr);

        $this->job_id = $this->jobs->addSingleJob($this->config->get('randommapcycle', 'interval'), array($this, "announceVote"));
    }

    /**
     * executes the command vmap
     *
     * Example: !vmap 1-3
     *
     * @param   string      $guid       Guid of executing player
     * @param   string[]    $parameters The chatline splitted by " " without !command
     */
    public function commandVmap($guid, $parameters) {
        if ($this->config->get('randommapcycle', 'type') != 1) {
            return;
        }

        if (!$this->voteinprogress) {
            return;
        }

        //-1 because we start with 1 but count array with 0
        $map = $parameters[0] - 1;


        if (!array_key_exists($map, $this->chosen)) {
            return;
        }
        if (in_array($guid, $this->voters)) {
            return;
        }

        $this->voters[] = $guid;
        $this->votes[$map] ++;

        $str = $this->language->get('randommapcycle.voted', array('<MAP>', '<GAMETYPE>'), array($this->mod->getLongMapName($this->chosen[$map]['map']), $this->mod->getLongGametype($this->chosen[$map]['gametype'])));
        $this->players[$guid]->say($str);
    }
}
