package edu.berkeley.nlp.util;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StrUtils {
	public static String[] split(String s) {
		// Differs from Java's functions in that it returns String[0] on ""
		return split(s, " ");
	}

	public static String[] split(String s, String delim) {
		return isEmpty(s) ? new String[0] : s.split(delim);
	}

	// delim is treated as a string, rather than a list of delimiters
	public static List<String> splitByStr(String s, String delim) {
		if (isEmpty(s))
			return Collections.emptyList();
		ArrayList<String> tokens = new ArrayList<String>();
		int i = 0;
		while (i < s.length()) {
			int j = s.indexOf(delim, i);
			if (j == -1)
				break;
			tokens.add(s.substring(i, j));
			i = j + delim.length();
		}
		tokens.add(s.substring(i));
		return tokens;
	}

	// Return the first occurrence of c that doesn't have an escape character in
	// front of it starting at position i
	public static int indexOfIgnoreEscaped(String s, char c) {
		return indexOfIgnoreEscaped(s, c, 0);
	}

	public static int indexOfIgnoreEscaped(String s, char c, int i) {
		return indexOfIgnoreEscaped(s, "" + c, i);
	}

	// Find first occurrence of some non-escaped character in cs
	public static int indexOfIgnoreEscaped(String s, String cs, int i) {
		boolean escape = false;
		while (i < s.length()) {
			if (escape)
				escape = false;
			else {
				if (s.charAt(i) == '\\') // Enable escaping
					escape = true;
				else if (cs.indexOf(s.charAt(i)) != -1)
					return i;
			}
			i++;
		}
		return -1;
	}

	// Split, but mind the escaped delimiters.
	// Example: "a\ b c" => "a\ b" "c"
	public static List<String> splitIgnoreEscaped(String line, String delim) {
		// Split
		String[] tokens = StrUtils.split(line, delim);
		// But now, have to piece together the escaped delimiters
		List<String> newTokens = new ArrayList<String>();
		for (int i = 0; i < tokens.length; i++) {
			if (tokens[i].endsWith("\\") && i + 1 < tokens.length)
				tokens[i + 1] = tokens[i].substring(0, tokens[i].length() - 1)
						+ "\\" + delim + tokens[i + 1];
			else
				newTokens.add(tokens[i]);
		}
		return newTokens;
	}

	public static double[] doubleSplit(String s, String delim) {
		String[] tokens = split(s, delim);
		double[] data = new double[tokens.length];
		for (int i = 0; i < tokens.length; i++)
			data[i] = Double.parseDouble(tokens[i]);
		return data;
	}

	public static double[] doubleSplit(String s) {
		return doubleSplit(s, " ");
	}

	public static int[] intSplit(String s, String delim) {
		String[] tokens = split(s, delim);
		int[] data = new int[tokens.length];
		for (int i = 0; i < tokens.length; i++)
			data[i] = Integer.parseInt(tokens[i]);
		return data;
	}

	public static int[] intSplit(String s) {
		return intSplit(s, " ");
	}

	public static short[] shortSplit(String s, String delim) {
		String[] tokens = split(s, delim);
		short[] data = new short[tokens.length];
		for (int i = 0; i < tokens.length; i++)
			data[i] = Short.parseShort(tokens[i]);
		return data;
	}

	public static short[] shortSplit(String s) {
		return shortSplit(s, " ");
	}

	public static List<Integer> intSplitList(String s, String delim) {
		String[] tokens = split(s, delim);
		ArrayList<Integer> data = new ArrayList<Integer>(tokens.length);
		for (int i = 0; i < tokens.length; i++)
			data.add(Integer.parseInt(tokens[i]));
		return data;
	}

	public static List<Integer> intSplitList(String s) {
		return intSplitList(s, " ");
	}

	// Example: a=3 b=4
	public static Map<String, String> parseHashMap(String line,
			String keyValueDelim) {
		return parseHashMap(line, keyValueDelim, " ");
	}

	public static Map<String, String> parseHashMap(String line,
			String keyValueDelim, String entryDelim) {
		return parseHashMap(Arrays.asList(StrUtils.split(line, entryDelim)),
				keyValueDelim);
	}

	public static Map<String, String> parseHashMap(List<String> tokens,
			String keyValueDelim) {
		Map<String, String> map = new HashMap<String, String>();
		for (String token : tokens) {
			String[] kv = token.split(keyValueDelim);
			if (kv.length != 2)
				continue; // Be silent?
			map.put(kv[0], kv[1]);
		}
		return map;
	}

	// public static TDoubleMap<String> parseTDoubleMap(String line, String
	// keyValueDelim) {
	// return parseTDoubleMap(line, keyValueDelim, " ");
	// }
	// public static TDoubleMap<String> parseTDoubleMap(String line, String
	// keyValueDelim, String entryDelim) {
	// TDoubleMap<String> map = new TDoubleMap<String>();
	// String[] tokens = line.split(entryDelim);
	// for(String token : tokens) {
	// String[] kv = token.split(keyValueDelim);
	// if(kv.length != 2) continue; // Be silent?
	// map.put(kv[0], Double.parseDouble(kv[1]));
	// }
	// return map;
	// }

	public static <T> String join(double[] list) {
		return join(list, " ");
	}

	public static <T> String join(double[] list, String delim) {
		if (list == null)
			return "";
		List<Double> objs = new ArrayList<Double>();
		for (double x : list)
			objs.add(x);
		return join(objs, delim);
	}

	public static String join(int[] list) {
		return join(list, " ");
	}

	public static String join(int[] list, String delim) {
		if (list == null)
			return "";
		List<Integer> objs = new ArrayList<Integer>();
		for (int x : list)
			objs.add(x);
		return join(objs, delim);
	}

	public static String join(boolean[] list) {
		return join(list, " ");
	}

	public static String join(boolean[] list, String delim) {
		if (list == null)
			return "";
		List<Boolean> objs = new ArrayList<Boolean>();
		for (boolean x : list)
			objs.add(x);
		return join(objs, delim);
	}

	public static <T> String join(T[] objs) {
		if (objs == null)
			return "";
		return join(Arrays.asList(objs), " ");
	}

	public static <T> String join(T[] objs, int start, int end) {
		if (objs == null)
			return "";
		return join(Arrays.asList(objs), " ", start, end);
	}

	public static <T> String join(List<T> objs) {
		return join(objs, " ");
	}

	public static <T> String join(T[] objs, String delim) {
		if (objs == null)
			return "";
		return join(Arrays.asList(objs), delim);
	}

	public static <T> String join(List<T> objs, String delim) {
		if (objs == null)
			return "";
		return join(objs, delim, 0, objs.size());
	}

	public static <T> String join(List<T> objs, String delim, int start, int end) {
		if (objs == null)
			return "";
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for (int i = start; i < end; i++) {
			if (!first)
				sb.append(delim);
			sb.append(objs.get(i));
			first = false;
		}
		return sb.toString();
	}

	public static <T> String join(Collection<T> objs, String delim) {
		if (objs == null)
			return "";
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for (T x : objs) {
			if (!first)
				sb.append(delim);
			sb.append(x);
			first = false;
		}
		return sb.toString();
	}

	public static String join(int[] x, boolean withIndices,
			int magnitudeThreshold) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < x.length; i++) {
			if (Math.abs(x[i]) < magnitudeThreshold)
				continue;
			if (i > 0)
				sb.append(' ');
			if (withIndices)
				sb.append(i + ":");
			sb.append(x[i]);
		}
		return sb.toString();
	}

	public static String joinWithIndices(int[] x) {
		return join(x, true, 0);
	}

	public static String join(double[] x, boolean withIndices,
			double magnitudeThreshold) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < x.length; i++) {
			if (Math.abs(x[i]) < magnitudeThreshold)
				continue;
			if (i > 0)
				sb.append(' ');
			if (withIndices)
				sb.append(i + ":");
			sb.append(x[i]);
		}
		return sb.toString();
	}

	public static String joinWithIndices(double[] x) {
		return join(x, true, 0);
	}

	// Format a matrix with spaces and newlines
	public static <T> String join(T[][] data, String[] rowNames,
			String[] colNames) {
		int r0 = (colNames == null ? 0 : 1);
		int c0 = (rowNames == null ? 0 : 1);
		int R = data.length;
		int C = data[0].length;
		String[][] mat = new String[R + r0][C + r0];
		for (int r = 0; r < R; r++)
			for (int c = 0; c < C; c++)
				mat[r + r0][c + c0] = "" + data[r][c];
		if (rowNames != null)
			for (int r = 0; r < R; r++)
				mat[r + r0][0] = rowNames[r];
		if (colNames != null)
			for (int c = 0; c < C; c++)
				mat[0][c + c0] = colNames[c];
		int[] widths = new int[C + c0];
		for (int c = 0; c < C + c0; c++) {
			for (int r = 0; r < R + r0; r++) {
				if (mat[r][c] == null)
					mat[r][c] = "";
				widths[c] = Math.max(widths[c], mat[r][c].length());
			}
		}
		StringBuilder buf = new StringBuilder();
		for (int r = 0; r < R + r0; r++) {
			for (int c = 0; c < C + c0; c++) {
				String fmt = "%-" + (widths[c] + 1) + "s";
				buf.append(String.format(fmt, mat[r][c]));
			}
			buf.append("\n");
		}
		return buf.toString();
	}

	public static String toString(Object o) {
		return o == null ? null : o.toString();
	}

	public static boolean isEmpty(String s) {
		return s == null || s.equals("");
	}

	public static String repeat(String s, int n) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < n; i++)
			sb.append(s);
		return sb.toString();
	}

	// Regular expression
	public static Matcher match(String pattern, String s) {
		return Pattern.compile(pattern).matcher(s);
	}

	public static String[] format(String fmt, Object... is) {
		int n = Array.getLength(is[0]);
		String[] out = new String[n];
		for (int i = 0; i < n; i++) {
			Object[] args = new Object[is.length];
			for (int j = 0; j < is.length; j++)
				args[j] = Array.get(is[j], i);
			out[i] = String.format(fmt, args);
		}
		return out;
	}
}
