/*
 * 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.managers;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Personal speech style of a fake player, derived deterministically from the
 * character id (nothing is stored, survives restarts by construction).
 * Mirrors the sandbox StylePass 1:1: laughter/filler markers, "go" form,
 * comma habits, personal vocabulary partition and a private shop assortment.
 * @author Mobius SPP
 */
public class FakePlayerIdiolect
{
	private static final Map<Integer, FakePlayerIdiolect> CACHE = new ConcurrentHashMap<>();
	
	private static final String[] LAUGHS =
	{
		"кек",
		"лол",
		")))",
		"ахах",
		"хех"
	};
	private static final String[] FILLERS =
	{
		"ну",
		"короч",
		"прям",
		"вообще",
		"эт самое"
	};
	private static final String[] GO_FORMS =
	{
		"го",
		"гоу",
		"погнали",
		"го го"
	};
	private static final String[] GOODS =
	{
		"соски D",
		"бижа D",
		"свитки тп",
		"дубина+3",
		"кристаллы D",
		"банки хп",
		"рецепты C",
		"уголь духа"
	};
	private static final Pattern CONJ_PATTERN = Pattern.compile("\\s(?:а|но|что|чтобы|если|когда|который|пока|хотя)\\s");
	private static final Pattern GO_PATTERN = Pattern.compile("\\bго\\b", Pattern.UNICODE_CHARACTER_CLASS);
	
	/** Era chat contractions, applied per word with the bot's personal slang intensity. */
	private static final Object[][] CONTRACTIONS =
	{
		{ Pattern.compile("\\bсейчас\\b", Pattern.UNICODE_CHARACTER_CLASS), "щас" },
		{ Pattern.compile("\\bвообще\\b", Pattern.UNICODE_CHARACTER_CLASS), "ваще" },
		{ Pattern.compile("\\bтолько\\b", Pattern.UNICODE_CHARACTER_CLASS), "тока" },
		{ Pattern.compile("\\bсколько\\b", Pattern.UNICODE_CHARACTER_CLASS), "скок" },
		{ Pattern.compile("\\bнормально\\b", Pattern.UNICODE_CHARACTER_CLASS), "норм" },
		{ Pattern.compile("\\bспасибо\\b", Pattern.UNICODE_CHARACTER_CLASS), "спс" },
		{ Pattern.compile("\\bпожалуйста\\b", Pattern.UNICODE_CHARACTER_CLASS), "плз" },
		{ Pattern.compile("\\bчто\\b", Pattern.UNICODE_CHARACTER_CLASS), "че" },
		{ Pattern.compile("\\bчего\\b", Pattern.UNICODE_CHARACTER_CLASS), "чо" },
		{ Pattern.compile("\\bкто-нибудь\\b", Pattern.UNICODE_CHARACTER_CLASS), "кто-нить" },
		{ Pattern.compile("\\bчто-нибудь\\b", Pattern.UNICODE_CHARACTER_CLASS), "че-нить" },
		{ Pattern.compile("\\bкороче\\b", Pattern.UNICODE_CHARACTER_CLASS), "короч" },
		{ Pattern.compile("\\bнаверное\\b", Pattern.UNICODE_CHARACTER_CLASS), "наверн" },
		{ Pattern.compile("\\bсегодня\\b", Pattern.UNICODE_CHARACTER_CLASS), "сёдня" },
		{ Pattern.compile("\\bздесь\\b", Pattern.UNICODE_CHARACTER_CLASS), "тут" },
		{ Pattern.compile("\\bпосмотри\\b", Pattern.UNICODE_CHARACTER_CLASS), "глянь" }
	};
	
	public final int charId;
	public final double chattiness; // 0.4..1.6, scales optional chunk chance.
	public final double punct; // 0 / 0.6 / 1 - comma habits.
	public final String laugh;
	public final String filler;
	public final String go;
	public final boolean shortAddress; // Shortens long nicknames.
	public final boolean yo; // "che" -> "chyo".
	public final double silence; // 0..0.3 chance to ignore a stranger's first message.
	public final boolean traitMat;
	public final boolean traitEng;
	public final boolean traitOld;
	public final double loudness; // Channel preference (shout/trade lover).
	public final double slang; // 0..1 - how hard the bot contracts words (щас/ваще/тока).
	public final String storeItem;
	public final String storePrice;
	private final int _vocabSeed;
	
	private FakePlayerIdiolect(int charId)
	{
		this.charId = charId;
		final long[] state =
		{
			hash32("idio" + charId)
		};
		chattiness = round2(0.4 + (next(state) * 1.2));
		final double[] punctRoll =
		{
			0,
			0,
			0.6,
			1
		};
		punct = punctRoll[(int) (next(state) * 4)];
		laugh = LAUGHS[(int) (next(state) * LAUGHS.length)];
		filler = FILLERS[(int) (next(state) * FILLERS.length)];
		go = GO_FORMS[(int) (next(state) * GO_FORMS.length)];
		shortAddress = next(state) > 0.66;
		yo = next(state) < 0.3;
		silence = round2(next(state) * 0.3);
		traitMat = next(state) < 0.35;
		traitEng = next(state) < 0.3;
		traitOld = next(state) < 0.25;
		loudness = round2(next(state));
		storeItem = GOODS[(int) (next(state) * GOODS.length)];
		slang = round2(next(state));
		storePrice = ((5 + (int) (next(state) * 45)) * 10) + "к";
		_vocabSeed = hash32("vocab" + charId);
	}
	
	public static FakePlayerIdiolect of(int charId)
	{
		return CACHE.computeIfAbsent(charId <= 0 ? 0 : charId, FakePlayerIdiolect::new);
	}
	
	public boolean hasTrait(String trait)
	{
		switch (trait)
		{
			case "mat":
			{
				return traitMat;
			}
			case "eng":
			{
				return traitEng;
			}
			case "old":
			{
				return traitOld;
			}
		}
		return false;
	}
	
	/**
	 * @param poolKey pool name.
	 * @param size pool size.
	 * @param index line index.
	 * @return true when this line belongs to the bot's personal vocabulary subset of the pool.
	 */
	public boolean ownsPoolLine(String poolKey, int size, int index)
	{
		if (size <= 5)
		{
			return true;
		}
		final int keep = Math.max(4, (int) Math.ceil(size * 0.6));
		final int[] order = new int[size];
		for (int i = 0; i < size; i++)
		{
			order[i] = i;
		}
		final long[] state =
		{
			_vocabSeed ^ hash32(poolKey)
		};
		for (int i = size - 1; i > 0; i--)
		{
			final int j = (int) (next(state) * (i + 1));
			final int tmp = order[i];
			order[i] = order[j];
			order[j] = tmp;
		}
		for (int i = 0; i < keep; i++)
		{
			if (order[i] == index)
			{
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Incoming message analysis for style mirroring.
	 */
	public static class Mirror
	{
		public boolean noPunct;
		public boolean shortMessage;
		
		public static Mirror of(String incoming)
		{
			final Mirror mirror = new Mirror();
			mirror.noPunct = !incoming.matches(".*[,.!?].*");
			mirror.shortMessage = incoming.trim().split("\\s+").length <= 2;
			return mirror;
		}
	}
	
	/**
	 * Applies the personal style to an assembled line: laughter/filler markers,
	 * "go" form, "yo" habit and comma rules; mirrors the interlocutor when given.
	 * @param text assembled line (slots already filled).
	 * @param mirror incoming message analysis (may be null).
	 * @return styled line.
	 */
	public String stylePass(String text, Mirror mirror)
	{
		String result = text.replace("~lol~", laugh).replace("~f~", filler);
		if (!"го".equals(go))
		{
			result = GO_PATTERN.matcher(result).replaceAll(go);
		}
		if (yo)
		{
			result = result.replaceAll("\\bче\\b", "чё").replaceAll("\\bчо\\b", "чё");
		}
		if (slang > 0.15)
		{
			for (Object[] contraction : CONTRACTIONS)
			{
				if (Math.random() < (slang * 0.8))
				{
					result = ((Pattern) contraction[0]).matcher(result).replaceAll((String) contraction[1]);
				}
			}
		}
		double effectivePunct = punct;
		if ((mirror != null) && mirror.noPunct)
		{
			effectivePunct = Math.min(effectivePunct, 0.3);
		}
		if (effectivePunct > 0)
		{
			final Matcher matcher = CONJ_PATTERN.matcher(result);
			final StringBuilder sb = new StringBuilder();
			while (matcher.find())
			{
				matcher.appendReplacement(sb, (Math.random() < effectivePunct ? "," : "") + Matcher.quoteReplacement(matcher.group()));
			}
			matcher.appendTail(sb);
			result = sb.toString();
		}
		else
		{
			result = result.replace(",", "");
		}
		return result.replaceAll("\\s+", " ").replaceAll("\\s,", ",").replaceAll(",\\s*,+", ",").replaceAll("\\s+([:.!?])", "$1").trim();
	}
	
	/**
	 * @param nick interlocutor nickname.
	 * @return the form of address this bot uses (full or shortened nick).
	 */
	public String addressForm(String nick)
	{
		if (shortAddress && (nick != null) && (nick.length() > 5))
		{
			return nick.substring(0, 4);
		}
		return nick;
	}
	
	// --- Deterministic PRNG, same as the sandbox (fnv1a hash + mulberry32) ---
	
	static int hash32(String text)
	{
		int hash = 0x811C9DC5;
		for (int i = 0; i < text.length(); i++)
		{
			hash ^= text.charAt(i);
			hash *= 0x01000193;
		}
		return hash;
	}
	
	private static double next(long[] state)
	{
		int seed = (int) state[0];
		seed += 0x6D2B79F5;
		state[0] = seed;
		int t = (seed ^ (seed >>> 15)) * (1 | seed);
		t = (t + ((t ^ (t >>> 7)) * (61 | t))) ^ t;
		return ((t ^ (t >>> 14)) & 0xFFFFFFFFL) / 4294967296.0;
	}
	
	private static double round2(double value)
	{
		return Math.round(value * 100.0) / 100.0;
	}
}
