gt: 				off = argument.trim().equalsIgnoreCase("off");
context: package com.joelchristophel.sourceradio;
  
  import java.io.Closeable;
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.net.UnknownHostException;
  import java.sql.Types;
  import java.text.Normalizer;
  import java.text.Normalizer.Form;
  import java.time.Duration;
  import java.time.Instant;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Scanner;
  import java.util.Set;
  
  /**
   * A <code>Playlist</code> listens for players' song requests and manages the playing and queuing of these {@link Song
   * Songs}. In addition to song requests, a <code>Playlist</code> also listen for other {@link Command Commands} issued
   * by players.
   * 
   * <p>
   * <code>Playlist</code> is a singleton, meaning that only one instance is allowed to exist. To obtain this instance,
   * use {@link #getInstance}.
   * </p>
   * 
   * @author Joel Christophel
   */
  public class Playlist implements Closeable {
  
  	private static Properties properties = Properties.getInstance();
  	private static Playlist instance;
  	private ScriptWriter scriptWriter;
  	private DatabaseManager database;
  	private Player owner;
  	private Set<Player> admins;
  	private Set<Player> bannedPlayers;
  	private Set<String> blockedSongs;
  	private LogReader logReader;
  	private Game game;
  	private boolean commandVocalization;
  	private boolean shareCommandVocals;
  	private int volumeIncrement;
  	private int durationLimit;
  	private int playerSongLimit;
  	private int queueLimit;
  	private Song currentSong;
  	private List<Song> songRequests = new ArrayList<Song>();
  	private Map<Player, List<Runnable>> playerDiscoveryJobs = new HashMap<Player, List<Runnable>>();
  	private List<Song> songQueue = new ArrayList<Song>();
  	private Instant timeOfLastRequest;
  	private Instant timeOfCurrentRequest;
  	private static String CONNECTION_ERROR = "Error: Could not connect to the Internet. Check your connection.";
  
  	public static void main(String[] args) {
  		System.out.println();
  		String version = Playlist.class.getPackage().getImplementationVersion();
  		version = version == null ? "" : version;
  		System.out.println("**** SourceRadio" + (version.isEmpty() ? "" : " v" + version) + " ****");
  		System.out.println();
  		List<String> argsList = args == null ? new ArrayList<String>() : new ArrayList<String>(Arrays.asList(args));
  		if (argsList.contains("-d")) { // Restore default properties
  			System.out.println("**Restoring default properties**");
  			properties.restoreDefaults();
  		} else { // Run SourceRadio
  			if (!argsList.contains("-g")) {
  				argsList.add("-g");
  				String defaultGame = properties.get("default game");
  				if (defaultGame == null || defaultGame.isEmpty()) {
  					Scanner scanner = new Scanner(System.in);
  					Game game = null;
  					while (game == null) {
  						System.out.println("Enter the game you're playing. (Options: tf2, csgo, l4d2)");
  						if (scanner.hasNext()) {
  							game = Game.getGame(scanner.nextLine());
  						}
  					}
  					argsList.add(game.getFriendlyName());
  					scanner.close();
  				} else {
  					argsList.add(defaultGame);
  				}
  			}
  
  			Game game = Game.getGame(argsList.get(argsList.indexOf("-g") + 1));
  			int argIndex = -1;
  			if ((argIndex = argsList.indexOf("-f")) != -1) { // Set the game's directory
  				game.setPath(argsList.get(argIndex + 1));
  			}
  
  			try {
  				Playlist playlist = Playlist.getInstance(game);
  				if ((argIndex = argsList.indexOf("-l")) != -1) { // Specify a different log path for debugging
  					playlist.logReader.setDebugLogPath(argsList.get(argIndex + 1));
  					System.out.println("DEBUG MODE");
  				} else {
  					if (!new File(game.getPath()).exists()) {
  						throw new FileNotFoundException(
  								"Error: Could not find a " + game.getFriendlyName() + " installation.");
  					}
  				}
  				System.out.println("Game: " + game.getFriendlyName());
  				System.out.println("Checking for SourceRadio updates...");
  				try {
  					String latestVersion = getLatestVersion("joelamos", "SourceRadio");
  					if (version.compareTo(latestVersion) < 0) {
  						System.out.println("Update found: " + "SourceRadio v" + latestVersion);
  					}
  				} catch (Exception e) {
  					System.err.println("Error while checking for updates.");
  				}
  				System.out.println("Updating youtube-dl...");
  				Song.downloadYoutubedl("libraries");
  				System.out.println("Listening for commands...");
  				playlist.start();
  			} catch (IOException e) {
  				System.out.println(e.getMessage());
  			}
  		}
  	}
  
  	/**
  	 * This method is to be used to obtain a {@link Playlist} instance.
  	 * 
  	 * @return a <code>Playlist</code> instance
  	 * @throws IOException
  	 */
  	public synchronized static Playlist getInstance(Game game) throws IOException {
  		Playlist playlist = null;
  		if (instance == null) {
  			playlist = (instance = new Playlist(game));
  		} else {
  			if (instance.game == game) {
  				playlist = instance;
  			}
  		}
  		return playlist;
  	}
  
  	/**
  	 * Constructs a {@link Playlist}.
  	 * 
  	 * @throws IOException
  	 * 
  	 * @see #getInstance
  	 */
  	private Playlist(Game game) throws IOException {
  		super();
  		this.game = game;
  		initialize();
  	}
  
  	/**
  	 * Starts the database, initializes instance variables, and writes key binds.
  	 * 
  	 * @throws IOException
  	 */
  	private void initialize() throws IOException {
  		Game.setCurrentGame(game);
  		logReader = LogReader.getInstance();
  		database = DatabaseManager.getInstance();
  		database.start();
  		scriptWriter = new ScriptWriter();
  		scriptWriter.writeScripts();
  		owner = properties.getOwner();
  		admins = properties.getAdmins();
  		bannedPlayers = properties.getBannedPlayers();
  		blockedSongs = properties.getBlockedSongs();
  		durationLimit = Integer.parseInt(properties.get("duration limit"));
  		playerSongLimit = Integer.parseInt(properties.get("player song limit"));
  		queueLimit = Integer.parseInt(properties.get("queue limit"));
  		commandVocalization = properties.get("enable command vocalization").equalsIgnoreCase("true");
  		shareCommandVocals = properties.get("share command vocalizations").equalsIgnoreCase("true");
  		volumeIncrement = Integer.parseInt(properties.get("volume increment"));
  	}
  
  	/**
  	 * This method starts the playlist by beginning to listen for new lines in <code>console.log</code>, a real-time
  	 * dump of the console log. New lines get sent to {@link #handleNewInput} for parsing.
  	 */
  	public void start() {
  		logReader.start(this);
  	}
  
  	public void setGame(Game game) {
  		this.game = game;
  	}
  
  	/**
  	 * Handles a new line from the console log. More specifically, this method looks for and executes commands being
  	 * issued via the ingame chat.
  	 * 
  	 * @param input
  	 *            - a line of input from <code>console.log</code>
  	 */
  	void handleNewInput(Input input) {
  		final Player issuer = input.getAuthor();
  		String commandText = input.getCommand();
  		String argument = getCommandArgument(commandText);
  		Command command = Command.triggeredBy(commandText, isAdmin(issuer), isBanned(issuer));
  		boolean toBeWritten = changesToBeWritten(commandText) && command.propertyUpdate;
  		boolean success = false;
  		if (command != null) {
  			if (command != Command.INCREASE_VOLUME && command != Command.DECREASE_VOLUME) {
  				System.out.println(issuer.getUsername() + ": " + command.syntax + (toBeWritten ? "-w" : "")
  						+ (argument == null ? "" : " " + argument));
  			}
  			switch (command) {
  			case REQUEST_SONG:
  				final String query = normalizeQuery(argument);
  				timeOfLastRequest = timeOfCurrentRequest;
  				timeOfCurrentRequest = Instant.now();
  				Song existing = findSongIfExists(query);
  				try {
  					final Song song = existing == null ? Song.createSong(query, issuer, true) : existing.copy(issuer);
  					if (existing == null && !query.isEmpty()) {
  						database.addSongRequest(query, song.getYoutubeId(), issuer.getSteamId3(), isAdmin(issuer),
  								isOwner(issuer), song.usedCachedQuery());
  						final int songRequestId = database.getLatestRequestId();
  						song.setRequestId(songRequestId);
  						if (issuer.getSteamId3() == null) {
  							addPlayerDiscoveryJob(issuer, new Runnable() {
  
  								@Override
  								public void run() {
  									database.updateCell("SONG_REQUEST", "PlayerID", issuer.getSteamId3(), Types.VARCHAR,
  											"ID", String.valueOf(songRequestId), Types.INTEGER);
  								}
  							});
  						}
  					}
  					songRequests.add(0, song);
  					if (song != null && song.getYoutubeId() != null && !query.trim().isEmpty()
  							&& !blockedSongs.contains(song.getYoutubeId())) {
  						handleNewSong(song);
  					}
  				} catch (Exception e) {
  					handleSongCreationException(e);
  				}
  				printSongQueue();
  				break;
  			case SKIP:
  				Song temp = currentSong;
  				if (skipCurrentSong()) {
  					database.updateCell("SONG_REQUEST", "Skipped", "true", Types.BOOLEAN, "ID",
  							String.valueOf(temp.getRequestId()), Types.INTEGER);
  					if (commandVocalization) {
  						Command.SKIP.playAudio(true, shareCommandVocals);
  					}
  				}
  				printSongQueue();
  				break;
  			case EXTEND:
  				boolean isACurrentSong = currentSong != null;
  				if (isACurrentSong) {
  					currentSong.extend();
  					database.updateCell("SONG_REQUEST", "Extended", "true", Types.BOOLEAN, "ID",
  							String.valueOf(currentSong.getRequestId()), Types.INTEGER);
  				}
  				if (commandVocalization) {
  					Command.EXTEND.playAudio(isACurrentSong, shareCommandVocals);
  				}
  				break;
  			case CLEAR:
  				clearSongs();
  				if (commandVocalization) {
  					Command.CLEAR.playAudio(true, shareCommandVocals);
  				}
  				break;
  			case ALLTALK:
  				boolean on = argument.trim().equalsIgnoreCase("on");
  				boolean off = argument.trim().equalsIgnoreCase("off");
  				if (on || off) {
  					try {
  						scriptWriter.setAlltalk(on);
  						scriptWriter.writeScripts();
  						scriptWriter.updateCurrentSongScript(currentSong);
  						if (toBeWritten) {
  							properties.writeProperty("alltalk", on + "");
  						}
  					} catch (FileNotFoundException e) {
  						e.printStackTrace();
  					}
  				} else {
  					if (commandVocalization) {
  						String vocalsConfused = "resources/audio/vocals confused.wav";
  						AudioUtilities.playAudio(vocalsConfused, shareCommandVocals, null);
  					}
  				}
  				break;
  			case ADD_ADMIN:
  				addAdmin(Player.getPlayerFromUsername(argument, false), toBeWritten);
  				if (commandVocalization) {
  					Command.ADD_ADMIN.playAudio(true, shareCommandVocals);
  				}
  				printAdmins(false);
  				break;
  			case REMOVE_ADMIN:
  				success = removeAdmin(Player.getPlayerFromUsername(argument, false), issuer, toBeWritten);
  				if (commandVocalization) {
  					Command.REMOVE_ADMIN.playAudio(success, shareCommandVocals);
  				}
  				printAdmins(false);
  				break;
  			case SET_DURATION_LIMIT:
  				try {
  					int newLimit = Integer.parseInt(argument);
  					boolean changeLimit = newLimit >= 10;
  					if (changeLimit) {
  						durationLimit = newLimit;
  						if (toBeWritten) {
  							properties.writeProperty("duration limit", String.valueOf(newLimit));
  						}
  						success = true;
  					}
  				} catch (NumberFormatException e) {
  				}
  				if (commandVocalization) {
  					Command.SET_DURATION_LIMIT.playAudio(success, shareCommandVocals);
  				}
  				break;
  			case SET_PLAYER_SONG_LIMIT:
  				try {
  					int newLimit = Integer.parseInt(argument);
  					boolean changeLimit = newLimit > 0;
  					if (changeLimit) {
  						playerSongLimit = newLimit;
  						if (toBeWritten) {
  							properties.writeProperty("player song limit", String.valueOf(newLimit));
  						}
  						success = true;
  					}
  				} catch (NumberFormatException e) {
  				}
  				if (commandVocalization) {
  					Command.SET_PLAYER_SONG_LIMIT.playAudio(success, shareCommandVocals);
  				}
  				break;
  			case SET_QUEUE_LIMIT:
  				try {
  					int newLimit = Integer.parseInt(argument);
  					boolean changeLimit = newLimit >= 0;
  					if (changeLimit) {
  						queueLimit = newLimit;
  						if (toBeWritten) {
  							properties.writeProperty("queue limit", String.valueOf(newLimit));
  						}
  						success = true;
  					}
  				} catch (NumberFormatException e) {
  				}
  				if (commandVocalization) {
  					Command.SET_QUEUE_LIMIT.playAudio(success, shareCommandVocals);
  				}
  				break;
  			case IGNORE_REQUEST:
  				if (ignoreRequest(argument) == null) {
  					System.out.println("Failed to ignore request.");
  				} else {
  					printSongQueue();
  				}
  				break;
  			case BAN_PLAYER:
  				success = banPlayer(Player.getPlayerFromUsername(argument, false), toBeWritten);
  				if (commandVocalization) {
  					Command.BAN_PLAYER.playAudio(success, shareCommandVocals);
  				}
  				printBannedPlayers();
  				break;
  			case UNBAN_PLAYER:
  				success = unbanPlayer(Player.getPlayerFromUsername(argument, false), toBeWritten);
  				if (commandVocalization) {
  					Command.UNBAN_PLAYER.playAudio(success, shareCommandVocals);
  				}
  				printBannedPlayers();
  				break;
  			case BLOCK_SONG:
  				String youtubeId = null;
  				try {
  					Song songToBlock = ignoreRequest(argument);
  					boolean songWasQueued = songToBlock != null;
  					if (!songWasQueued) {
  						songToBlock = Song.createSong(argument, null, true);
  					}
  					youtubeId = songToBlock.getYoutubeId();
  					if (youtubeId == null) {
  						System.out.println("Failed to block song.");
  					} else {
  						blockedSongs.add(youtubeId);
  						if (toBeWritten) {
  							properties.addBlockedSong(songToBlock);
  						}
  						System.out.println("Blocked song: " + songToBlock.getTitle());
  					}
  					if (songWasQueued) {
  						printSongQueue();
  					}
  				} catch (Exception e) {
  					handleSongCreationException(e);
  				}
  				if (commandVocalization) {
  					Command.BLOCK_SONG.playAudio(youtubeId != null, shareCommandVocals);
  				}
  				break;
  			case UNBLOCK_SONG:
  				success = false;
  				try {
  					if (argument != null && !argument.isEmpty()) {
  						Song songToUnblock = Song.createSong(argument, null, true);
  						if (songToUnblock.getYoutubeId() != null) {
  							success = blockedSongs.remove(songToUnblock.getYoutubeId());
  							if (toBeWritten) {
  								success = properties.removeBlockedSong(songToUnblock);
  							}
  							if (success) {
  								System.out.println("Unblocked song: " + songToUnblock.getTitle());
  							}
  						}
  					}
  					if (!success) {
  						System.out.println("Failed to unblock song.");
  					}
  				} catch (Exception e) {
  					handleSongCreationException(e);
  				}
  				if (commandVocalization) {
  					Command.UNBLOCK_SONG.playAudio(success, shareCommandVocals);
  				}
  				break;
  			case ENABLE_VOCALS:
  				boolean wasEnabled = commandVocalization;
  				on = argument.trim().equalsIgnoreCase("on");
  