<?php

namespace MCTeam;

use ManiaControl\Admin\AuthenticationManager;
use ManiaControl\Callbacks\TimerListener;
use ManiaControl\Commands\CommandListener;
use ManiaControl\Files\FileUtil;
use ManiaControl\ManiaControl;
use ManiaControl\Players\Player;
use ManiaControl\Plugins\Plugin;

/**
 * ManiaControl GameMode Presets Plugin
 *
 * @author    ManiaControl Team <mail@maniacontrol.com>
 * @copyright 2014-2020 ManiaControl Team
 * @license   http://www.gnu.org/licenses/ GNU General Public License, Version 3
 */
class GameModePresetsPlugin implements Plugin, CommandListener, TimerListener {
	/*
	 * Constants
	 */
	const PLUGIN_ID      = 9;
	const PLUGIN_VERSION = 0.12;
	const PLUGIN_NAME    = 'GameMode Presets Plugin';
	const PLUGIN_AUTHOR  = 'MCTeam';

	const PRESET_SETTING_MODE_NUMBER = 'Mode Number';
	const PRESET_SETTING_SCRIPT_NAME = 'Script Name';

	const SETTING_MAP_ACTION_ON_LOADMODE          = 'Map Action on //loadmode';
	const SETTING_MAP_ACTION_DELAY_ON_LOADMODE    = 'Map Action Delay on //loadmode (in ms)';
	const SETTING_PERMISSION_LOAD_GAMEMODE_PRESET = 'Permission load GameMode Preset';
	const SETTING_PERMISSION_SAVE_GAMEMODE_PRESET = 'Permission save GameMode Preset';

	const MAP_ACTION_ON_LOADMODE_NONE    = 'None';
	const MAP_ACTION_ON_LOADMODE_RESTART = 'Restart Map';
	const MAP_ACTION_ON_LOADMODE_SKIP    = 'Skip Map';

	const TABLE_GAMEMODEPRESETS = 'mc_gamemodepresets';

	/*
	 * Private properties
	 */
	/** @var ManiaControl $maniaControl * */
	private $maniaControl = null;


	/**
	 * @see \ManiaControl\Plugins\Plugin::prepare()
	 */
	public static function prepare(ManiaControl $maniaControl) {
	}

	/**
	 * @see \ManiaControl\Plugins\Plugin::load()
	 */
	public function load(ManiaControl $maniaControl) {
		$this->maniaControl = $maniaControl;

		// Authentication Permission Level
		$this->maniaControl->getAuthenticationManager()->definePluginPermissionLevel(
			$this,
			self::SETTING_PERMISSION_LOAD_GAMEMODE_PRESET,
			AuthenticationManager::AUTH_LEVEL_ADMIN
		);
		$this->maniaControl->getAuthenticationManager()->definePluginPermissionLevel(
			$this,
			self::SETTING_PERMISSION_SAVE_GAMEMODE_PRESET,
			AuthenticationManager::AUTH_LEVEL_SUPERADMIN
		);

		// Settings
		$this->maniaControl->getSettingManager()->initSetting(
			$this,
			self::SETTING_MAP_ACTION_ON_LOADMODE,
			array(
				self::MAP_ACTION_ON_LOADMODE_NONE,
				self::MAP_ACTION_ON_LOADMODE_RESTART,
				self::MAP_ACTION_ON_LOADMODE_SKIP,
			)
		);
		$this->maniaControl->getSettingManager()->initSetting(
			$this,
			self::SETTING_MAP_ACTION_DELAY_ON_LOADMODE,
			1000
		);
		
		// Commands
		$this->maniaControl->getCommandManager()->registerCommandListener(array('loadmode', 'modeload'), $this, 'commandLoadMode', true, 'Loads the mode settings from the given preset name.');
		$this->maniaControl->getCommandManager()->registerCommandListener(array('savemode', 'modesave'), $this, 'commandSaveMode', true, 'Saves the mode settings under the given preset name.');
		$this->maniaControl->getCommandManager()->registerCommandListener(array('showmode', 'modeshow'), $this, 'commandShowMode', true, 'Shows the available game mode presets.');

		$this->initTables();
	}

	/**
	 * Initialize needed database tables
	 */
	private function initTables() {
		$mysqli = $this->maniaControl->getDatabase()->getMysqli();
		$query  = "CREATE TABLE IF NOT EXISTS `" . self::TABLE_GAMEMODEPRESETS . "` (
				`index` int(11) NOT NULL AUTO_INCREMENT,
				`name` varchar(20) NOT NULL,
				`settings` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
				PRIMARY KEY (`index`),
				UNIQUE KEY (`name`)
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;";
		$mysqli->query($query);
		if ($mysqli->error) {
			trigger_error($mysqli->error, E_USER_ERROR);
		}
	}

	/**
	 * Fetch preset from database
	 * @param string $name
	 * @return array|null
	 */
	private function fetchPreset($name) {
		$mysqli = $this->maniaControl->getDatabase()->getMysqli();
		$query  = "SELECT `settings`
				FROM `" . self::TABLE_GAMEMODEPRESETS . "`
				WHERE `name` LIKE ?;";

		$statement = $mysqli->prepare($query);
		if ($mysqli->error || !$statement) {
			trigger_error($mysqli->error, E_USER_ERROR);
			return null;
		}

		$statement->bind_param('s', $name);
		$statement->execute();
		if ($statement->error) {
			trigger_error($statement->error, E_USER_ERROR);
			$statement->close();
			return null;
		}

		$settings = null;
		$statement->store_result();
		$statement->bind_result($settings);
		$statement->fetch();
		$statement->free_result();

		return json_decode($settings, true);
	}

	/**
	 * Fetch preset from database
	 * @param string $name
	 * @return array|null
	 */
	private function storePreset($name) {
		$modeNumber = $this->maniaControl->getClient()->getGameMode();
		$scriptName = $this->maniaControl->getClient()->getScriptName()['CurrentValue'];
		$settings = null;
		if ($modeNumber === 0) {
			$settings = $this->maniaControl->getClient()->getModeScriptSettings();
		} else {
			$settings = $this->maniaControl->getClient()->execute('GetCurrentGameInfo');
		}

		$settings[self::PRESET_SETTING_MODE_NUMBER] = $modeNumber;
		$settings[self::PRESET_SETTING_SCRIPT_NAME] = $scriptName;
		$settings = json_encode($settings);

		$mysqli = $this->maniaControl->getDatabase()->getMysqli();
		$query  = "INSERT INTO `" . self::TABLE_GAMEMODEPRESETS . "`
				(`name`, `settings`) VALUES (?, ?)
				ON DUPLICATE KEY UPDATE `settings` = VALUES(`settings`);";

		$statement = $mysqli->prepare($query);
		if ($mysqli->error || !$statement) {
			trigger_error($mysqli->error, E_USER_ERROR);
			return false;
		}

		$statement->bind_param('ss', $name, $settings);
		$statement->execute();
		if ($statement->error) {
			trigger_error($statement->error, E_USER_ERROR);
			return false;
		}

		$statement->close();
		return true;
	}

	/**
	 * Load Script
	 * @param string $scriptName
	 */
	private function loadScript($scriptName) {
        static $scriptsDir = null;
        if ($scriptsDir === null)
        {
            $scriptsDataDir = FileUtil::shortenPath($this->maniaControl->getServer()->getDirectory()->getScriptsFolder());
            if ($this->maniaControl->getServer()->checkAccess($scriptsDataDir))
            {
                $gameShort = $this->maniaControl->getMapManager()->getCurrentMap()->getGame();
                $game = '';
                switch ($gameShort)
                {
                    case 'qm': $game = 'QuestMania'; break;
                    case 'sm': $game = 'ShootMania'; break;
                    case 'tm': $game = 'TrackMania'; break;
                }

                if ($game != '')
                {
                    $scriptsDir = $scriptsDataDir.DIRECTORY_SEPARATOR.'Modes'.DIRECTORY_SEPARATOR.$game.DIRECTORY_SEPARATOR;
                    if (!$this->maniaControl->getServer()->checkAccess($scriptsDir))
                        $scriptsDir = null;
                }
            }

            if ($scriptsDir === null)
                throw new \Exception('Scripts directory not found, unable to load different scripts!');
		}
		
		$scriptPath = $scriptsDir.$scriptName;
		if (!file_exists($scriptPath))
			throw new \Exception('Script not found ('.$scriptPath.').');

		$scriptText = file_get_contents($scriptPath);

        $this->maniaControl->getClient()->setModeScriptText($scriptText);
        $this->maniaControl->getClient()->setScriptName($scriptName);
	}

	/**
	 * Handle //loadmode command
	 *
	 * @param array                        $chatCallback
	 * @param \ManiaControl\Players\Player $player
	 */
	public function commandLoadMode(array $chatCallback, Player $player) {
		if (!$this->maniaControl->getAuthenticationManager()->checkPluginPermission($this, $player, self::SETTING_PERMISSION_LOAD_GAMEMODE_PRESET)) {
			$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
			return;
		}

		$params = explode(' ', $chatCallback[1][2]);
		if (count($params) < 2) {
			$this->maniaControl->getChat()->sendError('You must provide a gamemode preset name to load settings from!', $player);
			return;
		} elseif (count($params) > 2) {
			$this->maniaControl->getChat()->sendError('You can only provide one gamemode preset name to load settings from!', $player);
			return;
		}

		$presetName = strtolower($params[1]);
		$presetSettings = $this->fetchPreset($presetName);
		if (!$presetSettings) {
			$this->maniaControl->getChat()->sendError('The gamemode preset $<$g$z$fff' . $presetName . '$> does not exist, use $<$g$z$fff//showmode$> to see the available presets!', $player);
			return;
		}

		$modeNumber = $presetSettings[self::PRESET_SETTING_MODE_NUMBER];
		$scriptName = $presetSettings[self::PRESET_SETTING_SCRIPT_NAME];
		unset($presetSettings[self::PRESET_SETTING_MODE_NUMBER]);
		unset($presetSettings[self::PRESET_SETTING_SCRIPT_NAME]);

		// this is a hack, because this setting always throws errors otherwise
		$presetSettings['S_MatchmakingRematchRatio'] = floatval($presetSettings['S_MatchmakingRematchRatio']);

		try {
			$this->maniaControl->getClient()->setGameMode($modeNumber);
			if ($modeNumber === 0) {
				$this->loadScript($scriptName);
        		$this->maniaControl->getClient()->setModeScriptSettings($presetSettings);
			} else {
				$this->maniaControl->getClient()->setScriptName($scriptName);
				$this->maniaControl->getClient()->execute('SetGameInfos', $presetSettings);
			}
		} catch (\Exception $e) {
			$this->maniaControl->getChat()->sendException($e, $player);
			$this->maniaControl->getChat()->sendError('Unable to load gamemode preset $<$fff' . $presetName . '$>!', $player);
			return;
		}
		
		$authLevel = AuthenticationManager::getAuthLevelInt($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_PERMISSION_LOAD_GAMEMODE_PRESET));
		$this->maniaControl->getChat()->sendSuccessToAdmins($player->getEscapedNickname() . ' loaded gamemode preset $<$fff' . $presetName . '$>!', $authLevel);
		$this->maniaControl->getTimerManager()->registerOneTimeListening(
			$this,
			function () {
				$mapActionOnLoadmode = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_MAP_ACTION_ON_LOADMODE);
				switch ($mapActionOnLoadmode) {
					case self::MAP_ACTION_ON_LOADMODE_NONE:
					break;
					case self::MAP_ACTION_ON_LOADMODE_RESTART:
						$this->maniaControl->getMapManager()->getMapActions()->restartMap();
					break;
					case self::MAP_ACTION_ON_LOADMODE_SKIP:
						$this->maniaControl->getMapManager()->getMapActions()->skipMap();
					break;
				}
			},
			$this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_MAP_ACTION_DELAY_ON_LOADMODE)
		);
	}

	/**
	 * Handle //savemode command
	 *
	 * @param array                        $chatCallback
	 * @param \ManiaControl\Players\Player $player
	 */
	public function commandSaveMode(array $chatCallback, Player $player) {
		if (!$this->maniaControl->getAuthenticationManager()->checkPluginPermission($this, $player, self::SETTING_PERMISSION_SAVE_GAMEMODE_PRESET)) {
			$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
			return;
		}

		$params = explode(' ', $chatCallback[1][2]);
		if (count($params) < 2 || empty($params[1])) {
			$this->maniaControl->getChat()->sendError('You must provide a gamemode preset name to save settings into!', $player);
			return;
		} elseif (count($params) > 2) {
			$this->maniaControl->getChat()->sendError('You can only provide one gamemode preset name to save settings into!', $player);
			return;
		}

		$presetName = strtolower($params[1]);
		try {
			$this->storePreset($presetName);
		} catch (\Exception $e) {
			$this->maniaControl->getChat()->sendException($e, $player);
			$this->maniaControl->getChat()->sendError('Unable to save gamemode preset $<$fff' . $presetName . '$>!', $player);
			return;
		}

		$authLevel = AuthenticationManager::getAuthLevelInt($this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_PERMISSION_SAVE_GAMEMODE_PRESET));
		$this->maniaControl->getChat()->sendSuccessToAdmins($player->getEscapedNickname() . ' saved gamemode settings in preset $<$fff' . $presetName . '$>!', $authLevel);
	}

	/**
	 * Handle //showmode command
	 *
	 * @param array                        $chatCallback
	 * @param \ManiaControl\Players\Player $player
	 */
	public function commandShowMode(array $chatCallback, Player $player) {
		if (!$this->maniaControl->getAuthenticationManager()->checkPluginPermission($this, $player, self::SETTING_PERMISSION_LOAD_GAMEMODE_PRESET)) {
			$this->maniaControl->getAuthenticationManager()->sendNotAllowed($player);
			return;
		}

		$mysqli = $this->maniaControl->getDatabase()->getMysqli();
		$query  = "SELECT `name`
				FROM `" . self::TABLE_GAMEMODEPRESETS . "`;";
		$result = $mysqli->query($query);
		if ($mysqli->error) {
			trigger_error($mysqli->error);
			return null;
		}
		$presets = array();
		while ($preset = $result->fetch_object()) {
			array_push($presets, $preset->name);
		}
		$result->free();

		$this->maniaControl->getChat()->sendInformation('Available presets: $<$g$z$fff' . implode(', ', $presets) . '$>', $player);
	}

	/**
	 * @see \ManiaControl\Plugins\Plugin::getId()
	 */
	public static function getId() {
		return self::PLUGIN_ID;
	}

	/**
	 * @see \ManiaControl\Plugins\Plugin::getName()
	 */
	public static function getName() {
		return self::PLUGIN_NAME;
	}

	/**
	 * @see \ManiaControl\Plugins\Plugin::getVersion()
	 */
	public static function getVersion() {
		return self::PLUGIN_VERSION;
	}

	/**
	 * @see \ManiaControl\Plugins\Plugin::getAuthor()
	 */
	public static function getAuthor() {
		return self::PLUGIN_AUTHOR;
	}

	/**
	 * @see \ManiaControl\Plugins\Plugin::getDescription()
	 */
	public static function getDescription() {
		return "Plugin offers presets functionalites for GameModes";
	}

	/**
	 * @see \ManiaControl\Plugins\Plugin::unload()
	 */
	public function unload() {
	}
}