package common.log;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;

import common.collect.Lists;
import common.color.TextColor;
import common.future.ListenableFuture;
import common.network.IThreadListener;
import common.util.Util;

public enum Log {
	SYSTEM("System"),
	RENDER("Render"),
	IO("I/O"),
	CONSOLE("Console"),
	TICK("Tick"),
	SOUND("Sound"),
	JNI("JNI"),
	NETTY("Netty");
	
	private static class LogMessage {
		private final LogLevel level;
		private final String prefix;
		private final String[] lines;
		
		private LogMessage(LogLevel level, String prefix, String[] lines) {
			this.level = level;
			this.prefix = prefix;
			this.lines = lines;
		}
	}
	
	public static final char CHR_CRESET = 0x01;
	public static final char CHR_COLORS2 = 0x02;
	public static final char CHR_COLORE2 = 0x09;
	public static final char CHR_NLN = 0x0a;
	public static final char CHR_COLORS1 = 0x0b;
	public static final char CHR_COLORE1 = 0x1f;
	public static final char CHR_SPC = 0x20;
	public static final char CHR_UNK = 0x7f;
	
	private static final List<LogMessage> LOG = Lists.newArrayList();

	private static LogLevel level = LogLevel.INFO;
	private static boolean colors = true;
	private static IThreadListener sync = new IThreadListener() {
		public ListenableFuture<Object> schedule(Runnable run) {
			return null;
		}
		
		public boolean isMainThread() {
			return true;
		}
	};
	
	private final String prefix;
	
	private Log(String prefix) {
		this.prefix = prefix;
	}
	
	private static void logOut(String str) {
		char c;
		int pos = 0;
		int last = 0;
		int color;
		while(pos < str.length()) {
			c = str.charAt(pos);
	 		if(((c >= CHR_COLORS1) && (c <= CHR_COLORE1)) || ((c >= CHR_COLORS2) && (c <= CHR_COLORE2))) {
				if(pos - last != 0)
					System.err.print(str.substring(last, pos));
				color = TextColor.getColor(c); //  (c >= CHR_COLORS2) && (c <= CHR_COLORE2) ? aux_colors[c - CHR_COLORS2] : text_colors[c - CHR_COLORS1];
				if(colors)
					System.err.printf("\u001b[38;2;%d;%d;%dm", (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
				last = ++pos;
				continue;
			}
	 		else if(c == CHR_CRESET) {
				if(pos - last != 0)
					System.err.print(str.substring(last, pos));
				if(colors)
					System.err.print("\u001b[0m");
				last = ++pos;
				continue;
			}
	 		else if(c == CHR_UNK) {
				if(pos - last != 0)
					System.err.print(str.substring(last, pos));
				System.err.print("?");
				last = ++pos;
				continue;
			}
	 		else if(c >= 0 && c < CHR_SPC && c != CHR_NLN) {
				if(pos - last != 0)
					System.err.print(str.substring(last, pos));
				last = ++pos;
				continue;
			}
			pos++;
	 		if(pos >= str.length() && pos - last != 0)
	 			System.err.print(str.substring(last, pos));
		}
		System.err.print(colors ? "\u001b[0m\n" : "\n");
	}

	public static String str_time(long time) {
		long secs = (time /= 1000000L) % 60L;
		long mins = (time /= 60L) % 60L;
		long hrs = (time /= 60L) % 24L;
		time /= 24L;
		return time > 0 ? String.format("%dD+%02d:%02d:%02d", time, hrs, mins, secs) : String.format("%02d:%02d:%02d", hrs, mins, secs);
	}
	
	public static void init(IThreadListener sync) {
		Log.sync = sync;
		Log.colors = System.getProperty("log.nocolor") == null && !Util.WINDOWS;
	}
	
	public static void flushLog() {
		synchronized(LOG) {
			while(!LOG.isEmpty()) {
				LogMessage msg = LOG.remove(0);
				for(String line : msg.lines) {
					line = msg.level.color + line;
					Log.sync.log(msg.prefix + line, line);
					logOut(msg.prefix + line);
				}
			}
		}
	}
	
	public static void setLevel(LogLevel level) {
		Log.level = level;
	}
	
	public static LogLevel getLevel() {
		return Log.level;
	}
	
	public void log(LogLevel level, String msg) {
		if(level.ordinal() > Log.level.ordinal())
			return;
		String prefix = String.format(TextColor.DGRAY + "[" + TextColor.GREEN + "%s" + TextColor.DGRAY + "][" + TextColor.LGRAY + "%s" + TextColor.DGRAY + "|" + TextColor.LGRAY + "%s" + 
				TextColor.DGRAY + "][%s%s" + TextColor.DGRAY + "] ", 
				str_time(Util.rtime()), this.prefix, Thread.currentThread().getName(), level.color, level.log);
		String[] lines = msg.split("\n");
		if(/* Game.getGame() == null || */ Log.sync.isMainThread()) {
			for(String line : lines) {
				line = level.color + line;
				Log.sync.log(prefix + line, line);
				logOut(prefix + line);
			}
		}
		else {
			synchronized(LOG) {
				LOG.add(new LogMessage(level, prefix, lines));
			}
		}
	}
	
	public void user(String fmt, Object ... args) {
		this.log(LogLevel.USER, String.format(fmt, args));
	}
	
	public void error(String fmt, Object ... args) {
		this.log(LogLevel.ERROR, String.format(fmt, args));
	}
	
	public void warn(String fmt, Object ... args) {
		this.log(LogLevel.WARN, String.format(fmt, args));
	}
	
	public void info(String fmt, Object ... args) {
		this.log(LogLevel.INFO, String.format(fmt, args));
	}
	
	public void perf(String fmt, Object ... args) {
		this.log(LogLevel.PERF, String.format(fmt, args));
	}
	
	public void debug(String fmt, Object ... args) {
		this.log(LogLevel.DEBUG, String.format(fmt, args));
	}
	
	public void trace(String fmt, Object ... args) {
		this.log(LogLevel.TRACE, String.format(fmt, args));
	}
	
	public void error(Throwable ex, String fmt, Object ... args) {
		this.log(LogLevel.ERROR, String.format(fmt, args));
		StringWriter sw = new StringWriter();
		PrintWriter st = new PrintWriter(sw);
		ex.printStackTrace(st);
		sw.flush();
		this.log(LogLevel.ERROR, sw.toString().trim().replace('\t', ' '));
	}
	
	public void user(String msg) {
		this.log(LogLevel.USER, msg);
	}
	
	public void error(String msg) {
		this.log(LogLevel.ERROR, msg);
	}
	
	public void warn(String msg) {
		this.log(LogLevel.WARN, msg);
	}
	
	public void info(String msg) {
		this.log(LogLevel.INFO, msg);
	}
	
	public void perf(String msg) {
		this.log(LogLevel.PERF, msg);
	}
	
	public void debug(String msg) {
		this.log(LogLevel.DEBUG, msg);
	}
	
	public void trace(String msg) {
		this.log(LogLevel.TRACE, msg);
	}
	
	public void error(Throwable ex, String msg) {
		this.log(LogLevel.ERROR, msg);
		StringWriter sw = new StringWriter();
		PrintWriter st = new PrintWriter(sw);
		ex.printStackTrace(st);
		sw.flush();
		this.log(LogLevel.ERROR, sw.toString().trim().replace('\t', ' '));
	}
}
