package common.util;

import java.awt.GraphicsEnvironment;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import javax.swing.JOptionPane;

import common.collect.Lists;
import common.collect.Maps;
import common.log.Log;

public abstract class Util {
	public static final boolean WINDOWS = System.getProperty("os.name").startsWith("Windows");
    private static final long START = getTime();
    
	public static String strip(String str, int offset, int len, char newl, char tab, char unk) {
		StringBuilder sb = new StringBuilder();
		for(int pos = offset; pos < offset + len; pos++) {
			char c = str.charAt(pos);
			if(c == '\n') {
				if(newl == 0)
					return sb.toString();
				sb.append(newl);
			}
			else if((c == '\t') && tab != 0) {
				for(int z = 0; z < 4; z++)
					sb.append(tab);
			}
			else if(c == Log.CHR_UNK) {
				if(unk != 0)
					sb.append(unk);
			}
			else if(c >= Log.CHR_SPC && c <= 0xff) {
				sb.append(c);
			}
		}
		return sb.toString();
	}

/*
uint utf_read(const char **ptr) {
	uint ch;
	byte utf;
	char c = *((*ptr)++);
	for(utf = 0; (utf < 6) && (c & (0x80 >> utf)); utf++) {
		;
	}
	if(utf == 1)
		return CHR_UNK;
	for(ch = ((!utf) || ((((uint)c) << 6) | (((**ptr) & 0x3f) & ~(0xff >> utf)))) ? (c & (0x7f >> utf)) : 0; utf > 1; utf--) {
		if(((c = **ptr) & 0xc0) == 0x80) {
			// if(ch) {
			ch <<= 6;
			ch |= c & 0x3f;
			// }
		}
		else {
			return c ? CHR_UNK : 0;
		}
		(*ptr)++;
	}
	// fprintf(stderr, "%d / %c\n", ch, ch);
	return (utf && !ch) ? CHR_UNK : ch;
}

uint utf_readn(const char *ptr, int *pos) {
	const char *str = &ptr[*pos];
	uint ch = utf_read(&str);
	*pos = (int)(str-ptr);
	return ch;
}

byte utf_write(char **ptr, int *len, uint ch) {
	uint test;
	uint mask = 0xffffff80;
	char utf;
	char c;
	if(ch & 0x80000000) {
		return 1;
	}
	for(utf = 0; ch & mask & ~(test = (0xffffffff << ((utf + 1) * 6 + (5 - utf)))); utf++) {
		mask &= test;
	}
	// fprintf(stderr, "%d\n", utf);
	if(utf + 1 >= (*len)) {
		return 0;
	}
	(*len) -= utf + 1;
	*((*ptr)++) = utf ? (~(0x7f >> utf) | (((uint)(ch >> (utf * 6))) & (0x3f >> utf))) : ch;
	for(--utf; utf >= 0; utf--) {
		*((*ptr)++) = 0x80 | (((uint)(ch >> (utf * 6))) & 0x3f);
	}
	return 1;
}

byte utf_rwriten(char *ptr, int *pos, int len, uint ch) {
	char *str = &ptr[*pos];
	len -= (*pos);
	byte res = utf_write(&str, &len, ch);
	*pos = (int)(str-ptr);
	return res;
}

uint utf_rread(const char **ptr, const char *start) {
	const char *tp = *ptr;
	uint ch;
	char c;
	do {
		if(tp == start)
			return 0;
		tp--;
	} while(((c = (*tp)) & 0xc0) == 0x80);
	*ptr = tp;
	return (ch = utf_read(&tp)) ? ch : CHR_UNK;
}

uint utf_rreadn(const char *ptr, int *pos) {
	const char *str = &ptr[*pos];
	uint ch = utf_rread(&str, ptr);
	*pos = (int)(str-ptr);
	return ch;
}

int utf_len(const char *str) {
	int len;
	for(len = 0; utf_read(&str); len++) {
		;
	}
	return len;
}
*/

	public static int compareLower(String str1, String str2) {
		if(str2.length() > str1.length())
			return 0;
		for(int z = 0; z < str2.length(); z++) {
			if(Character.toLowerCase(str1.charAt(z)) != Character.toLowerCase(str2.charAt(z)))
				return 0;
		}
		return str2.length() == str1.length() ? 0x7fffffff : str2.length();
	}
	
	public static Integer parseInt(String str, int base) {
		char c;
		boolean tbase = false;
		int digits = 0;
		long v = 0;
		long nbase = base != 0 ? (base < 0 ? -base : base) : 10;
		long nsign = 1;
		for(int pos = 0; pos < str.length(); pos++) {
			c = Character.toLowerCase(str.charAt(pos));
			if(pos == 0 && ((c == '+') || ((c == '-') && (base >= 0))))
				nsign = (c == '+') ? 1 : -1;
			else if(pos == 0 && c == '0') {
				tbase = true;
				digits++;
			}
			else if(tbase && pos == 1 && c == 'x') {
				nbase = 16;
				digits--;
			}
			else if(((nbase == 16) && (c >= 'a' && c <= 'f')) || (c >= '0' && c <= '9')) {
				v *= nbase;
				v += ((long)((c >= '0' && c <= '9') ? (c - '0') : (10 + (c - 'a'))));
				digits++;
			}
			else
				return null;
		}
		if(digits == 0)
			return null;
		v *= nsign;
		if((base < 0) ? (v < 0L || v > 0xffffffffL) : (v < -0x80000000L || v > 0x7fffffffL)) {
			return null;
		}
		return (int)((base < 0) ? (v & 0xffffffff) : (((v >> 32) & 0x80000000) | (v & 0x7fffffff)));
	}
	
	public static <T extends Enum> T parseEnum(Class<T> clazz, String str) {
		boolean name = Identifyable.class.isAssignableFrom(clazz);
		T[] values = clazz.getEnumConstants();
		Integer value;
		if((value = parseInt(str, 0)) != null && (value >= 0) && (value < values.length)) {
			return values[value];
		}
		int comp;
		int max = 0;
		T best = null;
		for(int z = 0; z < values.length; z++) {
			if((comp = compareLower(name ? ((Identifyable)values[z]).getName() : values[z].toString(), str)) > max) {
				max = comp;
				best = values[z];
			}
		}
		return best;
	}
	
	public static <T> T parseEnum(Class<T> base, String str, Class<? extends Enum> ... enums) {
		int comp;
		int max = 0;
		T best = null;
		for(Class<? extends Enum> clazz : enums) {
			if(!base.isAssignableFrom(clazz))
				throw new IllegalArgumentException("Klasse " + clazz.getSimpleName() + " ist nicht " + base.getSimpleName() + " untergeordnet");
			boolean name = Identifyable.class.isAssignableFrom(clazz);
			Enum<?>[] values = clazz.getEnumConstants();
			for(int z = 0; z < values.length; z++) {
				if((comp = compareLower(name ? ((Identifyable)values[z]).getName() : values[z].toString(), str)) > max) {
					max = comp;
					best = (T)values[z];
				}
			}
		}
		return best;
	}
	
	public static <T> int indexOf(T[] array, T elem) {
		for(int z = 0; z < array.length; z++) {
			if(array[z] == elem)
				return z;
		}
		return -1;
	}
	
	public static <T> int indexOfChecked(T[] array, T elem) {
		for(int z = 0; z < array.length; z++) {
			if(array[z] == elem)
				return z;
		}
		throw new IllegalArgumentException("Objekt ist nicht in Array");
	}
	
	public static Boolean parseBoolean(String str) {
		if("1".equals(str) || "true".equalsIgnoreCase(str) || "on".equalsIgnoreCase(str) || "yes".equalsIgnoreCase(str) || "y".equalsIgnoreCase(str))
			return true;
		else if("0".equals(str) || "false".equalsIgnoreCase(str) || "off".equalsIgnoreCase(str) || "no".equalsIgnoreCase(str) || "n".equalsIgnoreCase(str))
			return false;
		return null;
	}
	
	public static <T> String buildLines(String separator, Function<T, String> func, Iterable<T> elems) {
		StringBuilder sb = new StringBuilder();
		for(T elem : elems) {
			if(sb.length() > 0)
				sb.append(separator);
			sb.append(func.apply(elem));
		}
		return sb.toString();
	}

	public static <T> String buildLines(Function<T, String> func, Iterable<T> elems) {
		return buildLines("\n", func, elems);
	}
	
	public static <T> String buildLines(String separator, Iterable<T> elems) {
		return buildLines(separator, new Function<T, String>() {
			public String apply(T t) {
				return String.valueOf(t);
			}
		}, elems);
	}
	
	public static <T> String buildLines(Iterable<T> elems) {
		return buildLines("\n", elems);
	}
	
	public static <T> String buildLines(String separator, Function<T, String> func, T ... elems) {
		StringBuilder sb = new StringBuilder();
		for(T elem : elems) {
			if(sb.length() > 0)
				sb.append(separator);
			sb.append(func.apply(elem));
		}
		return sb.toString();
	}

	public static <T> String buildLines(Function<T, String> func, T ... elems) {
		return buildLines("\n", func, elems);
	}
	
	public static <T> String buildLines(String separator, T ... elems) {
		return buildLines(separator, new Function<T, String>() {
			public String apply(T t) {
				return String.valueOf(t);
			}
		}, elems);
	}
	
	public static <T> String buildLines(T ... elems) {
		return buildLines("\n", elems);
	}

	public static int mixColor(int c1, int c2) {
		return ((((c1 >> 24 & 255) + (c2 >> 24 & 255)) / 2) << 24) | ((((c1 >> 16 & 255) + (c2 >> 16 & 255)) / 2) << 16) | ((((c1 >> 8 & 255) + (c2 >> 8 & 255)) / 2) << 8) | 
				(((c1 & 255) + (c2 & 255)) / 2);
	}

	public static int mulColor(int color, float brightness) {
		int mul = ExtMath.clampi((int)(brightness * 255.0f), 0, 255);
		return (color & 0xff000000) | ((((color >> 16 & 255) * mul) / 255) << 16) | ((((color >> 8 & 255) * mul) / 255) << 8) | 
				(((color & 255) * mul) / 255);
	}
	
	public static void checkOs() {
		if(System.getProperty("os.name").startsWith("Mac")) {
			String info = "Inkompatibles Betriebssystem";
			String msg = "Linux, *BSD oder Windows ist erforderlich, um dieses Programm\n" + 
					"auszuführen. Alle Versionen von Mac OS (X) sind nicht kompatibel.";
			System.err.println("#################################################################");
			System.err.println("*** " + info + " ***");
			System.err.println(msg);
			System.err.println("#################################################################");
			if(!GraphicsEnvironment.isHeadless())
				JOptionPane.showMessageDialog(null, msg, info, JOptionPane.ERROR_MESSAGE);
			System.exit(1);
		}
	}
	
	public static Tuple<String, String> getKeyValue(String text, char separator) {
		int index = text.indexOf(separator);
		if(index == -1)
			return new Tuple<String, String>(text, null);
		return new Tuple<String, String>(text.substring(0, index), text.substring(index + 1));
	}
	
	public static Tuple<String, String> getKeyValue(String text) {
		return getKeyValue(text, ' ');
	}
	
	private static Object parseObject(String arg) {
		try {
			return Float.parseFloat(arg);
		}
		catch(NumberFormatException e) {
		}
		try {
			return Integer.parseInt(arg);
		}
		catch(NumberFormatException e) {
		}
		return arg;
	}
	
	public static Map<String, Object> parseArgsSimple(String[] args) {
		Map<String, Object> map = Maps.newHashMap();
		List<Object> list = Lists.newArrayList();
		String option = null;
		boolean parse = true;
		for(int z = 0; z < args.length; z++) {
			String arg = args[z];
			if(arg.startsWith("--")) {
				if(arg.length() == 2) {
					parse = false;
					continue;
				}
				if(option != null)
					map.put(option, null);
				option = arg.substring(2);
			}
			else if(arg.startsWith("-") && arg.length() == 2) {
				if(option != null)
					map.put(option, null);
				option = arg.substring(1);
			}
			else if(option != null) {
				map.put(option, parseObject(arg));
				option = null;
			}
			else {
				list.add(parseObject(arg));
			}
		}
		if(option != null)
			map.put(option, null);
		map.put("__pos", list);
		return map;
	}

	public static long getTime() {
		return System.nanoTime() / 1000L; // glfwGetTimerValue() / (glfwGetTimerFrequency() / 1000000L);
	}
	
	public static long rtime() {
		return Util.getTime() - START;
	}
	
	public static double ftime() {
		return ((double)rtime()) / 1000000.0;
	}

	public static String getRegionFolder(int x, int z) {
		return String.format("%c%03X%c%03X", x < 0 ? 'n' : 'p', ((x < 0) ? -x : x) >> 9, z < 0 ? 'n' : 'p', ((z < 0) ? -z : z) >> 9);
	}

	public static String getRegionName(int x, int z) {
		return String.format("r.%c%X%c%X.rgn", x < 0 ? 'n' : 'p', ((x < 0) ? -x : x) >> 3, z < 0 ? 'n' : 'p', ((z < 0) ? -z : z) >> 3);
	}
}
