package speiger.src.coreengine.utils.io;

import java.io.PrintStream;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class GameLog
{
	static final PrintStream GAME_LOG_STREAM = new GameLogWriter(System.out);
	static final String LINE_SEPERATOR = System.lineSeparator();
	static final ThreadLocal<StringBuilder> BUFFER_PROVIDER = ThreadLocal.withInitial(() -> new StringBuilder(320));
	public static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
	public static boolean STACK_TRACE = true;
	public static boolean FULL_STACK_TRACE = false;
	
	public static void trace(String name)
	{
		StringBuilder builder = BUFFER_PROVIDER.get();
		new Exception(name).printStackTrace(GAME_LOG_STREAM);
		String result = builder.toString();
		builder.setLength(0);
		log(LogLevel.INFO, result);
	}
	
	public static void test(Object...values)
	{
		StringBuilder builder = new StringBuilder();
		builder.append("Test: ");
		for(Object obj : values)
		{
			builder.append(obj).append(" : ");
		}
		log(LogLevel.INFO, builder.substring(0, builder.length() - 3));
	}
	
	public static void info(String name, Object...values)
	{
		StringBuilder builder = new StringBuilder();
		builder.append(name).append(": ");
		for(Object obj : values)
		{
			builder.append(obj).append(" : ");
		}
		log(LogLevel.INFO, builder.substring(0, builder.length() - 3));
	}
	
	public static void info(Object obj)
	{
		log(LogLevel.INFO, obj == null ? "Null" : obj.toString());
	}
	
	public static void info(String text)
	{
		log(LogLevel.INFO, text);
	}
	
	public static void warn(Object obj)
	{
		log(LogLevel.WARNING, obj == null ? "Null" : obj.toString());
	}
	
	public static void warn(String text)
	{
		log(LogLevel.WARNING, text);
	}
	
	public static void error(Object obj)
	{
		log(LogLevel.ERROR, obj == null ? "Null" : obj.toString());
	}
	
	public static void error(String text)
	{
		log(LogLevel.ERROR, text);
	}
	
	public static void error(String message, Throwable error, LogLevel level)
	{
		StringBuilder buffer = BUFFER_PROVIDER.get();
		buffer.append("[").append(ZonedDateTime.now().format(TIME_FORMAT)).append("]").append(level.getLevelName());
		if(STACK_TRACE)
		{
			buffer.append("[ ").append(getTrace(4)).append("]");
		}
		buffer.append(" ").append(message);
		System.out.println(buffer.toString());
		buffer.setLength(0);		
		error.printStackTrace(GAME_LOG_STREAM);
		buffer.setLength(buffer.length() - 2);
		System.err.println(buffer.toString());
		buffer.setLength(0);
	}
	
	public static void log(LogLevel level, String info)
	{
		StringBuilder buffer = BUFFER_PROVIDER.get();
		buffer.append("[").append(ZonedDateTime.now().format(TIME_FORMAT)).append("]").append(getThreadName()).append(level.getLevelName());
		if(STACK_TRACE)
		{
			buffer.append("[ ").append(getTrace(4)).append("]");
		}
		buffer.append(" ").append(info);
		(level == LogLevel.ERROR ? System.err : System.out).println(buffer.toString());
		buffer.setLength(0);
		if(FULL_STACK_TRACE)
		{
			printStackTrace(buffer);
			System.out.flush();
			System.err.flush();
		}
	}
	
	private static String getThreadName()
	{
		return "["+Thread.currentThread().getName()+"]";
	}
	
	private static void printStackTrace(StringBuilder buffer)
	{
		new Exception("Stack trace").printStackTrace(GAME_LOG_STREAM);
		System.err.println(buffer.toString());
		buffer.setLength(0);
	}
	
	private static String getTrace(int index)
	{
		return createStackTrace(Thread.currentThread().getStackTrace()[index]);
	}
	
	private static String createStackTrace(StackTraceElement trace)
	{
		if(trace.isNativeMethod())
		{
			return "(Native Methode)";
		}
		else if(trace.getFileName() != null)
		{
			if(trace.getLineNumber() >= 0)
			{
				return "(" + trace.getFileName() + ":" + trace.getLineNumber() + ")";				
			}
			return "("+trace.getFileName()+":0)";
		}
		return "(Unknown Source)";
	}
	
	public static enum LogLevel
	{
		INFO("[Info]"),
		WARNING("[Warning]"),
		ERROR("[Error]");
		
		String levelName;
		
		private LogLevel(String level)
		{
			levelName = level;
		}
		
		public String getLevelName()
		{
			return levelName;
		}
	}
	
	public static class GameLogWriter extends PrintStream
	{
		public GameLogWriter(PrintStream parent)
		{
			super(parent);
		}
		
		@Override
		public void println(String x)
		{
			GameLog.BUFFER_PROVIDER.get().append(x).append(LINE_SEPERATOR);
		}
		
		@Override
		public void println(Object x)
		{
			GameLog.BUFFER_PROVIDER.get().append(String.valueOf(x)).append(LINE_SEPERATOR);
		}
	}
}
