/*
 * Copyright (c) 2013-2016 Chris Newland.
 * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
 * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
 */
package org.adoptopenjdk.jitwatch.util;

import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_DOT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_DOUBLE_QUOTE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_EQUALS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_QUOTE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_SPACE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_APOSTROPHE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_CLOSE_ANGLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_COLON;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DOT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_EMPTY;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_ENTITY_APOS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_ENTITY_GT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_ENTITY_LT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_ESCAPED_DOT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_OPEN_ANGLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_SPACE;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class StringUtil
{
	private static final DecimalFormat DF_THOUSANDS = new DecimalFormat("#,###");

	private StringUtil()
	{
	}

	public static String formatTimestamp(long stamp, boolean showMillis)
	{
		long stampCopy = stamp;

		long dayMillis = 24L * 60L * 60_000L;
		long hourMillis = 60L * 60_000L;
		long minuteMillis = 60_000L;
		long secondMillis = 1_000L;

		long days = (long) Math.floor(stampCopy / dayMillis);
		stampCopy -= days * dayMillis;

		long hours = (long) Math.floor(stampCopy / hourMillis);
		stampCopy -= hours * hourMillis;

		long minutes = (long) Math.floor(stampCopy / minuteMillis);
		stampCopy -= minutes * minuteMillis;

		long seconds = (long) Math.floor(stampCopy / secondMillis);
		stampCopy -= seconds * secondMillis;

		long millis = stampCopy;

		StringBuilder sb = new StringBuilder();

		if (days > 0)
		{
			sb.append(days).append("d ");
		}

		sb.append(padZero(hours, 2)).append(S_COLON);
		sb.append(padZero(minutes, 2)).append(S_COLON);
		sb.append(padZero(seconds, 2));

		if (showMillis)
		{
			sb.append(S_DOT).append(padZero(millis, 3));
		}

		return sb.toString();
	}

	public static String replaceXMLEntities(String input)
	{
		String result = null;

		if (input != null)
		{
			result = input.replace(S_ENTITY_LT, S_OPEN_ANGLE).replace(S_ENTITY_GT, S_CLOSE_ANGLE).replace(S_ENTITY_APOS,
					S_APOSTROPHE);
		}

		return result;
	}

	public static String repeat(char c, int count)
	{
		StringBuilder builder = new StringBuilder();

		for (int i = 0; i < count; i++)
		{
			builder.append(c);
		}

		return builder.toString();
	}

	public static String rtrim(String string)
	{
		return string.replaceAll("\\s+$", "");
	}

	public static String alignRight(long num, int width)
	{
		return pad(Long.toString(num), width, C_SPACE, true);
	}

	public static String alignRight(String str, int width)
	{
		return pad(str, width, C_SPACE, true);
	}

	public static String alignLeft(long num, int width)
	{
		return pad(Long.toString(num), width, C_SPACE, false);
	}

	public static String alignLeft(String str, int width)
	{
		return pad(str, width, C_SPACE, false);
	}

	public static String padZero(long num, int width)
	{
		return pad(Long.toString(num), width, '0', true);
	}

	public static String pad(String str, int width, char padding, boolean left)
	{
		StringBuilder sb = new StringBuilder();

		if (str != null)
		{
			int len = str.length();

			if (!left)
			{
				sb.append(str);
			}

			if (len < width)
			{
				for (int i = 0; i < width - len; i++)
				{
					sb.append(padding);
				}
			}

			if (left)
			{
				sb.append(str);
			}
		}

		return sb.toString();
	}

	public static List<String> textToList(String text, String split)
	{
		List<String> result = new ArrayList<>();

		if (text != null)
		{
			String[] lines = text.split(split);

			for (String line : lines)
			{
				if (line.trim().length() > 0)
				{
					result.add(line);
				}
			}
		}

		return result;
	}

	public static String listToText(List<String> list, String join)
	{
		StringBuilder builder = new StringBuilder();

		for (String line : list)
		{
			builder.append(line).append(join);
		}

		if (builder.length() > 0)
		{
			builder.deleteCharAt(builder.length() - 1);
		}

		return builder.toString();
	}

	public static String getSubstringBetween(String input, String start, String end)
	{
		int startPos = input.indexOf(start);

		String result = null;

		if (startPos != -1)
		{
			int endPos = input.indexOf(end, startPos + start.length());

			if (endPos != -1)
			{
				result = input.substring(startPos + start.length(), endPos);
			}
		}

		return result;
	}

	public static String getUnqualifiedClassName(String fqClassName)
	{
		int lastDot = fqClassName.lastIndexOf('.');

		String result = fqClassName;

		if (lastDot != -1)
		{
			result = fqClassName.substring(lastDot + 1);
		}

		return result;
	}

	public static String getUnqualifiedMemberName(String memberName)
	{
		return getUnqualifiedClassName(memberName);
	}

	public static String getPackageName(String fqClassName)
	{
		int lastDot = fqClassName.lastIndexOf('.');

		String result = S_EMPTY;

		if (lastDot != -1)
		{
			result = fqClassName.substring(0, lastDot);
		}

		return result;
	}

	public static String padLineNumber(int number, int maxWidth)
	{
		return alignRight(Integer.toString(number), maxWidth);
	}

	public static String attributeMapToString(Map<String, String> map)
	{
		return attributeMapToString(map, C_QUOTE);
	}

	public static String attributeMapToString(Map<String, String> map, char quote)
	{
		StringBuilder builder = new StringBuilder();

		for (Map.Entry<String, String> entry : map.entrySet())
		{
			builder.append(entry.getKey()).append(C_EQUALS).append(quote).append(entry.getValue()).append(quote).append(C_SPACE);
		}

		return builder.toString().trim();
	}

	public static Map<String, String> attributeStringToMap(String line)
	{
		Map<String, String> result = new HashMap<>();

		if (line != null)
		{
			int len = line.length();

			StringBuilder key = new StringBuilder();
			StringBuilder val = new StringBuilder();

			boolean inValue = false;

			for (int i = 0; i < len; i++)
			{
				char c = line.charAt(i);

				switch (c)
				{
				case C_SPACE:
					if (!inValue)
					{
						// C_SPACE before new key
						key.delete(0, key.length());
					}
					else
					{
						val.append(C_SPACE);
					}
					break;
				case C_QUOTE:
					if (inValue)
					{
						// finished attr
						result.put(key.toString(), val.toString());
						key.delete(0, key.length());
						val.delete(0, val.length());
						inValue = false;
					}
					else
					{
						inValue = true;
					}
					break;
				case C_EQUALS:
					if (inValue)
					{
						val.append(C_EQUALS);
					}
					break;
				default:
					if (inValue)
					{
						val.append(c);
					}
					else
					{
						key.append(c);
					}
				}
			}
		}

		return result;
	}

	public static Map<String, String> getLineAttributesDoubleQuote(String line)
	{
		Map<String, String> result = new HashMap<>();

		if (line != null)
		{
			int len = line.length();

			StringBuilder key = new StringBuilder();
			StringBuilder val = new StringBuilder();

			boolean inValue = false;

			for (int i = 0; i < len; i++)
			{
				char c = line.charAt(i);

				switch (c)
				{
				case C_SPACE:
					if (!inValue)
					{
						// C_SPACE before new key
						key.delete(0, key.length());
					}
					else
					{
						val.append(C_SPACE);
					}
					break;
				case C_DOUBLE_QUOTE:
					if (inValue)
					{
						// finished attr
						result.put(key.toString(), val.toString());
						key.delete(0, key.length());
						val.delete(0, val.length());
						inValue = false;
					}
					else
					{
						inValue = true;
					}
					break;
				case C_EQUALS:
					if (inValue)
					{
						val.append(C_EQUALS);
					}
					break;
				default:
					if (inValue)
					{
						val.append(c);
					}
					else
					{
						key.append(c);
					}
				}
			}
		}

		return result;
	}

	public static String formatThousands(String inValue)
	{
		String value = inValue;
		// see if it can be formatted as a long with commas at thousands
		try
		{
			value = DF_THOUSANDS.format(Long.parseLong(value));
		}
		catch (NumberFormatException nfe)
		{
		}

		return value;
	}

	public static String arrayToString(Object[] array)
	{
		return listToString(Arrays.asList(array), C_SPACE);
	}

	public static String listToString(List<?> list)
	{
		return listToString(list, C_SPACE);
	}

	public static String listToString(List<?> list, char separator)
	{
		StringBuilder builder = new StringBuilder();

		if (list != null)
		{
			for (Object item : list)
			{
				builder.append(item.toString()).append(separator);
			}

			if (builder.length() > 0)
			{
				builder.deleteCharAt(builder.length() - 1);
			}
		}

		return builder.toString();
	}

	public static String wordWrap(String text, int width)
	{
		StringBuilder builder = new StringBuilder(text);

		int i = 0;

		while (i + width < builder.length() && (i = builder.lastIndexOf(S_SPACE, i + width)) != -1)
		{
			builder.replace(i, i + 1, S_NEWLINE);
		}

		return builder.toString();
	}

	public static String getAbbreviatedFQName(String fqClassName)
	{
		StringBuilder builder = new StringBuilder();

		if (fqClassName != null && fqClassName.length() > 0)
		{
			String[] parts = fqClassName.split(S_ESCAPED_DOT);

			for (int i = 0; i < parts.length - 1; i++)
			{
				String part = parts[i];

				builder.append(part.charAt(0)).append(C_DOT);
			}

			builder.append(parts[parts.length - 1]);
		}

		return builder.toString();
	}

	public static List<String> getSortedKeys(Map<String, ?> map)
	{
		List<String> attrList = new ArrayList<>(map.keySet());
		Collections.sort(attrList);

		return attrList;
	}
}
