package com.cyksj.common.util;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.cyksj.dto.ArkIpAddressDto;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author  chan 字符串操作辅助类
 **/
public final class StringUtil {

	/**
	 * Linux and macOS.
	 */
	private static final String LF = "\n";

	/**
	 * Classic macOS.
	 */
	private static final String CR = "\r";

	/**
	 * Windows.
	 */
	private static final String CRLF = "\r\n";

	public static final String QM = "?";

	public static final String AD = "&";

	/**
	 * An empty immutable {@code String} array.
	 */
	public static final String[] EMPTY_STRING_ARRAY = new String[0];


	public static final String EMPTY = "";

	public static final String randomStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

	private static final String baseStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

	private static final String PAID_IP_APP_CODE_AUTH = "APPCODE ce891b0008634139a493d477c064ff19";

	/**
	 * provate constructor
	 */
	private StringUtil() { }

	// Empty checks
	//-----------------------------------------------------------------------
	/**
	 * <p>Checks if a CharSequence is empty ("") or null.</p>
	 *
	 * <pre>
	 * Strings.isEmpty(null)      = true
	 * Strings.isEmpty("")        = true
	 * Strings.isEmpty(" ")       = false
	 * Strings.isEmpty("bob")     = false
	 * Strings.isEmpty("  bob  ") = false
	 * </pre>
	 *
	 * <p>NOTE: This method changed in Lang version 2.0.
	 * It no longer trims the CharSequence.
	 * That functionality is available in isBlank().</p>
	 *
	 * @param cs  the CharSequence to check, may be null
	 * @return {@code true} if the CharSequence is empty or null
	 */
	public static boolean isEmpty(final String cs) {
		return cs == null || cs.isEmpty();
	}

	/**
	 * <p>Checks if a CharSequence is not empty ("") and not null.</p>
	 *
	 * <pre>
	 * Strings.isNotEmpty(null)      = false
	 * Strings.isNotEmpty("")        = false
	 * Strings.isNotEmpty(" ")       = true
	 * Strings.isNotEmpty("bob")     = true
	 * Strings.isNotEmpty("  bob  ") = true
	 * </pre>
	 *
	 * @param cs  the CharSequence to check, may be null
	 * @return {@code true} if the CharSequence is not empty and not null
	 */
	public static boolean isNotEmpty(final String cs) {
		return !isEmpty(cs);
	}

	/**
	 * <p>Checks if a CharSequence is empty (""), null or whitespace only.</p>
	 *
	 * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
	 *
	 * <pre>
	 * Strings.isBlank(null)      = true
	 * Strings.isBlank("")        = true
	 * Strings.isBlank(" ")       = true
	 * Strings.isBlank("bob")     = false
	 * Strings.isBlank("  bob  ") = false
	 * </pre>
	 *
	 * @param cs  the CharSequence to check, may be null
	 * @return {@code true} if the CharSequence is null, empty or whitespace only
	 */
	public static boolean isBlank(final String cs) {
		return cs == null || cs.isBlank();
	}

	/**
	 * <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p>
	 *
	 * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
	 *
	 * <pre>
	 * Strings.isNotBlank(null)      = false
	 * Strings.isNotBlank("")        = false
	 * Strings.isNotBlank(" ")       = false
	 * Strings.isNotBlank("bob")     = true
	 * Strings.isNotBlank("  bob  ") = true
	 * </pre>
	 *
	 * @param cs  the CharSequence to check, may be null
	 * @return {@code true} if the CharSequence is
	 *  not empty and not null and not whitespace only
	 */
	public static boolean isNotBlank(final String cs) {
		return !isBlank(cs);
	}

	/**
	 * <p>Compares two CharSequences, returning {@code true} if they represent
	 * equal sequences of characters.</p>
	 *
	 * <p>{@code null}s are handled without exceptions. Two {@code null}
	 * references are considered to be equal. The comparison is <strong>case sensitive</strong>.</p>
	 *
	 * <pre>
	 * StringUtils.equals(null, null)   = true
	 * StringUtils.equals(null, "abc")  = false
	 * StringUtils.equals("abc", null)  = false
	 * StringUtils.equals("abc", "abc") = true
	 * StringUtils.equals("abc", "ABC") = false
	 * </pre>
	 *
	 * @param cs1  the first CharSequence, may be {@code null}
	 * @param cs2  the second CharSequence, may be {@code null}
	 * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null}
	 */
	public static boolean equals(final String cs1, final String cs2) {
		if (cs1 == null && cs2 == null) {
			return true;
		}
		if (cs1 == null || cs2 == null) {
			return false;
		}
		if (cs1.length() != cs2.length()) {
			return false;
		}
		return cs1.equals(cs2);
	}

	/**
	 * <p>Compares two CharSequences, returning {@code true} if they represent
	 * equal sequences of characters, ignoring case.</p>
	 *
	 * <p>{@code null}s are handled without exceptions. Two {@code null}
	 * references are considered equal. The comparison is <strong>case insensitive</strong>.</p>
	 *
	 * <pre>
	 * StringUtils.equalsIgnoreCase(null, null)   = true
	 * StringUtils.equalsIgnoreCase(null, "abc")  = false
	 * StringUtils.equalsIgnoreCase("abc", null)  = false
	 * StringUtils.equalsIgnoreCase("abc", "abc") = true
	 * StringUtils.equalsIgnoreCase("abc", "ABC") = true
	 * </pre>
	 *
	 * @param cs1  the first CharSequence, may be {@code null}
	 * @param cs2  the second CharSequence, may be {@code null}
	 * @return {@code true} if the CharSequences are equal (case-insensitive), or both {@code null}
	 */
	public static boolean equalsIgnoreCase(final String cs1, final String cs2) {
		if (cs1 == null && cs2 == null) {
			return true;
		}
		if (cs1 == null || cs2 == null) {
			return false;
		}
		if (cs1.length() != cs2.length()) {
			return false;
		}
		return cs1.equalsIgnoreCase(cs2);
	}

	/**
	 * <p>Check if a CharSequence starts with a specified prefix.</p>
	 *
	 * <p>{@code null}s are handled without exceptions. Two {@code null}
	 * references are considered to be equal. The comparison is case sensitive.</p>
	 *
	 * <pre>
	 * StringUtils.startsWith(null, null)      = true
	 * StringUtils.startsWith(null, "abc")     = false
	 * StringUtils.startsWith("abcdef", null)  = false
	 * StringUtils.startsWith("abcdef", "abc") = true
	 * StringUtils.startsWith("ABCDEF", "abc") = false
	 * </pre>
	 *
	 * @see String#startsWith(String)
	 * @param str  the CharSequence to check, may be null
	 * @param prefix the prefix to find, may be null
	 * @return {@code true} if the CharSequence starts with the prefix, case sensitive, or
	 *  both {@code null}
	 */
	public static boolean startsWith(final String str, final String prefix) {
		if (null == str && prefix == null) {
			return true;
		}
		if (null == str || prefix == null) {
			return false;
		}
		return str.startsWith(prefix);
	}

	/**
	 * <p>Check if a CharSequence ends with a specified suffix (optionally case insensitive).</p>
	 *
	 * @see String#endsWith(String)
	 * @param str  the CharSequence to check, may be null
	 * @param suffix the suffix to find, may be null
	 * @return {@code true} if the CharSequence starts with the prefix or
	 *  both {@code null}
	 */
	public static boolean endsWith(final String str, final String suffix) {
		if (null == str && suffix == null) {
			return true;
		}
		if (null == str || suffix == null) {
			return false;
		}
		return str.endsWith(suffix);
	}

	/**
	 * Performs the logic for the {@code split} and
	 * {@code splitPreserveAllTokens} methods that do not return a
	 * maximum array length.
	 *
	 * @param str  the String to parse, may be {@code null}
	 * @param separatorChar the separate character
	 * @return an array of parsed Strings, {@code null} if null String input
	 */
	public static String[] split(final String str, final char separatorChar) {
		// Performance tuned for 2.0 (JDK1.4)

		if (str == null) {
			return null;
		}
		final int len = str.length();
		if (len == 0) {
			return EMPTY_STRING_ARRAY;
		}
		final List<String> list = new ArrayList<>();
		int i = 0, start = 0;
		boolean match = false;
		while (i < len) {
			if (str.charAt(i) == separatorChar) {
				if (match) {
					list.add(str.substring(start, i));
					match = false;
				}
				start = ++i;
				continue;
			}
			match = true;
			i++;
		}
		if (match) {
			list.add(str.substring(start, i));
		}
		return list.toArray(new String[list.size()]);
	}

	/**
	 * Performs the logic for the {@code split} and
	 * {@code splitPreserveAllTokens} methods that return a maximum array
	 * length.
	 *
	 * @param str  the String to parse, may be {@code null}
	 * @param separatorChars the separate character
	 * @return an array of parsed Strings, {@code null} if null String input
	 */
	public static String[] split(final String str, final String separatorChars) {
		// Performance tuned for 2.0 (JDK1.4)
		// Direct code is quicker than StringTokenizer.
		// Also, StringTokenizer uses isSpace() not isWhitespace()

		if (str == null) {
			return null;
		}
		final int len = str.length();
		if (len == 0) {
			return EMPTY_STRING_ARRAY;
		}
		final List<String> list = new ArrayList<>();
		int sizePlus1 = 1;
		int i = 0, start = 0;
		boolean match = false;

		final int max = -1;

		if (separatorChars == null) {
			// Null separator means use whitespace
			while (i < len) {
				if (Character.isWhitespace(str.charAt(i))) {
					if (match) {
						if (sizePlus1++ == max) {
							i = len;
						}
						list.add(str.substring(start, i));
						match = false;
					}
					start = ++i;
					continue;
				}
				match = true;
				i++;
			}
		} else if (separatorChars.length() == 1) {
			// Optimise 1 character case
			final char sep = separatorChars.charAt(0);
			while (i < len) {
				if (str.charAt(i) == sep) {
					if (match) {
						if (sizePlus1++ == max) {
							i = len;
						}
						list.add(str.substring(start, i));
						match = false;
					}
					start = ++i;
					continue;
				}
				match = true;
				i++;
			}
		} else {
			// standard case
			while (i < len) {
				if (separatorChars.indexOf(str.charAt(i)) >= 0) {
					if (match) {
						if (sizePlus1++ == max) {
							i = len;
						}
						list.add(str.substring(start, i));
						match = false;
					}
					start = ++i;
					continue;
				}
				match = true;
				i++;
			}
		}
		if (match) {
			list.add(str.substring(start, i));
		}
		return list.toArray(new String[list.size()]);
	}


	/**
	 * @author chan
	 * @param obj 需要转换的对象
	 * @return string
	 **/
	public static String getString(Object obj) {
		if (obj == null) {
			return EMPTY;
		} else {
			return obj.toString();
		}
	}

	/**
	 * @author chan 异常栈字符串输出
	 * @param throwable ex
	 * @return String
	 **/
	public static String getErrorText(Throwable throwable) {
		if (throwable == null) {
			return "ERROR,throwable is NULL!";
		}

		try (StringWriter strWriter = new StringWriter(512); PrintWriter writer = new PrintWriter(strWriter)) {
			throwable.printStackTrace(writer);
			StringBuffer sb = strWriter.getBuffer();
			return sb.toString();
		} catch (Exception ex) {
			return "ERROR!";
		}
	}

	public static String getErrorMsg(Throwable throwable) {
		if (throwable == null) {
			return "ERROR,throwable is NULL!";
		}
		String message = throwable.getMessage();
		return message;
	}

	/**
	 * 随即获取length随机字符串
	 */
	public static void getRandomStr(StringBuilder sb, Integer length) {
		Integer baseLength = randomStr.length();
		for (int i = 0; i < length; i++) {
			int number = RandomUtil.randomInt(baseLength);
			sb.append(randomStr.charAt(number));
		}
	}

	public static String randomString(Integer length) {
		if (length == null || length < 0) {
			length = 6;
		}
		Integer baseLength = baseStr.length();
		StringBuilder sb = new StringBuilder(length);
		for (int i = 0; i < length; i++) {
			int number = RandomUtil.randomInt(baseLength);
			sb.append(baseStr.charAt(number));
		}
		return sb.toString();
	}

	/**
	 * 生成4位\6位随机数
	 */
	public static String getRandomCodeStr(Integer digit) {
		if (null == digit) {
			digit = 6;
		}
		String code;
		// 生成验证码
		if (digit == 4) {
			code = new Random().nextInt(8999) + 1000 + "";
		} else {
			code = new Random().nextInt(899999) + 100000 + "";
		}
		return code;
	}

	/**
	 * 姓名匿名
	 */
	public static String anonymity(char[] name) {
		final int size = name.length;

		char[] chars = new char[3];
		if (size == 1 || size == 2) {
			chars[0] = '*';
			chars[1] = ' ';
			chars[2] = name[size - 1];
		} else {
			chars[0] = '*';
			chars[1] = name[size - 2];
			chars[2] = name[size - 1];
		}
		return new String(chars);
	}

	public static String getRealAddressByIP(String ip) {
		if (StrUtil.isBlank(ip)) {
			return "unknown";
		}
		try {
			String url = String.format("http://whois.pconline.com.cn/ipJson.jsp?ip=%s" + "&json=true", ip);
			HttpResponse<String> send = J11HttpC
					.custom().ofGet().url(url)
					.send(HttpResponse.BodyHandlers.ofString());
			String rspStr = send.body();
			if (StrUtil.isEmpty(rspStr)) {
				return "unknown";
			}
			JSONObject obj = JSONUtil.parseObj(rspStr);
			String region = obj.getStr("pro");
			String city = obj.getStr("city");
			return String.format("%s %s", region, city);
		} catch (Exception e) {
		}

		return "unknown";
	}

	public static String getInternalAddressByIP(String ip) {
		if (StrUtil.isBlank(ip)) {
			return "unknown";
		}
		try {
			String url = String.format("http://whois.pconline.com.cn/ipJson.jsp?ip=%s" + "&json=true", ip);
			HttpResponse<String> send = J11HttpC
					.custom().ofGet().url(url)
					.send(HttpResponse.BodyHandlers.ofString());
			String rspStr = send.body();
			if (StrUtil.isEmpty(rspStr)) {
				return "unknown";
			}
			JSONObject obj = JSONUtil.parseObj(rspStr);
			String addr = obj.getStr("addr");
			return addr;
		} catch (Exception e) {
		}

		return "unknown";
	}

	/**
	 * 身份证号返回
	 */
	public static String enIndent(String ident) {
		String front = ident.substring(0, 4);
		String back = ident.substring(ident.length() - 4);
		StringBuilder sb = new StringBuilder();
		sb.append(front).append("****").append(back);
		return sb.toString();
	}

	public static String getNewInternalAddressByIp(String ip) {
		if (StrUtil.isBlank(ip)) {
			return "unknown";
		}
		try {
			String url = String.format("https://searchplugin.csdn.net/api/v1/ip/get?ip=%s", ip);
			HttpResponse<String> send = J11HttpC
					.custom().ofGet().url(url)
					.send(HttpResponse.BodyHandlers.ofString());
			String rspStr = send.body();
			if (StrUtil.isEmpty(rspStr)) {
				return "unknown";
			}
			JSONObject obj = JSONUtil.parseObj(rspStr);
			JSONObject data = JSONUtil.parseObj(obj.get("data"));
			String addr = data.getStr("address");
			return addr;
		} catch (Exception e) {
		}

		return "unknown";
	}

	/**
	 * 获取账号登录验证码
	 */
	public static String getVerifyCodePattern(String code, Integer length) {
		if (StrUtil.isNotEmpty(code)) {
			Pattern pattern = Pattern.compile(String.format("\\d{%s,}", length));
			Matcher matcher = pattern.matcher(code);
			while (matcher.find()) {
				return matcher.group();
			}
		}
		return StrUtil.EMPTY;
	}

	public static <T> List<List<T>> averageAssign(List<T> source, int n) {
		List<List<T>> result = new ArrayList<List<T>>();
		int remainder = source.size() % n;  //(先计算出余数)
		int number = source.size() / n;  //然后是商
		int offset = 0;//偏移量
		for (int i = 0; i < n; i++) {
			List<T> value = null;
			if (remainder > 0) {
				value = source.subList(i * number + offset, (i + 1) * number + offset + 1);
				remainder--;
				offset++;
			} else {
				value = source.subList(i * number + offset, (i + 1) * number + offset);
			}
			result.add(value);
		}
		return result;
	}

	/**
	 * 付费ip地址查询
	 */
	public static String paidIpAddress(String ip) {
		cn.hutool.http.HttpResponse execute = HttpUtil.createGet(String.format("https://ipaddr.market.alicloudapi.com/ip_addr_search/v1?IP_ADDR=%s", ip))
				.header("Authorization", PAID_IP_APP_CODE_AUTH)
				.header("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
				.execute();

		try {
			ArkIpAddressDto arkIpAddressDto = Jsons.parseObject(execute.body(), ArkIpAddressDto.class);
			ArkIpAddressDto.Entity.InputIpAddress inputIpAddress = arkIpAddressDto.getEntity().getInputIpAddress();
			return new StringBuilder()
					.append(inputIpAddress.getNation())
					.append(inputIpAddress.getProvince())
					.append(inputIpAddress.getCity()).toString();
		} catch (Exception e) {
		}
		return "unknown";
	}

	/**
	 * 姓名返回
	 */
	public static String enName(String name) {
		String first = name.substring(0, 1);
		if (name.length() <= 2) {
			return first + "*";
		}
		String end = name.substring(name.length() - 1);
		StringBuilder sb = new StringBuilder();
		sb.append(first).append("*").append(end);
		return sb.toString();
	}
}
