package com.platform.common.utils;

import java.util.*;

/**
 * 敏感词库工具<br>
 * 每个敏感词库独立使用对象，加对象锁（初始化、增加、删除）
 *
 * @author Anine
 * @date 2019/3/11
 */
public class SensitiveWordUtil {
	/**
	 * 敏感词匹配规则，最小匹配规则，如：敏感词库["中国","中国人"]，语句："我是中国人"，匹配结果：我是[中国]人
	 */
	private static final int MinMatchTYpe = 1;
	/**
	 * 敏感词匹配规则，最大匹配规则，如：敏感词库["中国","中国人"]，语句："我是中国人"，匹配结果：我是[中国人]
	 */
	private static final int MaxMatchType = 2;

	/**
	 * 敏感词集合
	 */
	private HashMap sensitiveWordMap = new HashMap();

	/**
	 * 停顿符集合
	 */
	private HashSet<String> stopWords = new HashSet<>();

	/**
	 * 初始化敏感词工具：敏感词库、停顿符库
	 *
	 * @param sensitiveWordSet
	 *            敏感词库
	 */
	public synchronized void init(Set<String> sensitiveWordSet, HashSet<String> stopWords) {
		initSensitiveWordMap(sensitiveWordSet);
		initStopWords(stopWords);
	}

	/**
	 * 初始化敏感词工具：敏感词库及对应的替换词库、停顿符库
	 *
	 * @param sensitiveWords
	 *            敏感词库
	 * @param stopWords
	 *            停顿符库
	 */
	public synchronized void init(Map<String, String> sensitiveWords, Set<String> stopWords) {
		initSensitiveWordMap(sensitiveWords);
		initStopWords(stopWords);
	}

	/**
	 * 初始化停顿符库
	 *
	 * @param words
	 *            停顿符库
	 */
	private synchronized void initStopWords(Set<String> words) {
		stopWords = new HashSet<>();
		if (words != null && words.size() > 0) {
			for (String s : words) {
				if (s != null && s.length() == 1) {
					stopWords.add(convert(s));
				}
			}
		}
	}

	/**
	 * 初始化敏感词库及对应的替换词库，构建DFA算法模型
	 *
	 * @param sensitiveWordSet
	 *            敏感词库
	 */
	@SuppressWarnings("unchecked")
	private synchronized void initSensitiveWordMap(Set<String> sensitiveWordSet) {
		// 初始化敏感词容器，减少扩容操作
		sensitiveWordMap = new HashMap(sensitiveWordSet.size());
		String key;
		Map nowMap;
		// 迭代sensitiveWordSet
		if (sensitiveWordSet.size() > 0) {
			for (String sensitiveWord : sensitiveWordSet) {
				// 关键字
				key = convert(sensitiveWord);
				nowMap = sensitiveWordMap;
				for (int i = 0; i < key.length(); i++) {
					// 转换成char型
					char keyChar = key.charAt(i);
					// 如果存在该key，直接赋值，用于下一个循环获取
					nowMap = sensitiveWordMapInit(nowMap, keyChar);

					if (i == key.length() - 1) {
						// 最后一个
						nowMap.put("isEnd", "1");
					}
				}
			}
		}
	}

	/**
	 * 初始化敏感词库，构建DFA算法模型,包含对应的替换字符串
	 *
	 * @param sensitiveWords
	 *            敏感词库
	 */
	@SuppressWarnings("unchecked")
	private synchronized void initSensitiveWordMap(Map<String, String> sensitiveWords) {
		sensitiveWordMap = new HashMap(sensitiveWords.size());
		String key;
		String replaceWord;
        if (sensitiveWords.size() > 0) {
			for (String sensitiveWord : sensitiveWords.keySet()) {
				key = convert(sensitiveWord);
				replaceWord = convert(sensitiveWords.get(sensitiveWord));
                addSensitiveWordAndReplaceWord(key, replaceWord);
			}
		}

	}

	/**
	 * 判断文字是否包含敏感字符
	 *
	 * @param txt
	 *            文字
	 * @param matchType
	 *            匹配规则 1：最小匹配规则，2：最大匹配规则
	 * @return 若包含返回true，否则返回false
	 */
	public boolean contains(String txt, int matchType) {
		boolean flag = false;
		for (int i = 0; i < txt.length(); i++) {
			// 判断是否包含敏感字符
			int matchFlag = checkSensitiveWord(txt, i, matchType);
			if (matchFlag > 0) {
				// 大于0存在，返回true
				flag = true;
			}
		}
		return flag;
	}

	/**
	 * 判断文字是否包含敏感字符，默认为最大匹配规则
	 *
	 * @param txt
	 *            文字
	 * @return 若包含返回true，否则返回false
	 */
	public boolean contains(String txt) {
		return contains(txt, MaxMatchType);
	}

	/**
	 * 获取文字中的敏感词
	 *
	 * @param txt
	 *            文字
	 * @param matchType
	 *            匹配规则 1：最小匹配规则，2：最大匹配规则
	 * @return 敏感词集合
	 */
	private Set<String> getSensitiveWord(String txt, int matchType) {
		Set<String> sensitiveWordList = new HashSet<>();

		for (int i = 0; i < txt.length(); i++) {
			// 判断是否包含敏感字符
			int length = checkSensitiveWord(txt, i, matchType);
			if (length > 0) {
				// 存在,加入list中
				sensitiveWordList.add(txt.substring(i, i + length));
				// 减1的原因，是因为for会自增
				i = i + length - 1;
			}
		}

		return sensitiveWordList;
	}

	/**
	 * 获取文字中的敏感词,默认为最大匹配规则
	 *
	 * @param txt
	 *            文字
	 * @return 敏感词集合
	 */
	public Set<String> getSensitiveWord(String txt) {
		return getSensitiveWord(txt, MaxMatchType);
	}

	/**
	 * 替换敏感字字符
	 *
	 * @param txt
	 *            文本
	 * @param replaceChar
	 *            替换的字符，匹配的敏感词以字符逐个替换，如 语句：我爱中国人 敏感词：中国人，替换字符：*， 替换结果：我爱***
	 * @param matchType
	 *            敏感词匹配规则 1：最小匹配规则，2：最大匹配规则
	 * @return 替换后的文本
	 */
	public String replaceSensitiveWord(String txt, char replaceChar, int matchType) {
		String resultTxt = txt;
		// 获取所有的敏感词
		Set<String> set = getSensitiveWord(txt, matchType);
		Iterator<String> iterator = set.iterator();
		String word;
		String replaceString;
		while (iterator.hasNext()) {
			word = iterator.next();
			replaceString = getReplaceChars(replaceChar, word.length());
			resultTxt = resultTxt.replaceAll(word, replaceString);
		}

		return resultTxt;
	}
	/**
	 * 替换敏感字字符，默认使用最大匹配规则
	 *
	 * @param txt
	 *            文本
	 * @param replaceChar
	 *            替换的字符，匹配的敏感词以字符逐个替换，如 语句：我爱中国人 敏感词：中国人，替换字符：*， 替换结果：我爱***
	 * @return 替换后的文本
	 */
	public String replaceSensitiveWord(String txt, char replaceChar) {
		return replaceSensitiveWord(txt, replaceChar, MaxMatchType);
	}
	/**
	 * 替换敏感字字符
	 *
	 * @param txt
	 *            文本
	 * @param replaceStr
	 *            替换的字符串，匹配的敏感词以字符逐个替换，如 语句：我爱中国人 敏感词：中国人，替换字符串：[屏蔽]，替换结果：我爱[屏蔽]
	 * @param matchType
	 *            敏感词匹配规则：1：最小匹配规则，2：最大匹配规则
	 * @return 替换后的文本
	 */
	public String replaceSensitiveWord(String txt, String replaceStr, int matchType) {
		String resultTxt = txt;
		// 获取所有的敏感词
		Set<String> set = getSensitiveWord(txt, matchType);
		Iterator<String> iterator = set.iterator();
		String word;
		while (iterator.hasNext()) {
			word = iterator.next();
			resultTxt = resultTxt.replaceAll(word, replaceStr);
		}

		return resultTxt;
	}
	/**
	 * 替换敏感字字符，默认使用最大匹配规则
	 *
	 * @param txt
	 *            文本
	 * @param replaceStr
	 *            替换的字符串，匹配的敏感词以字符逐个替换，如 语句：我爱中国人 敏感词：中国人，替换字符串：[屏蔽]，替换结果：我爱[屏蔽]
	 * @return 替换后的文本
	 */
	public String replaceSensitiveWord(String txt, String replaceStr) {
		return replaceSensitiveWord(txt, replaceStr, MaxMatchType);
	}

	/**
	 * 使用敏感词集合内部替换词替换敏感词
	 * 
	 * @param txt
	 *            文本
	 * @param matchType
	 *            敏感词匹配规则：1：最小匹配规则，2：最大匹配规则
	 * @return 替换后的文本
	 */
	public String replaceSensitiveWord(String txt, int matchType) {

		// txt转换
		StringBuilder text = new StringBuilder(txt);
		StringBuilder test = new StringBuilder(convert(txt));
		for (int i = 0; i < test.length(); i++) {
			int j;
			// 敏感词结束标识位：用于敏感词只有1位的情况
			boolean flag = false;
			// 匹配标识数默认为0
			int matchFlag = 0;
			char word;

			Map nowMap = sensitiveWordMap;
			String replaceWord;
			if (nowMap.containsKey(test.charAt(i))) {
				matchFlag++;
				Object replace = null;
				// 上一个字符
				char previous = test.charAt(i);
				// 上一个map
				Map previousMap;
				for (j = i; j < test.length(); j++) {
					word = test.charAt(j);
					// 获取指定key
					previousMap = nowMap;
					nowMap = (Map) nowMap.get(word);
					if (nowMap != null) {
						// 存在，则判断是否为最后一个
						// 找到相应key，匹配标识+1
						previous = word;
						matchFlag++;
						// 如果为最后一个匹配规则,结束循环，返回匹配标识数
						if ("1".equals(nowMap.get("isEnd"))) {
							// 结束标志位为true
							flag = true;
							replace = nowMap.get("replaceWord");

							// 最小规则，直接返回,最大规则还需继续查找
							if (MinMatchTYpe == matchType) {
								break;
							}
						}
					} else {
						// 不存在，判断是否为停顿符
						if (stopWords.contains(word + "")) {
							matchFlag++;
							nowMap = previousMap;
							continue;
						}
						// 判断是否重复
						if (i != j && word == previous) {
							matchFlag++;
							nowMap = previousMap;
							continue;
						}
						break;

					}
				}

				if (matchFlag > 0 && flag) {
					replaceWord = String.valueOf(replace);
					test.replace(i, j, replaceWord);
					text.replace(i, j, replaceWord);
					i = i + replaceWord.length() - 1;
				}
			}
		}
		return text.toString();
	}

	/**
	 * 使用敏感词集合内部替换词替换敏感词 默认使用最大匹配规则
	 * 
	 * @param txt
	 *            文本
	 * @return 替换后的文本
	 */
	public String replaceSensitiveWord(String txt) {
		return replaceSensitiveWord(txt, MaxMatchType);
	}
	/**
	 * 获取替换字符串
	 *
	 * @param replaceChar
	 *            替换字符
	 * @param length
	 *            长度
	 * @return 替换字符串
	 */
	public String getReplaceChars(char replaceChar, int length) {
		StringBuilder resultReplace = new StringBuilder(String.valueOf(replaceChar));
		for (int i = 1; i < length; i++) {
			resultReplace.append(replaceChar);
		}

		return resultReplace.toString();
	}
	/**
	 * 检查文字某个位置字符开始是否为敏感词开头，检查规则如下：<br>
	 *
	 * @param txt
	 *            文本
	 * @param beginIndex
	 *            开始位置
	 * @param matchType
	 *            匹配规则
	 * @return 如果存在，则返回敏感词字符的长度，不存在返回0
	 */
	private int checkSensitiveWord(String txt, int beginIndex, int matchType) {
		// txt转换
		txt = convert(txt);

		// 敏感词结束标识位：用于敏感词只有1位的情况
		boolean flag = false;
		// 匹配标识数默认为0
		int matchFlag = 0;
		char word;
		Map nowMap = sensitiveWordMap;
		char previous = txt.charAt(beginIndex);

		// 上一个map
		Map previousMap;

		for (int i = beginIndex; i < txt.length(); i++) {
			word = txt.charAt(i);
			// 获取指定key
			previousMap = nowMap;
			nowMap = (Map) nowMap.get(word);
			if (nowMap != null) {
				// 存在，则判断是否为最后一个
				// 找到相应key，匹配标识+1
				previous = word;
				matchFlag++;
				// 如果为最后一个匹配规则,结束循环，返回匹配标识数
				if ("1".equals(nowMap.get("isEnd"))) {
					// 结束标志位为true
					flag = true;
					// 最小规则，直接返回,最大规则还需继续查找
					if (MinMatchTYpe == matchType) {
						break;
					}
				}
			} else {
				// 不存在，判断是否为停顿符
				if (stopWords.contains(word + "")) {
					matchFlag++;
					nowMap = previousMap;
					continue;
				}

				// 判断是否重复
				if (i != beginIndex && word == previous) {
					matchFlag++;
					nowMap = previousMap;
					continue;
				}
				break;
			}
		}

		if (matchFlag < 2 || !flag) {
			// 长度必须大于等于1，为词
			matchFlag = 0;
		}
		return matchFlag;
	}

	/**
	 * 大写转化为小写 全角转化为半角
	 *
	 * @param src
	 *            字符串
	 * @return 转化后的字符串
	 */
	private static String convert(String src) {
		String s = StringKit.toBj(src);
		return s.toLowerCase();
	}

	/**
	 * map 迭代
	 * 
	 * @param nowMap
	 *            当前map
	 * @param keyChar
	 *            字符
	 * @return 字符的map
	 */
	@SuppressWarnings("unchecked")
	private static Map sensitiveWordMapInit(Map nowMap, char keyChar) {
		// 库中获取关键字
		Object wordMap = nowMap.get(keyChar);
		Map<String, String> newWorMap;
		if (wordMap != null) {
			nowMap = (Map) wordMap;
		} else {
			// 不存在则，则构建一个map，同时将isEnd设置为0，因为他不是最后一个
			newWorMap = new HashMap<>();
			// 不是最后一个
			newWorMap.put("isEnd", "0");
			nowMap.put(keyChar, newWorMap);
			nowMap = newWorMap;
		}
		return nowMap;
	}

	/**
	 * 敏感词库增加（敏感词-替换词）
	 * 
	 * @param sensitiveWord
	 *            敏感词
	 * @param replaceWord
	 *            替换词
	 */
	@SuppressWarnings("unchecked")
	public void addSensitiveWordAndReplaceWord(String sensitiveWord, String replaceWord) {
		if (sensitiveWord == null) {
			return;
		}
		if (replaceWord == null) {
			replaceWord = getReplaceChars('*', sensitiveWord.length());
		}
		Map nowMap = sensitiveWordMap;
		sensitiveWord = convert(sensitiveWord);
		replaceWord = convert(replaceWord);
		for (int i = 0; i < sensitiveWord.length(); i++) {
			char keyChar = sensitiveWord.charAt(i);
			nowMap = sensitiveWordMapInit(nowMap, keyChar);
			if (i == sensitiveWord.length() - 1) {
				nowMap.put("isEnd", "1");
				nowMap.put("replaceWord", replaceWord);
			}
		}
	}

	/**
	 * 敏感词库删除
	 *
	 * @param sensitiveWord
	 *            敏感词
	 */
	@SuppressWarnings("unchecked")
	public void deleteSensitive(String sensitiveWord) {
		if (sensitiveWord == null) {
			return;
		}
		sensitiveWord = convert(sensitiveWord);
		Map nowMap = sensitiveWordMap;
		for (int i = 0; i < sensitiveWord.length(); i++) {
			char keyChar = sensitiveWord.charAt(i);
			nowMap = sensitiveWordMapInit(nowMap, keyChar);
			if (nowMap == null) {
				return;
			}
			if (i == sensitiveWord.length() - 1) {
				nowMap.put("isEnd", 0);
			}
		}
	}

	/**
	 * 增加停顿符
	 * 
	 * @param stopWord
	 *            停顿符
	 */
	public void addStopWord(String stopWord) {
		if (stopWord != null && stopWord.length() == 1) {
			stopWords.add(convert(stopWord));
		}
	}

	/**
	 * 删除停顿符
	 * 
	 * @param oriStopWord
	 *            停顿符
	 */
	public void deleteStopWord(String oriStopWord) {
		stopWords.remove(convert(oriStopWord));
	}

	public static void main(String[] args) {
		SensitiveWordUtil sensitiveWordUtil = new SensitiveWordUtil();
		Map<String, String> map = new HashMap<>();
		map.put("4567", "22");
		map.put("456", "22");
		map.put("Ea", "22");
		Set<String> stopWords = new HashSet<>();
		stopWords.add("@");
		sensitiveWordUtil.init(map, stopWords);
		String txt = "123@45@6789146044@5@61E@a32eA";
		System.out.println("文本：" + txt);
		String result = sensitiveWordUtil.replaceSensitiveWord(txt);
		System.out.println("敏感词：" + sensitiveWordUtil.getSensitiveWord(txt).toString());
		System.out.println("替换结果：" + result);
	}

}
