/***
	 * OnDisable
	 */
	@Override
	public void onDisable() {
		
		// kicks all players when the plugin is disabled (server is shutdown)
		for (Player player : Bukkit.getOnlinePlayers()){
			player.kickPlayer(ChatColor.RED + _("shutdownKickMessage", player.getName()));
		}
		// if we don't have per-player language loaded from DB, do not try to load it now :-)
		avoidDB = true;
		
		// execute everything that should be executed on disable
		if (onDisableFunctions.size() > 0) {
			Class<?>[] proto = new Class[] {this.getClass()};
			Object[] params = new Object[] {this};
			
			for (String s : onDisableFunctions) {
				try {
					String[] ss = s.split("#####");
					Class<?> c = Class.forName(ss[0]);
					Method method = c.getDeclaredMethod(ss[1], proto);
					method.invoke(null, params);
				} catch (Throwable e) {
					LogHelper.logSevere("[CommandsEX] " + _("errorFunctionOnDisableExecute", "") + s);
					LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
				}
			}
		}
		
		// close all database connections
		LogHelper.logInfo("[" + this.getDescription().getName() + "] " + _("disableMsg", ""));
	}
	public void stopTimer(){
		stopTime = System.currentTimeMillis();
		finalTime = stopTime - startTime;
		if (getConf().getBoolean("startupTimer")){
			LogHelper.logInfo("[CommandsEx] " + _("startupTime", "") + finalTime + "ms");
		}
	}
	/***
	 * OnEnable
	 */
	@Override
	public void onEnable() {
		startTimer();
		// save default config if not saved yet
		getConfig().options().copyDefaults(true);
		saveConfig();
		
		// check for Vault plugin presence
		try {
			new Vault();
			vaultPresent = true;
		} catch (Throwable e) {}
		
		// set up commands listener
		cListener = new Commands(this);

		// initialize translations
		Language.init(this);

		// get description file and display initial startup OK info
		pdfFile = this.getDescription();
		LogHelper.logInfo("[" + pdfFile.getName() + "] " + _("startupMessage", "") + " " + Language.defaultLocale);
		LogHelper.logInfo("[" + pdfFile.getName() + "] " + _("version", "") + " " + pdfFile.getVersion() + " " + _("enableMsg", ""));

		// initialize database, if we have it included in our build
		Class<?>[] proto = new Class[] {this.getClass()};
		Object[] params = new Object[] {this};
		if (getConf().getBoolean("enableDatabase")) {
			try {
				Class<?> c = Class.forName("com.github.zathrus_writer.commandsex.SQLManager");
				Method method = c.getDeclaredMethod("init", proto);
				method.invoke(null, params);
			} catch (ClassNotFoundException e) {
				// this is OK, since we won't neccessarily have this class in each build
			} catch (Throwable e) {
				LogHelper.logSevere(_("dbError", ""));
				LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
			}
		}
		
		// enable existing classes that are listening to events - determine names from permissions
		// ... also call init() function for each helper class that requires initialization (has Init prefix in permissions)
		List<Permission> perms = CommandsEX.pdfFile.getPermissions();
		for(int i = 0; i <= perms.size() - 1; i++) {
			// call initialization function for each of the event handling functions
			String pName = perms.get(i).getName();
			if (pName.startsWith("Listener")) {
				String[] s = pName.split("\\.");
				if (s.length == 0) continue;
				try {
					Class.forName("com.github.zathrus_writer.commandsex.handlers.Handler_" + s[1]).newInstance();
				} catch (ClassNotFoundException e) {
					// this is OK, since we won't neccessarily have this class in each build
				} catch (Throwable e) {
					LogHelper.logSevere(_("loadTimeError", ""));
					LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
				}
			} else if (pName.startsWith("Init")) {
				String[] s = pName.split("\\.");
				if (s.length == 0) continue;
				try {
					Class<?> c = Class.forName("com.github.zathrus_writer.commandsex.helpers." + s[1]);
					Method method = c.getDeclaredMethod("init", proto);
					method.invoke(null, params);
				} catch (ClassNotFoundException e) {
					// this is OK, since we won't neccessarily have this class in each build
				} catch (Throwable e) {
					LogHelper.logSevere(_("loadTimeError", ""));
					LogHelper.logDebug("Message: " + e.getMessage() + ", cause: " + e.getCause());
				}
			}
		}
		
		// setup a recurring task that will periodically save players' play times into DB
		if (sqlEnabled) {
			getServer().getScheduler().scheduleAsyncRepeatingTask(this, new Runnable() {
				@Override
			    public void run() {
			        // flush play times only for players that have their playtime loaded initially
					Integer stamp = Utils.getUnixTimestamp(0L);
					Iterator<Entry<String, Integer>> it = CommandsEX.playTimes.entrySet().iterator();
					List<Object> insertParts = new ArrayList<Object>();
					List<Object> insertValues = new ArrayList<Object>();
					while (it.hasNext()) {
						Map.Entry<String, Integer> pairs = (Map.Entry<String, Integer>)it.next();
						
						// only update data for players that don't have -1 set as their playTime
						if (pairs.getValue() <= -1) continue;
						
						String pName = pairs.getKey();
						// update play time and join time
						Integer played = (pairs.getValue() + (stamp - CommandsEX.joinTimes.get(pName)));
						CommandsEX.playTimes.put(pName, played);
						CommandsEX.joinTimes.put(pName, Utils.getUnixTimestamp(0L));
						
						// prepare DB query parts
						insertParts.add("SELECT ? AS 'player_name', ? AS 'seconds_played'");
						insertValues.add(pName);
						insertValues.add(played);
						//it.remove(); // avoids a ConcurrentModificationException - not needed in our case and will clear out HashMap!
					}
					
					if (insertParts.size() > 0) {
						// update the database
						SQLManager.query("INSERT "+ (SQLManager.sqlType.equals("mysql") ? "" : "OR REPLACE ") +"INTO " + SQLManager.prefix + "playtime "+ Utils.implode(insertParts, " UNION ") + (SQLManager.sqlType.equals("mysql") ? " ON DUPLICATE KEY UPDATE seconds_played = VALUES(seconds_played)" : ""), insertValues);
					}
			    }
			}, (20 * playTimesFlushTime), (20 * playTimesFlushTime));
			
			// tell Bukkit we have some event handling to do in this class :-)
			this.getServer().getPluginManager().registerEvents(this, this);
		}
		
		try {
		    Metrics metrics = new Metrics(plugin);
		    metrics.start();
		} catch (IOException e) {

		}
		stopTimer();
	}