/*
 * Copyright (c) 2013 L2jMobius
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.l2jmobius.gameserver.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Генератор ников для фейк-игроков. Порт nickgen.html / nickgen.py — логика 1:1.<br>
 * Ник состоит из 1-3 частей; русские слова рендерятся транслитом ("ZlojGnom") или
 * псевдокириллицей ("TpaBka", "IIeJIbMeHb"), английские — с шансом l33t-цифр ("Ki4iga").
 * Декорации: цифровой суффикс ("OldRogue38"), обёртка "xX...Xx", lowercase, капс-хвост ("HolodOK").
 * Результат всегда соответствует [A-Za-z0-9]{3,16} (лимит клиента Interlude).
 */
public class FakePlayerNameGenerator
{
	private static final String[] RU_ADJ =
	{
		"злой", "добрый", "старый", "дикий", "мелкий", "жирный", "хитрый", "бешеный", "мамкин", "просто",
		"лысый", "грозный", "тихий", "сонный", "голодный", "кривой", "ржавый", "бодрый", "унылый", "модный",
		"четкий", "дерзкий", "борзый", "лютый", "шустрый", "реальный", "угарный", "наглый"
	};
	private static final String[] RU_NOUN =
	{
		"гном", "пес", "кот", "волк", "сухарь", "травка", "пельмень", "колбаса", "мармелад", "чума",
		"ведьмак", "сквозняк", "холодок", "бомж", "топор", "кабан", "хомяк", "огурец", "батон", "чеснок",
		"валенок", "тапок", "кефир", "компот", "барсук", "ежик", "мухомор", "лопата", "кирпич", "шашлык",
		"беляш", "танкист", "дед", "сосед", "медоед", "каратель", "монах",
		"гопник", "чебурек", "шаурма", "семки", "пацан", "браток", "кент", "батя", "гараж", "подъезд",
		"чушпан", "задрот", "нуб", "читер", "красавчик", "нагибатор", "убиватор", "терминатор"
	};
	private static final String[] RU_NAME =
	{
		"олег", "вася", "дашка", "ленка", "федор", "санек", "толик", "серега", "витек", "петруха",
		"колян", "димон", "настя", "машка", "гриша", "степан", "юрец", "макс",
		"жека", "вован", "толян", "саня", "диман", "леха", "андрюха", "кирюха", "паша", "стас",
		"руслан", "тимур", "натаха", "светка", "танюха", "ирка", "вовчик", "петрович", "палыч"
	};
	private static final String[] RU_SURNAME =
	{
		"пупкин", "сидоров", "петров", "иванов", "смирнов", "кузнецов", "козлов", "волков", "зайцев", "медведев"
	};
	private static final String[] PREFIXES =
	{
		"MC", "Dj", "Mr"
	};
	private static final String[] EN_ADJ =
	{
		"Dark", "Shadow", "Storm", "Night", "Blood", "Ghost", "Crazy", "Old", "Swift", "Silent",
		"Grim", "Mad", "Evil", "Frost", "Iron", "Black", "Wild", "Lone", "Sweet", "True"
	};
	private static final String[] EN_NOUN =
	{
		"Slayer", "Reaper", "Hunter", "Killer", "Knight", "Rogue", "Monk", "Rider", "Arrow", "Blade",
		"Master", "Lord", "King", "Sniper", "Mage", "Angel", "Demon", "Raven", "Wolf", "Dragon",
		"Fire", "Ice", "Death", "Star", "Dream", "Soul", "Fang", "Claw", "Bishop", "Titan"
	};

	private static final String RU_LETTERS = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя";
	/** Транслит: кириллица -> латиница. */
	private static final String[] TRANSLIT =
	{
		"a", "b", "v", "g", "d", "e", "e", "zh", "z", "i", "j", "k", "l", "m", "n", "o", "p", "r",
		"s", "t", "u", "f", "h", "c", "ch", "sh", "sch", "", "y", "", "e", "yu", "ya"
	};
	/** Псевдокириллица: латинские двойники (null = нет похожего, берётся транслит). */
	private static final String[] FAKE_CYR =
	{
		"a", "6", "B", "r", null, "e", "e", null, "3", "u", "u", "K", "JI", "M", "H", "o", "II", "p",
		"c", "T", "y", null, "x", null, "4", "LLI", null, "", "bI", "b", "3", "IO", "9I"
	};

	private static final Map<Character, String> TRANSLIT_MAP = new HashMap<>();
	private static final Map<Character, String> FAKE_CYR_MAP = new HashMap<>();
	private static final Map<Character, Character> LEET_MAP = new HashMap<>();
	private static final Pattern VALID = Pattern.compile("[A-Za-z0-9]{3,16}");
	static
	{
		for (int i = 0; i < RU_LETTERS.length(); i++)
		{
			TRANSLIT_MAP.put(RU_LETTERS.charAt(i), TRANSLIT[i]);
			FAKE_CYR_MAP.put(RU_LETTERS.charAt(i), FAKE_CYR[i]);
		}
		LEET_MAP.put('e', '3');
		LEET_MAP.put('o', '0');
		LEET_MAP.put('a', '4');
		LEET_MAP.put('i', '1');
		LEET_MAP.put('s', '5');
	}

	private final Random _rnd;
	private final boolean _translit;
	private final boolean _fakeCyr;
	private final boolean _leet;
	private final boolean _digits;
	private final boolean _wrap;
	private final int[] _weights;

	public FakePlayerNameGenerator()
	{
		this(new Random());
	}

	public FakePlayerNameGenerator(long seed)
	{
		this(new Random(seed));
	}

	public FakePlayerNameGenerator(Random rnd)
	{
		this(rnd, true, true, true, true, true, new int[]
		{
			25, 55, 20
		});
	}

	/**
	 * @param rnd источник случайности
	 * @param translit разрешить транслит русских слов
	 * @param fakeCyr разрешить псевдокириллицу
	 * @param leet разрешить l33t-цифры в английских словах
	 * @param digits разрешить цифровой суффикс
	 * @param wrap разрешить обёртку xX...Xx
	 * @param weights веса числа частей ника {1 часть, 2 части, 3 части}
	 */
	public FakePlayerNameGenerator(Random rnd, boolean translit, boolean fakeCyr, boolean leet, boolean digits, boolean wrap, int[] weights)
	{
		_rnd = rnd;
		_translit = translit;
		_fakeCyr = fakeCyr;
		_leet = leet;
		_digits = digits;
		_wrap = wrap;
		_weights = weights;
	}

	private boolean chance(int pct)
	{
		return _rnd.nextInt(100) < pct;
	}

	private String pick(String[] arr)
	{
		return arr[_rnd.nextInt(arr.length)];
	}

	private String translitWord(String word)
	{
		final StringBuilder sb = new StringBuilder();
		for (int i = 0; i < word.length(); i++)
		{
			final char ch = word.charAt(i);
			String t = TRANSLIT_MAP.getOrDefault(ch, String.valueOf(ch));
			if ((ch == 'й') && chance(50))
			{
				t = "y";
			}
			if ((ch == 'в') && chance(15))
			{
				t = "w"; // Wowan-стиль
			}
			if (ch == 'х')
			{
				t = chance(40) ? "x" : (chance(30) ? "kh" : "h"); // Xyligan-стиль
			}
			sb.append(t);
		}
		if (sb.length() > 0)
		{
			sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
		}
		return sb.toString();
	}

	private String fakeCyrWord(String word)
	{
		final StringBuilder sb = new StringBuilder();
		for (int i = 0; i < word.length(); i++)
		{
			final char ch = word.charAt(i);
			final String f = FAKE_CYR_MAP.get(ch);
			if (f != null)
			{
				sb.append(f);
			}
			else if (FAKE_CYR_MAP.containsKey(ch))
			{
				sb.append(TRANSLIT_MAP.get(ch)); // нет двойника — транслит буквы
			}
			else
			{
				sb.append(ch);
			}
		}
		return sb.toString();
	}

	private String leetWord(String word)
	{
		final StringBuilder sb = new StringBuilder();
		int used = 0;
		for (int i = 0; i < word.length(); i++)
		{
			final char ch = word.charAt(i);
			final Character l = LEET_MAP.get(Character.toLowerCase(ch));
			if ((l != null) && (used < 2) && chance(45))
			{
				sb.append(l);
				used++;
			}
			else
			{
				sb.append(ch);
			}
		}
		return sb.toString();
	}

	private boolean isRussian(String word)
	{
		for (int i = 0; i < word.length(); i++)
		{
			if (RU_LETTERS.indexOf(word.charAt(i)) >= 0)
			{
				return true;
			}
		}
		return false;
	}

	/** Часть ника: слово + флаг "это фамилия" (для декорации -ov -> -OFF). */
	private static final class Part
	{
		private final String _word;
		private final boolean _surname;

		Part(String word, boolean surname)
		{
			_word = word;
			_surname = surname;
		}

		String word()
		{
			return _word;
		}

		boolean surname()
		{
			return _surname;
		}
	}

	private List<Part> buildParts(boolean russian, int n)
	{
		final List<Part> parts = new ArrayList<>(3);
		if (russian)
		{
			switch (n)
			{
				case 1:
				{
					final int v = _rnd.nextInt(100);
					if (v < 45)
					{
						parts.add(new Part(pick(RU_NOUN), false));
					}
					else if (v < 80)
					{
						parts.add(new Part(pick(RU_NAME), false));
					}
					else
					{
						parts.add(new Part(pick(RU_SURNAME), true));
					}
					break;
				}
				case 2:
				{
					final int v = _rnd.nextInt(4);
					if (v == 0)
					{
						parts.add(new Part(pick(RU_ADJ), false));
						parts.add(new Part(pick(RU_NOUN), false));
					}
					else if (v == 1)
					{
						parts.add(new Part(pick(RU_ADJ), false));
						parts.add(new Part(pick(RU_NAME), false));
					}
					else if (v == 2)
					{
						parts.add(new Part(pick(RU_NOUN), false));
						parts.add(new Part(pick(RU_NOUN), false));
					}
					else
					{
						// VasyaPupkin
						parts.add(new Part(pick(RU_NAME), false));
						parts.add(new Part(pick(RU_SURNAME), true));
					}
					break;
				}
				default:
				{
					parts.add(new Part(pick(RU_ADJ), false));
					if (chance(50))
					{
						parts.add(new Part(pick(RU_NOUN), false));
						parts.add(new Part(pick(RU_NOUN), false));
					}
					else
					{
						parts.add(new Part(pick(RU_NAME), false));
						parts.add(new Part(pick(RU_SURNAME), true));
					}
					break;
				}
			}
		}
		else
		{
			switch (n)
			{
				case 1:
				{
					parts.add(new Part(pick(EN_NOUN), false));
					break;
				}
				case 2:
				{
					parts.add(new Part(pick(EN_ADJ), false));
					parts.add(new Part(pick(EN_NOUN), false));
					break;
				}
				default:
				{
					final String first = pick(EN_ADJ);
					String second;
					if (chance(50))
					{
						do
						{
							second = pick(EN_ADJ);
						}
						while (second.equals(first));
					}
					else
					{
						second = pick(EN_NOUN);
					}
					parts.add(new Part(first, false));
					parts.add(new Part(second, false));
					parts.add(new Part(pick(EN_NOUN), false));
					break;
				}
			}
		}
		return parts;
	}

	/**
	 * @return один ник в стиле сида (валидность НЕ гарантируется — используй {@link #generateBatch(int, Set)}).
	 */
	public String generateOne()
	{
		// Число частей по весам.
		final int total = Math.max(_weights[0] + _weights[1] + _weights[2], 1);
		final int roll = _rnd.nextInt(total);
		final int n = (roll < _weights[0]) ? 1 : (roll < (_weights[0] + _weights[1])) ? 2 : 3;

		final boolean russian = chance(70);
		final List<Part> parts = buildParts(russian, n);
		// Редкая смесь языков в 2-3-частных никах.
		if ((n > 1) && chance(15))
		{
			parts.set(parts.size() - 1, new Part(russian ? pick(EN_NOUN) : translitWord(pick(RU_NOUN)), false));
		}

		// Режим рендера русских слов — один на весь ник.
		final List<String> modes = new ArrayList<>(2);
		if (_translit)
		{
			modes.add("translit");
		}
		if (_fakeCyr)
		{
			modes.add("fakeCyr");
		}
		final String ruMode = modes.isEmpty() ? "translit" : modes.get(_rnd.nextInt(modes.size()));

		final StringBuilder sb = new StringBuilder();
		for (Part part : parts)
		{
			final String word = part.word();
			if (isRussian(word))
			{
				if ("fakeCyr".equals(ruMode))
				{
					sb.append(fakeCyrWord(word));
				}
				else
				{
					String t = translitWord(word);
					// Фамилии на -ov: SmirnOFF / Smirnoff.
					if (part.surname() && t.toLowerCase().endsWith("ov") && chance(50))
					{
						t = t.substring(0, t.length() - 2) + (chance(40) ? "OFF" : "off");
					}
					sb.append(t);
				}
			}
			else
			{
				sb.append((_leet && chance(20)) ? leetWord(word) : word);
			}
		}
		String nick = sb.toString();

		// Декорации (стиль сида: dashka, ZloyPES, HolodOK, OldRogue38, MCVovan95, xXDarkSlayerXx).
		if ("translit".equals(ruMode))
		{
			if (chance(12))
			{
				nick = nick.toLowerCase();
			}
			else if (chance(12) && (nick.length() > 4))
			{
				final int k = 2 + _rnd.nextInt(2);
				nick = nick.substring(0, nick.length() - k) + nick.substring(nick.length() - k).toUpperCase();
			}
		}
		if (_digits && chance(28) && (nick.length() <= 14))
		{
			final int v = _rnd.nextInt(100);
			final String suf;
			if (v < 45)
			{
				suf = chance(50) ? String.valueOf(1 + _rnd.nextInt(9)) : String.valueOf(10 + _rnd.nextInt(90));
			}
			else if (v < 80)
			{
				suf = String.format("%02d", (85 + _rnd.nextInt(23)) % 100); // год рождения 85..07
			}
			else
			{
				suf = String.valueOf(1985 + _rnd.nextInt(23)); // 1985..2007
			}
			if ((nick.length() + suf.length()) <= 16)
			{
				nick += suf;
			}
		}
		if (chance(4) && (nick.length() <= 13))
		{
			nick += "RUS";
		}
		boolean prefixed = false;
		if (chance(6) && (nick.length() <= 14))
		{
			nick = pick(PREFIXES) + nick; // MCVovan
			prefixed = true;
		}
		if (_wrap && !prefixed && chance(4) && (nick.length() <= 12))
		{
			nick = "xX" + nick + "Xx";
		}
		return nick;
	}

	/**
	 * @param count сколько ников сгенерировать
	 * @param taken занятые имена в lowercase (пополняется); null — только уникальность внутри батча
	 * @return count уникальных ников, каждый соответствует [A-Za-z0-9]{3,16}
	 */
	public List<String> generateBatch(int count, Set<String> taken)
	{
		final Set<String> used = (taken != null) ? taken : new HashSet<>();
		final List<String> out = new ArrayList<>(count);
		int guard = count * 200;
		while ((out.size() < count) && (guard-- > 0))
		{
			final String nick = generateOne();
			if (VALID.matcher(nick).matches() && !used.contains(nick.toLowerCase()))
			{
				used.add(nick.toLowerCase());
				out.add(nick);
			}
		}
		return out;
	}

	public static void main(String[] args)
	{
		final int count = (args.length > 0) ? Integer.parseInt(args[0]) : 30;
		final FakePlayerNameGenerator gen = (args.length > 1) ? new FakePlayerNameGenerator(Long.parseLong(args[1])) : new FakePlayerNameGenerator();
		for (String nick : gen.generateBatch(count, null))
		{
			System.out.println(nick);
		}
	}
}
