package edu.berkeley.nlp.util;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Stack;

public class Logger {

	public static interface LogInterface {
		public void logs(String s, Object... args);

		public void logss(String s);

		public void startTrack(String s);

		public void endTrack();

		public void dbg(String s);

		public void err(String s);

		public void err(String s, Object... args);

		public void warn(String s);

		public void warn(String string, Object... args);

		public void logss(String string, Object... args);

	}

	public static class FigLogger implements LogInterface {

		public void dbg(String s) {
			LogInfo.dbg(s);
		}

		public void endTrack() {
			LogInfo.end_track();
		}

		public void err(String s) {
			LogInfo.error(s);
		}

		public void err(String s, Object... args) {
			LogInfo.error(s, args);
		}

		public void logs(String s, Object... args) {
			LogInfo.logs(s, args);
		}

		public void logss(String s) {
			LogInfo.logss(s);
		}

		public void logss(String string, Object... args) {
			LogInfo.logss(string, args);
		}

		public void startTrack(String s) {
			LogInfo.track(s);
		}

		public void warn(String s) {
			LogInfo.warning(s);
		}

		public void warn(String string, Object... args) {
			LogInfo.warning(string, args);
		}
	}

	public static class SystemLogger implements LogInterface {

		private PrintStream out;
		private PrintStream err;
		private int trackLevel = 0;
		private boolean debug = true;

		public SystemLogger(PrintStream out, PrintStream err) {
			this.out = out;
			this.err = err;
		}

		public void close() {
			if (out != null) {
				out.close();
			}
			if (err != null) {
				err.close();
			}
		}

		public SystemLogger(String outFile, String errFile)
				throws FileNotFoundException {
			this(outFile != null ? new PrintStream(
					new FileOutputStream(outFile)) : null,
					errFile != null ? new PrintStream(new FileOutputStream(
							errFile)) : null);
		}

		public SystemLogger() {
			this(System.out, System.err);
		}

		private Stack<Long> trackStartTimes = new Stack<Long>();

		private String getIndentPrefix() {
			StringBuilder builder = new StringBuilder();
			for (int i = 0; i < trackLevel; ++i) {
				builder.append("\t");
			}
			return builder.toString();
		}

		private void output(PrintStream out, String txt) {
			if (out == null)
				return;
			String[] lines = txt.split("\n");
			String prefix = getIndentPrefix();
			for (String line : lines) {
				out.println(prefix + line);
			}
		}

		public void dbg(String s) {
			if (debug)
				output(out, "[dbg] " + s);
		}

		private String timeString(double milliSecs) {
			String timeStr = "";
			int hours = (int) (milliSecs / (1000 * 60 * 60));
			if (hours > 0) {
				milliSecs -= hours * 1000 * 60 * 60;
				timeStr += hours + "h";
			}
			int mins = (int) (milliSecs / (1000 * 60));
			if (mins > 0) {
				milliSecs -= mins * 1000.0 * 60.0;
				timeStr += mins + "m";
			}
			int secs = (int) (milliSecs / 1000.0);
			// if (secs > 0) {
			// milliSecs -= secs * 1000.0;
			timeStr += secs + "s";
			// }

			return timeStr;
		}

		public void endTrack() {
			String timeStr = null;
			synchronized (this) {
				trackLevel--;
				double milliSecs = System.currentTimeMillis()
						- trackStartTimes.pop();
				timeStr = timeString(milliSecs);
			}
			output(out, "} " + (timeStr != null ? "[" + timeStr + "]" : ""));
		}

		public void err(String s) {
			err.println(s);
		}

		public void logs(String s) {
			output(out, s);
		}

		public void logss(String s) {
			output(out, s);
		}

		public void startTrack(String s) {
			output(out, s + " {");
			synchronized (this) {
				trackLevel++;
				trackStartTimes.push(System.currentTimeMillis());
			}
		}

		public void warn(String s) {
			output(err, "[warn] " + s);
		}

		public void logs(String s, Object... args) {
			logs(String.format(s, args));
		}

		public void err(String s, Object... args) {
			output(err, "[err] " + String.format(s, args));
		}

		public void warn(String string, Object... args) {
			warn(String.format(string, args));
		}

		public void logss(String string, Object... args) {
			logss(String.format(string, args));
		}
	}

	public static class CompoundLogger implements LogInterface {
		private LogInterface[] loggers;

		public CompoundLogger(LogInterface... loggers) {
			this.loggers = loggers;
		}

		public void logs(String s, Object... args) {
			for (LogInterface logger : loggers) {
				logger.logs(s, args);
			}
		}

		public void logss(String s) {
			for (LogInterface logger : loggers) {
				logger.logss(s);
			}
		}

		public void startTrack(String s) {
			for (LogInterface logger : loggers) {
				logger.startTrack(s);
			}
		}

		public void endTrack() {
			for (LogInterface logger : loggers) {
				logger.endTrack();
			}
		}

		public void dbg(String s) {
			for (LogInterface logger : loggers) {
				logger.dbg(s);
			}
		}

		public void err(String s) {
			for (LogInterface logger : loggers) {
				logger.err(s);
			}
		}

		public void err(String s, Object... args) {
			for (LogInterface logger : loggers) {
				logger.err(s, args);
			}
		}

		public void warn(String s) {
			for (LogInterface logger : loggers) {
				logger.warn(s);
			}
		}

		public void warn(String string, Object... args) {
			for (LogInterface logger : loggers) {
				logger.warn(string, args);
			}
		}

		public void logss(String string, Object... args) {
			for (LogInterface logger : loggers) {
				logger.logss(string, args);
			}
		}
	}

	public synchronized static void setGlobalLogger(LogInterface logger) {
		instance = logger;
	}

	public synchronized static LogInterface getGlobalLogger() {
		return instance;
	}

	private static LogInterface instance = new SystemLogger();

	public static LogInterface i() {
		return instance;
	}

	public static void setFig() {
		setLogger(new FigLogger());
	}

	public static void setLogger(LogInterface i) {
		instance = i;
	}

	public static void logs(String s) {
		i().logs(s);
	}

	// Static Logger Methods
	public static void logs(String s, Object... args) {
		i().logs(s, args);
	}

	public static void logss(String s) {
		i().logss(s);
	}

	public static void startTrack(String s, Object... args) {
		i().startTrack(String.format(s, args));
	}

	public static void endTrack() {
		i().endTrack();
	}

	public static void dbg(String s) {
		i().dbg(s);
	}

	public static void err(String s) {
		i().err(s);
	}

	public static void err(String s, Object... args) {
		i().err(s, args);
	}

	public static void warn(String s) {
		i().warn(s);
	}

	public static void warn(String string, Object... args) {
		i().warn(string, args);
	}

	public static void logss(String string, Object... args) {
		i().logss(string, args);
	}

}
