content: /*
   *  ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
   *  Copyright (C) 2012 Kristian S. Stangeland
   *
   *  This program is free software; you can redistribute it and/or modify it under the terms of the
   *  GNU General Public License as published by the Free Software Foundation; either version 2 of
   *  the License, or (at your option) any later version.
   *
   *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
   *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   *  See the GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License along with this program;
   *  if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   *  02111-1307 USA
   */
  
  package com.comphenix.protocol.utility;
  
  import java.io.Serializable;
  import java.text.SimpleDateFormat;
  import java.util.Locale;
  import java.util.Objects;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import com.comphenix.protocol.ProtocolLibrary;
  import com.google.common.collect.ComparisonChain;
  import com.google.common.collect.Ordering;
  import org.bukkit.Bukkit;
  import org.bukkit.Server;
  
  /**
   * Determine the current Minecraft version.
   *
   * @author Kristian
   */
  public final class MinecraftVersion implements Comparable<MinecraftVersion>, Serializable {
  
  	/**
  	 * Version 1.19.4 - the rest of the feature preview
  	 */
  	public static final MinecraftVersion FEATURE_PREVIEW_2 = new MinecraftVersion("1.19.4");
  
  	/**
  	 * Version 1.19.3 - introducing feature preview
  	 */
  	public static final MinecraftVersion FEATURE_PREVIEW_UPDATE = new MinecraftVersion("1.19.3");
  	/**
  	 * Version 1.19 - the wild update
  	 */
  	public static final MinecraftVersion WILD_UPDATE = new MinecraftVersion("1.19");
  	/**
  	 * Version 1.18 - caves and cliffs part 2
  	 */
  	public static final MinecraftVersion CAVES_CLIFFS_2 = new MinecraftVersion("1.18");
  	/**
  	 * Version 1.17 - caves and cliffs part 1
  	 */
  	public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17");
  	/**
  	 * Version 1.16.4
  	 */
  	public static final MinecraftVersion NETHER_UPDATE_4 = new MinecraftVersion("1.16.4");
  	/**
  	 * Version 1.16.2 - breaking change to the nether update
  	 */
  	public static final MinecraftVersion NETHER_UPDATE_2 = new MinecraftVersion("1.16.2");
  	/**
  	 * Version 1.16.0 - the nether update
  	 */
  	public static final MinecraftVersion NETHER_UPDATE = new MinecraftVersion("1.16");
  	/**
  	 * Version 1.15 - the bee update
  	 */
  	public static final MinecraftVersion BEE_UPDATE = new MinecraftVersion("1.15");
  	/**
  	 * Version 1.14 - village and pillage update.
  	 */
  	public static final MinecraftVersion VILLAGE_UPDATE = new MinecraftVersion("1.14");
  	/**
  	 * Version 1.13 - update aquatic.
  	 */
  	public static final MinecraftVersion AQUATIC_UPDATE = new MinecraftVersion("1.13");
  	/**
  	 * Version 1.12 - the world of color update.
  	 */
  	public static final MinecraftVersion COLOR_UPDATE = new MinecraftVersion("1.12");
  	/**
  	 * Version 1.11 - the exploration update.
  	 */
  	public static final MinecraftVersion EXPLORATION_UPDATE = new MinecraftVersion("1.11");
  	/**
  	 * Version 1.10 - the frostburn update.
  	 */
  	public static final MinecraftVersion FROSTBURN_UPDATE = new MinecraftVersion("1.10");
  	/**
  	 * Version 1.9 - the combat update.
  	 */
  	public static final MinecraftVersion COMBAT_UPDATE = new MinecraftVersion("1.9");
  	/**
  	 * Version 1.8 - the "bountiful" update.
  	 */
  	public static final MinecraftVersion BOUNTIFUL_UPDATE = new MinecraftVersion("1.8");
  	/**
  	 * Version 1.7.8 - the update that changed the skin format (and distribution - R.I.P. player disguise)
  	 */
  	public static final MinecraftVersion SKIN_UPDATE = new MinecraftVersion("1.7.8");
  	/**
  	 * Version 1.7.2 - the update that changed the world.
  	 */
  	public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2");
  	/**
  	 * Version 1.6.1 - the horse update.
  	 */
  	public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1");
  	/**
  	 * Version 1.5.0 - the redstone update.
  	 */
  	public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0");
  	/**
  	 * Version 1.4.2 - the scary update (Wither Boss).
  	 */
  	public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2");
  
  	/**
  	 * The latest release version of minecraft.
  	 */
  	public static final MinecraftVersion LATEST = FEATURE_PREVIEW_2;
  
  	// used when serializing
  	private static final long serialVersionUID = -8695133558996459770L;
  
  	/**
  	 * Regular expression used to parse version strings.
  	 */
  	private static final Pattern VERSION_PATTERN = Pattern.compile(".*\\(.*MC.\\s*([a-zA-z0-9\\-.]+).*");
  
  	/**
  	 * The current version of minecraft, lazy initialized by MinecraftVersion.currentVersion()
  	 */
  	private static MinecraftVersion currentVersion;
  
  	private final int major;
  	private final int minor;
  	private final int build;
  	// The development stage
  	private final String development;
  
  	// Snapshot?
  	private final SnapshotVersion snapshot;
  	private volatile Boolean atCurrentOrAbove;
  
  	/**
  	 * Determine the current Minecraft version.
  	 *
  	 * @param server - the Bukkit server that will be used to examine the MC version.
  	 */
  	public MinecraftVersion(Server server) {
  		this(extractVersion(server.getVersion()));
  	}
  
  	/**
  	 * Construct a version object from the format major.minor.build, or the snapshot format.
  	 *
  	 * @param versionOnly - the version in text form.
  	 */
  	public MinecraftVersion(String versionOnly) {
  		this(versionOnly, true);
  	}
  
  	/**
  	 * Construct a version format from the standard release version or the snapshot verison.
  	 *
  	 * @param versionOnly   - the version.
  	 * @param parseSnapshot - TRUE to parse the snapshot, FALSE otherwise.
  	 */
  	private MinecraftVersion(String versionOnly, boolean parseSnapshot) {
  		String[] section = versionOnly.split("-");
  		SnapshotVersion snapshot = null;
  		int[] numbers = new int[3];
  
  		try {
  			numbers = this.parseVersion(section[0]);
  		} catch (NumberFormatException cause) {
  			// Skip snapshot parsing
  			if (!parseSnapshot) {
  				throw cause;
  			}
  
  			try {
  				// Determine if the snapshot is newer than the current release version
  				snapshot = new SnapshotVersion(section[0]);
  				SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
  
  				MinecraftVersion latest = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION, false);
  				boolean newer = snapshot.getSnapshotDate().compareTo(
  						format.parse(ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE)) > 0;
  
  				numbers[0] = latest.getMajor();
  				numbers[1] = latest.getMinor() + (newer ? 1 : -1);
  			} catch (Exception e) {
  				throw new IllegalStateException("Cannot parse " + section[0], e);
  			}
  		}
  
  		this.major = numbers[0];
  		this.minor = numbers[1];
  		this.build = numbers[2];
  		this.development = section.length > 1 ? section[1] : (snapshot != null ? "snapshot" : null);
  		this.snapshot = snapshot;
  	}
  
  	/**
  	 * Construct a version object directly.
  	 *
  	 * @param major - major version number.
  	 * @param minor - minor version number.
  	 * @param build - build version number.
  	 */
  	public MinecraftVersion(int major, int minor, int build) {
  		this(major, minor, build, null);
  	}
  
  	/**
  	 * Construct a version object directly.
  	 *
  	 * @param major       - major version number.
  	 * @param minor       - minor version number.
  	 * @param build       - build version number.
  	 * @param development - development stage.
  	 */
  	public MinecraftVersion(int major, int minor, int build, String development) {
  		this.major = major;
  		this.minor = minor;
  		this.build = build;
  		this.development = development;
  		this.snapshot = null;
  	}
  
  	/**
  	 * Extract the Minecraft version from CraftBukkit itself.
  	 *
  	 * @param text - the server version in text form.
  	 * @return The underlying MC version.
  	 * @throws IllegalStateException If we could not parse the version string.
  	 */
  	public static String extractVersion(String text) {
  		Matcher version = VERSION_PATTERN.matcher(text);
  
  		if (version.matches() && version.group(1) != null) {
  			return version.group(1);
  		} else {
  			throw new IllegalStateException("Cannot parse version String '" + text + "'");
  		}
  	}
  
  	/**
  	 * Parse the given server version into a Minecraft version.
  	 *
  	 * @param serverVersion - the server version.
  	 * @return The resulting Minecraft version.
  	 */
  	public static MinecraftVersion fromServerVersion(String serverVersion) {
  		return new MinecraftVersion(extractVersion(serverVersion));
  	}
  
  	public static MinecraftVersion getCurrentVersion() {
  		if (currentVersion == null) {
  			currentVersion = fromServerVersion(Bukkit.getVersion());
  		}
  
  		return currentVersion;
  	}
  
  	public static void setCurrentVersion(MinecraftVersion version) {
  		currentVersion = version;
  	}
  
  	private static boolean atOrAbove(MinecraftVersion version) {
  		return getCurrentVersion().isAtLeast(version);
  	}
  
  	private int[] parseVersion(String version) {
  		String[] elements = version.split("\\.");
  		int[] numbers = new int[3];
  
  		// Make sure it's even a valid version
  		if (elements.length < 1) {
  			throw new IllegalStateException("Corrupt MC version: " + version);
  		}
  
  		// The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively.
  		for (int i = 0; i < Math.min(numbers.length, elements.length); i++) {
  			numbers[i] = Integer.parseInt(elements[i].trim());
  		}
  		return numbers;
  	}
  
  	/**
  	 * Major version number
  	 *
  	 * @return Current major version number.
  	 */
  	public int getMajor() {
  		return this.major;
  	}
  
  	/**
  	 * Minor version number
  	 *
  	 * @return Current minor version number.
  	 */
  	public int getMinor() {
  		return this.minor;
  	}
  
  	/**
  	 * Build version number
  	 *
  	 * @return Current build version number.
  	 */
  	public int getBuild() {
  		return this.build;
  	}
  
  	/**
  	 * Retrieve the development stage.
  	 *
  	 * @return Development stage, or NULL if this is a release.
  	 */
  	public String getDevelopmentStage() {
  		return this.development;
  	}
  
  	/**
  	 * Retrieve the snapshot version, or NULL if this is a release.
  	 *
  	 * @return The snapshot version.
  	 */
  	public SnapshotVersion getSnapshot() {
  		return this.snapshot;
  	}
  
  	/**
  	 * Determine if this version is a snapshot.
  	 *
  	 * @return The snapshot version.
  	 */
  	public boolean isSnapshot() {
  		return this.snapshot != null;
  	}
  
  	/**
  	 * Checks if this version is at or above the current version the server is running.
  	 *
  	 * @return true if this version is equal or newer than the server version, false otherwise.
  	 */
  	public boolean atOrAbove() {
  		if (this.atCurrentOrAbove == null) {
  			this.atCurrentOrAbove = atOrAbove(this);
  		}
  
  		return this.atCurrentOrAbove;
  	}
  
  	/**
  	 * Retrieve the version String (major.minor.build) only.
  	 *
  	 * @return A normal version string.
  	 */
  	public String getVersion() {
  		if (this.getDevelopmentStage() == null) {
  			return String.format("%s.%s.%s", this.getMajor(), this.getMinor(), this.getBuild());
  		} else {
  			return String.format("%s.%s.%s-%s%s", this.getMajor(), this.getMinor(), this.getBuild(),
  					this.getDevelopmentStage(), this.isSnapshot() ? this.snapshot : "");
  		}
  	}
  
  	@Override
  	public int compareTo(MinecraftVersion o) {
  		if (o == null) {
  			return 1;
  		}
  
  		return ComparisonChain.start()
  				.compare(this.getMajor(), o.getMajor())
  				.compare(this.getMinor(), o.getMinor())
  				.compare(this.getBuild(), o.getBuild())
  				.compare(this.getDevelopmentStage(), o.getDevelopmentStage(), Ordering.natural().nullsLast())
  				.compare(this.getSnapshot(), o.getSnapshot(), Ordering.natural().nullsFirst())
  				.result();
  	}
  
  	public boolean isAtLeast(MinecraftVersion other) {
  		if (other == null) {
  			return false;
  		}
  
  		return this.compareTo(other) >= 0;
  	}
  
  	@Override
  	public boolean equals(Object obj) {
  		if (obj == null) {
  			return false;
  		}
  		if (obj == this) {
  			return true;
  		}
  
  		if (obj instanceof MinecraftVersion) {
  			MinecraftVersion other = (MinecraftVersion) obj;
  
  			return this.getMajor() == other.getMajor() &&
  					this.getMinor() == other.getMinor() &&
  					this.getBuild() == other.getBuild() &&
  					Objects.equals(this.getDevelopmentStage(), other.getDevelopmentStage());
  		}
  
  		return false;
  	}
  
  	@Override
  	public int hashCode() {
  		return Objects.hash(this.getMajor(), this.getMinor(), this.getBuild());
  	}
  
  	@Override
  	public String toString() {
  		// Convert to a String that we can parse back again
  		return String.format("(MC: %s)", this.getVersion());
  	}
  }
  