import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Diese Klasse kapselt die Logik zur Berechnung der Anzahl an möglichen
 * Sperrmustern in Android.
 *
 * @author André, savest8
 * @version 1.0 (12.02.2017)
 */
public class Simulator {
	/**
	 * Mit dieser Methode kann man zu einer gegebenen Länge alle Kombinationen
	 * der Zahlen 1-9 ohne Duplikate bestimmen. Duplikate sind dabei doppelt
	 * auftretende Zahlen.
	 *
	 * @param length
	 *            Länge der Zahlenkombination.
	 * @return Liste, die alle Zahlenkombinationen der Länge length ohne
	 *         Duplikat enthält.
	 */
	private List<String> possible_codes(final int length) {
		// Container für die möglichen Codes.
		final List<String> codes = new ArrayList<>();
		// Hilfsvariable zur Ermittlung des Startwerts
		String tmp = "";
		// Startwert (abhängig von der eingegebenen Länge) ermitteln.
		for (int counter = 1; counter <= length; counter++) {
			tmp += counter;
		}
		try {
			int number = Integer.parseInt(tmp);
			// Solange die Zahl nicht (length+1)-stellig ist ...
			while (number < Math.pow(10, length)) {
				// String-Repräsentation der Kombination.
				final String string_number = Integer.toString(number);
				// ... wird geprüft, ob die Kombination Duplikate oder Nullen
				// enthält. Wenn sie weder Duplikate, noch Nullen enthält, dann
				// ...
				if (!has_duplicate(string_number) && !string_number.contains("0")) {
					// ... wird die Zahl der Ergebnisliste hinzugefügt.
					codes.add(string_number);
				}
				number++;
			}
		} catch (final NumberFormatException nfe) {
			System.err.println(
					"Beim Parsen des Strings ist ein Fehler in der Methode possible_codes(final int length) aufgetreten");
		}
		return codes;
	}

	/**
	 * Diese Methode prüft, ob in to_check doppelte Zeichen vorkommen.
	 *
	 * @param to_check
	 *            String, der auf Duplikate geprüft werden soll.
	 * @return "true", wenn Duplikate in to_check enthalten sind, "false" sonst.
	 */
	private boolean has_duplicate(final String to_check) {
		// HashSet für Duplikat-Test.
		final Set<Character> container = new HashSet<>();
		for (int index = 0; index < to_check.length(); index++) {
			// Wenn das hinzuzufügende Zeichen bereits enthalten ist (add
			// liefert "false"), dann gibt es (mindestens) ein Duplikat.
			if (!container.add(to_check.charAt(index))) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Methode, die to_check (Sperrmusterpfad) auf Validität prüft.
	 *
	 * @param to_check
	 *            String, der geprüft werden soll.
	 * @return "true", wenn ein ungültiger Sperrmusterpfad enthalten ist,
	 *         "false" sonst.
	 */
	private boolean is_invalid(final String to_check) {
		return to_check.contains("13")
				&& (to_check.indexOf("2") == -1 || (to_check.indexOf("2") > to_check.indexOf("13")))
				|| to_check.contains("31")
						&& (to_check.indexOf("2") == -1 || (to_check.indexOf("2") > to_check.indexOf("31")))
				|| to_check.contains("39")
						&& (to_check.indexOf("6") == -1 || (to_check.indexOf("6") > to_check.indexOf("39")))
				|| to_check.contains("28")
						&& (to_check.indexOf("5") == -1 || (to_check.indexOf("5") > to_check.indexOf("28")))
				|| to_check.contains("82")
						&& (to_check.indexOf("5") == -1 || (to_check.indexOf("5") > to_check.indexOf("82")))
				|| to_check.contains("46")
						&& (to_check.indexOf("5") == -1 || (to_check.indexOf("5") > to_check.indexOf("46")))
				|| to_check.contains("64")
						&& (to_check.indexOf("5") == -1 || (to_check.indexOf("5") > to_check.indexOf("64")))
				|| to_check.contains("93")
						&& (to_check.indexOf("6") == -1 || (to_check.indexOf("6") > to_check.indexOf("93")))
				|| to_check.contains("79")
						&& (to_check.indexOf("8") == -1 || (to_check.indexOf("8") > to_check.indexOf("79")))
				|| to_check.contains("97")
						&& (to_check.indexOf("8") == -1 || (to_check.indexOf("8") > to_check.indexOf("97")))
				|| to_check.contains("71")
						&& (to_check.indexOf("4") == -1 || (to_check.indexOf("4") > to_check.indexOf("71")))
				|| to_check.contains("17")
						&& (to_check.indexOf("4") == -1 || (to_check.indexOf("4") > to_check.indexOf("17")))
				|| to_check.contains("37")
						&& (to_check.indexOf("5") == -1 || (to_check.indexOf("5") > to_check.indexOf("37")))
				|| to_check.contains("73")
						&& (to_check.indexOf("5") == -1 || (to_check.indexOf("5") > to_check.indexOf("73")))
				|| to_check.contains("19")
						&& (to_check.indexOf("5") == -1 || (to_check.indexOf("5") > to_check.indexOf("19")))
				|| to_check.contains("91")
						&& (to_check.indexOf("5") == -1 || (to_check.indexOf("5") > to_check.indexOf("91")));
	}

	/**
	 * Methode, mit der alle gültigen Sperrcodes berechnet werden können.
	 *
	 * @param combinations
	 *            Kombinationen, die auf ihre Gültigkeit geprüft werden sollen.
	 * @return Liste, die alle gültigen Kombinationen enthält.
	 */
	private List<String> combinations(final List<String> combinations) {
		// Container für die validen Codes.
		final List<String> codes = new ArrayList<>();
		// Jede der ermittelten Kombinationen wird auf Validität gemäß des
		// vorgegebenen Regelwerks getestet.
		for (String combination : combinations) {
			// Wenn die Kombination gültig ist, dann wird der Counter
			// inkrementiert.
			if (!is_invalid(combination)) {
				codes.add(combination);
			}
		}
		return codes;
	}

	/**
	 * Methode, mit der die Anzahl an Kombinationsmöglichkeiten für einen
	 * bestimmten Musterlängenbereich berechnet werden kann (überlädt
	 * simulate(simulate(final int length))).
	 *
	 * @param lower_bound
	 *            Untere Grenze ("Mindestlänge von ...").
	 * @param upper_bound
	 *            Obere Grenze ("Bis zur Länge ...").
	 * @return num_combinations Anzahl der möglichen Android-Sperrmuster.
	 */
	private int simulate(final int lower_bound, final int upper_bound) {
		// Variable, in der die Anzahl an gültigen Kombinationen gespeichert
		// wird.
		int num_combinations = 0;
		for (int counter = lower_bound; counter <= upper_bound; counter++) {
			// Für jeden Wert zwischen lower_bound und upper_bound wird die
			// Anzahl valider Kombinationen berechnet.
			num_combinations += combinations(possible_codes(counter)).size();
		}
		return num_combinations;
	}

	/**
	 * Methode, mit der die Anzahl an Kombinationsmöglichkeiten für eine
	 * bestimmte Musterlänge berechnet werden kann (überlädt simulate(final int
	 * lower_bound, final int upper_bound)).
	 *
	 * @param length
	 *            Länge des Sperrmusters.
	 * @return Anzahl der möglichen Sperrmuster der Länge length.
	 */
	private int simulate(final int length) {
		return combinations(possible_codes(length)).size();
	}

	/**
	 * Main-Methode, in der die Anwendung getestet werden kann.
	 *
	 * @param args
	 *            Argumente, die beim Programmaufruf über die Konsole mitgegeben
	 *            werden können.
	 */
	public static void main(final String... args) {
		// Simulator-Instanz erzeugen.
		final Simulator simulator = new Simulator();
		// Schalterblock, der die von der Anzahl an Input-Parametern abhängige
		// Programmlogik kapselt.
		switch (args.length) {
		case 0:
			System.out.println("Anzahl an validen Sperrmustern der Länge 4: " + simulator.simulate(4));
			System.out.println("Anzahl an validen Sperrmustern der Länge 5: " + simulator.simulate(5));
			System.out.println("Anzahl an validen Sperrmustern der Länge 6: " + simulator.simulate(6));
			break;
		case 1:
			try {
				final int length = Integer.parseInt(args[0]);
				// Prüfen, ob die vom Nutzer gewünschte Zahl außerhalb des
				// Intervalls [1,9] liegt.
				if (length < 1 || length > 9) {
					throw new IllegalArgumentException();
				}
				System.out.printf("Anzahl an validen Sperrmustern der Länge %d: %d", length,
						simulator.simulate(length));
			} catch (final NumberFormatException nfe) {
				System.err.println("<" + args[0] + "> ist kein Integer!");
			} catch (final IllegalArgumentException iae) {
				System.err.println("Die Eingabe <" + args[0] + "> ist ungültig, da nur Zahlen von 1-9 erlaubt sind!");
			}
			break;
		case 2:
			try {
				final int lower_bound = Integer.parseInt(args[0]);
				final int upper_bound = Integer.parseInt(args[1]);
				// Prüfen, ob die vom Nutzer gewünschten Grenzen außerhalb des
				// Intervalls [1,9] liegen und ob upper_bound kleiner als
				// lower_bound ist.
				if (lower_bound < 1 || upper_bound < 1 || lower_bound > 9 || upper_bound > 9
						|| upper_bound < lower_bound) {
					throw new IllegalArgumentException();
				}
				System.out.printf("Anzahl an validen Sperrmustern der Länge %d bis %d: %d", lower_bound, upper_bound,
						simulator.simulate(lower_bound, upper_bound));
			} catch (final NumberFormatException nfe) {
				System.err
						.println("Mindestens einer der Parameter <" + args[0] + "," + args[1] + "> ist kein Integer!");
			} catch (final IllegalArgumentException iae) {
				System.err.println("Die Eingabe <" + args[0] + "," + args[1]
						+ "> ist ungültig, da nur Zahlen von 1-9 erlaubt sind und upper_bound größer als lower_bound sein muss!");
			}
			break;
		default:
			System.err.println("Es sind maximal 2 Parameter beim Programmaufruf mitzugeben!");
		}
	}
}