package common.init;

import common.rng.Random;

/**
 * This class is released under the GNU general public license (GPL)
 * <p>
 * <h1>Description:</h1> This class generates random names from syllables, and provides
 * programmer a simple way to set a group of rules for the generator to avoid
 * unpronounceable and bizarre names.
 * <p>
 * <h1>Syllable table requirements/format:</h1>
 * 1) All syllables must be added using {@link #pre(NameRegistry, String...)}, 
 *    {@link #mid(NameRegistry, String...)} and {@link #sur(NameRegistry, String...)} 
 *    in {@link #register()}. It is called from {@link game.init.Registry Registry}.<p>
 * 2) To add new generator types, add a constant to {@link NameRegistry} <p>
 * 3) A syllable should not start or end with whitespace, as this character is ignored in 
 *    vocal/consonant checks, but one or more spaces can be contained within the string. <p>
 * 4) '+' and '-' as the first and last characters are used to set rules, and using them in 
 *    as normal characters in these positions will result in undesired behaviour.
 * <p>
 * <h1>Syllable classification:</h1> A name is usually composed from 3 different classes of
 * syllables, which are prefix, middle part and suffix. To declare a syllable
 * as a prefix in the table, use {@link #pre(NameRegistry, String...) pre()}. 
 * To declare a syllable as a suffix in the table, use {@link #sur(NameRegistry, String...) sur()}. 
 * Everything else has to be be added as a middle part using {@link #mid(NameRegistry, String...) mid()}.
 * <p>
 * <h1>Number of syllables:</h1> Names may have any positive number of syllables. In case
 * of 2 syllables, the name will be composed from a prefix and a suffix. In case of 1
 * syllable, the name will be chosen from amongst the prefixes. In case of 3 and
 * more syllables, the name will begin with a prefix, is extended with middle parts and
 * ended with a suffix.
 * <p>
 * <h1>Assigning rules:</h1> I included a way to set 4 kinds of rules for every syllable.
 * To add rules to the syllables, write them right before and right after the syllable. 
 * (example: "-aad+").
 * <p>
 * 1) ...+ means that the next syllable must definitely start with a vocal. <p>
 * 2) ...- means that the next syllable must definitely start with a consonant. <p>
 * 3) +... means that this syllable can only be appended to another syllable that 
 *    ends with a vocal. <p>
 * 4) -... means that this syllable can only be appended to another syllable that 
 *    ends with a consonant. <p>
 * So, the example "-aad+" means that "aad" can only be after a consonant and 
 * the next syllable must start with a vocal. Beware of creating logical mistakes, 
 * like providing only syllables ending with consonants, but expecting only 
 * vocals, which will be detected and an empty string will be returned.
 * <p>
 * <h1>Generating names:</h1> To generate a name, use {@link #generate(Random, int)}. 
 * Call this method on the {@link NameRegistry} enum of the name type to generate. 
 * The first parameter is a extended PRNG {@link Random} object to use to select 
 * random syllables. The second parameter is the number of syllables to generate, 
 * as described above.
 * <p>
 *
 * @author Joonas Vali, August 2009.
 * @author Sen, December 2023.
 */
public enum NameRegistry {
	FANTASY,
	ELVEN,
	GOBLIN,
	ROMAN;
	
	private static enum CharType {
		NONE,
		VOWEL,
		CONSONANT
	}
	
	private static class Syllable {
		private final String text;
		private final boolean reqVowel;
		private final boolean reqConsonant;
		private final boolean noVowel;
		private final boolean noConsonant;
		private final boolean vowelFirst;
		private final boolean consonantFirst;
		private final boolean vowelLast;
		private final boolean consonantLast;
		
		private Syllable(String text, boolean captitalize) {
			this(text.substring(text.startsWith("+") || text.startsWith("-") ? 1 : 0, text.length() - 
					(text.endsWith("+") || text.endsWith("-") ? 1 : 0)), captitalize, 
					text.endsWith("+"), text.endsWith("-"), text.startsWith("-"), text.startsWith("+"));
		}
		
		private Syllable(String text, boolean captitalize, boolean reqVowel, boolean reqConsonant, boolean noVowel, boolean noConsonant) {
			this.text = captitalize ? text.substring(0, 1).toUpperCase().concat(text.substring(1)) : text.toLowerCase();
			text = text.toLowerCase();
			this.reqVowel = reqVowel;
			this.reqConsonant = reqConsonant;
			this.noVowel = noVowel;
			this.noConsonant = noConsonant;
			this.vowelFirst = VOWELS.indexOf(text.charAt(0)) != -1;
			this.consonantFirst = CONSONANTS.indexOf(text.charAt(0)) != -1;
			this.vowelLast = VOWELS.indexOf(text.charAt(text.length() - 1)) != -1;
			this.consonantLast = CONSONANTS.indexOf(text.charAt(text.length() - 1)) != -1;
		}
	}
	
	private static class TokenList extends java.util.ArrayList<Syllable> {
		private boolean hasFirstVowel;
		private boolean hasFirstConsonant;
		private boolean hasConsonants;
		private boolean hasVowels;
		
		public boolean add(Syllable syl) {
			if(super.add(syl)) {
				this.hasFirstVowel |= syl.vowelFirst;
				this.hasFirstConsonant |= syl.consonantFirst;
				this.hasConsonants |= syl.noVowel || !syl.noConsonant;
				this.hasVowels |= syl.noConsonant || !syl.noVowel;
				return true;
			}
			return false;
		}
	}
	
	private static final String VOWELS = "aeiouyäöü";
	private static final String CONSONANTS = "bcdfghjklmnpqrstvwxyß";

	private final TokenList pre = new TokenList();
	private final TokenList mid = new TokenList();
	private final TokenList sur = new TokenList();

	private static void pre(NameRegistry type, String ... syls) {
		TokenList list = type.pre;
		for(String syl : syls)
			list.add(new Syllable(syl, true));
	}

	private static void mid(NameRegistry type, String ... syls) {
		TokenList list = type.mid;
		for(String syl : syls)
			list.add(new Syllable(syl, false));
	}

	private static void sur(NameRegistry type, String ... syls) {
		TokenList list = type.sur;
		for(String syl : syls)
			list.add(new Syllable(syl, false));
	}
	
	static void register() {
		pre(FANTASY, "a-", "ab", "ac", "ad", "af", "ak", "am", "an", "ap", "ar", "as", "at", "aw", "az", "ael", "ael", "ao", 
				"aer", "ash", "arsh+", "ath", "arg+", "arv+", "art+", "arn+", "ag", "ang+", "anthr+", "ba-", "be-", "bi", "bo-", "bu-", 
				"beo-", "bra-", "bre-", "bei", "bro-", "bru", "breo-", "brao-", "da-", "de-", "di", "do-", "du-", "dae-", "dai", "dao-", 
				"dra-", "dre-", "dri", "dro-", "dash", "desh", "dosh", "darr+", "dar", "derr+", "der", "dorr+", "dor", "durr+", "dur", 
				"dran", "dren", "dron", "drun", "drav", "drev", "drov", "dov", "duv", "darv+", "dorv+", "durv+", "eo-", "ei-", "ed", 
				"ga-", "go-", "gu-", "gra-", "gro-", "gru-", "garl", "gorl", "ha-", "he-", "hi-", "ho-", "hu-", "han", "hun", "hon", 
				"idr+", "igr+", "irn+", "ign+", "in", "ins", "intr+", "ka-", "ke-", "ki", "ko-", "ku-", "kov", "korv+", "kran", "kren", 
				"kron", "krun", "karn", "kern", "korn", "kurn", "lod+", "lodd+", "la-", "lad+", "ladd+", "lor", "lorn+", "llor", 
				"llorn+", "llorv+", "orv+", "orn+", "orgl+", "pa-", "pu-", "po-", "ra-", "re-", "ru-", "rab+", "rak", "rob+", "rok", 
				"rub+", "ruk", "stra-", "stre-", "sto-", "sar", "sor", "ser", "tu-", "tuk", "tuv+", "un", "unn+", "und", "ud", "uk", 
				"va", "vaa-", "ve-", "vo-", "vu-", "val", "vel", "vol", "vul", "vraal", "vral", "vrul", "vratt+", "vrott+", "vrod+", 
				"vrok+", "vork+", "vort+", "za-", "zaa-", "zar", "zon", "zoll+");
		
		mid(FANTASY, "-ab", "-ac", "-ad", "-af", "-ak", "-am", "-an", "-ap", "-ar", "-as", "-at", "-aw", "-az", "-ael", "-ael", 
				"-ao", "-aer", "ba", "be", "bi", "bo", "bu", "beo", "bra", "bre", "bei", "bro", "bru", "breo", "brao", "da", "de", "di", 
				"do", "du", "dae", "dai", "dao", "dra", "dre", "dri", "dro", "dash", "desh", "dosh", "darr", "dar", "derr", "der", 
				"dorr", "dor", "durr", "dur", "dran", "dren", "dron", "drun", "drav", "drev", "drov", "dov", "duv", "darv", "dorv", 
				"durv", "-eo-", "-ei", "-ed", "+ga-", "+go-", "+gu-", "+gra", "+gro", "+gru-", "+garl", "+gorl", "+ha-", "+he-", "+hi", 
				"+ho-", "+hu-", "+han", "+hun", "+hon", "-idr+", "-igr+", "-irn+", "-ign+", "in", "ins", "-intr", "+ka-", "+ke-", "+ki", 
				"+ko-", "+ku-", "+kov+", "+korv+", "+kran+", "+kren+", "+kron+", "+krun+", "+karn+", "+kern+", "+korn+", "+kurn+", 
				"+lod", "+lodd", "+la-", "+lad", "+ladd+", "+lor", "+lorn+", "-orv+", "-orn+", "-orgl+", "+pa-", "+pu-", "+po-", 
				"+ra-", "+re-", "+ru-", "+rab-", "+rak-", "+rob-", "+rok", "+rub-", "+ruk", "+stra-", "+stre-", "+sto-", "+sar", "+sor", 
				"+serr-", "+tu-", "+tuk", "+tuv-", "-un", "-unn+", "-und+", "-ud+", "-uk", "+va-", "+vaa-", "+ve-", "+vo-", "+vu-", 
				"+val", "+vel", "+vol", "+vul", "+vral+", "+vrul+"); // "ladd  -v +v", "lor  -v"
		
		sur(FANTASY, "-al", "-ol", "-ul", "-ath", "-oth", "-an", "-un", "-en", "-iel", "-iol", "-iul", "-at", "-ut", "+ll", 
				"+d", "+st", "+ss", "s");
		
		
		pre(ELVEN, "ael", "aer", "af", "ah", "am", "ama", "an", "ang+", "ansr+", "cael", "dae-", "dho", "eir", "fi", "fir", 
				"la", "seh", "sel", "ev", "fis", "hu", "ha", "gar", "gil", "ka", "kan", "ya", "za", "zy", "mara", "mai-", "lue-", "ny", 
				"she", "sum", "syl");
		
		mid(ELVEN, "-ae-", "-ael", "dar", "deth+", "+dre", "+drim", "dul", "-ean", "el", "emar", "hal", "-iat", "mah", "ten", 
				"+que-", "ria", "rail", "ther", "thus", "thi", "san");
		
		sur(ELVEN, "-ael", "dar", "deth", "dre", "drim", "dul", "-ean", "el", "emar", "nes", "nin", "oth", "hal", "iat", "mah", 
				"ten", "ther", "thus", "thi", "ran", "ath", "ess", "san", "yth", "las", "lian", "evar");
		
		
		pre(GOBLIN, "waa-", "boo-", "gar", "bar", "dar", "jar", "var", "kra", "gra", "dra", "zra", "gob", "dob", "rob", "fob", 
				"zob", "rag", "nag", "dag");
		
		mid(GOBLIN, "bra", "ga", "da", "do", "go", "ze", "sha", "naz", "zub", "zu", "na", "gor", "boo-");
		
		sur(GOBLIN, "byr", "gyr", "dyr", "vyr", "zyr", "-yr", "zog", "rog", "gog", "-og", "gul", "dul", "bul", "rul", "-ul", 
				"+rgh");
		
		
		pre(ROMAN, "a", "al", "au-", "an", "ba", "be", "bi", "br+", "da", "di", "do", "du", "e", "eu-", "fa");
		
		mid(ROMAN, "bi", "be", "bo", "bu", "nul+", "gu", "da", "-au-", "fri", "gus");
		
		sur(ROMAN, "tus", "lus", "lius", "nus", "es", "-ius", "cus", "tor", "cio", "tin");
	}

	public String generate(Random rand, int syls) {
		if((syls > 2 && this.mid.isEmpty()) || this.pre.isEmpty() || (syls > 1 && this.sur.isEmpty()) || syls < 1)
			return "";
		CharType expecting = CharType.NONE;
		Syllable syl = rand.pick(this.pre);
		CharType last = syl.vowelLast ? CharType.VOWEL : CharType.CONSONANT;
		if(syls > 2) {
			if(syl.reqVowel) {
				expecting = CharType.VOWEL;
				if(!this.mid.hasFirstVowel)
					return "";
			}
			if(syl.reqConsonant) {
				expecting = CharType.CONSONANT;
				if(!this.mid.hasFirstConsonant)
					return "";
			}
		}
		else {
			if(syl.reqVowel) {
				expecting = CharType.VOWEL;
				if(!this.sur.hasFirstVowel)
					return "";
			}
			if(syl.reqConsonant) {
				expecting = CharType.CONSONANT;
				if(!this.sur.hasFirstConsonant)
					return "";
			}
		}
		if(syl.vowelLast && !this.mid.hasVowels)
			return "";
		if(syl.consonantLast && !this.mid.hasConsonants)
			return "";
		StringBuilder name = new StringBuilder(syl.text);
		for(int i = 0; i < syls - 2; i++) {
			do {
				syl = rand.pick(this.mid);
			}
			while(expecting == CharType.VOWEL && !syl.vowelFirst || expecting == CharType.CONSONANT && !syl.consonantFirst
					|| last == CharType.VOWEL && syl.noVowel || last == CharType.CONSONANT && syl.noConsonant);
			expecting = CharType.NONE;
			if(syl.reqVowel) {
				expecting = CharType.VOWEL;
				if(i < syls - 3 && !this.mid.hasFirstVowel)
					return "";
				if(i == syls - 3 && !this.sur.hasFirstVowel)
					return "";
			}
			if(syl.reqConsonant) {
				expecting = CharType.CONSONANT;
				if(i < syls - 3 && !this.mid.hasFirstConsonant)
					return "";
				if(i == syls - 3 && !this.sur.hasFirstConsonant)
					return "";
			}
			if(syl.vowelLast && !this.mid.hasVowels && syls > 3)
				return "";
			if(syl.consonantLast && !this.mid.hasConsonants && syls > 3)
				return "";
			if(i == syls - 3) {
				if(syl.vowelLast && !this.sur.hasVowels)
					return "";
				if(syl.consonantLast && !this.sur.hasConsonants)
					return "";
			}
			last = syl.vowelLast ? CharType.VOWEL : CharType.CONSONANT;
			name.append(syl.text);
		}
		if(syls > 1) {
			do {
				syl = rand.pick(this.sur);
			}
			while(expecting == CharType.VOWEL && !syl.vowelFirst || expecting == CharType.CONSONANT && !syl.consonantFirst
					|| last == CharType.VOWEL && syl.noVowel || last == CharType.CONSONANT && syl.noConsonant);
			name.append(syl.text);
		}
		return name.toString();
	}
}
