/*******************************************************************************
 * Copyright (c) 2004, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*


 */
package org.eclipse.jem.util.logger.proxy;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.logging.Level;

/**
 * This is a base, UI independent logger.   It will
 * construct a consistent msg. body, and call an enfironment specific ILogRenderer.
 * By default, this logger will use a console based ILogRenderer,
 * and a J2EE Plugin identification.
 * 
 * <p>
 * When running outside of Eclipse, the trace and logging level come from the system properties
 * <ul>
 * 		<li>"debug" (="true") - The default is <code>false</code>. 
 * 		<li>"logLevel" (="level" where "level" is a level string, e.g. SEVERE, WARNING, etc. from the <code>java.util.logging.Level</code> class).
 * 			The default is "WARNING".
 * </ul>
 * 
 * 
 * @since 1.0.0
 */
public class Logger {
	
	// This is used by ILogRenderer2 to define the default level.
	static class LocalLevel extends Level {
		/**
		 * Comment for <code>serialVersionUID</code>
		 * 
		 * @since 1.1.0
		 */
		private static final long serialVersionUID = -6273357074767854883L;

		public LocalLevel(String name, int level) {
			super(name, level);
		}
	}	
	
	private boolean fTraceMode = false; // will we actually punch trace messaged or not              
	private String fPluginID;
	private ILogRenderer fRenderer = null;
	private ILogRenderer2 renderer2 = null;
	public String fLineSeperator;
	private Level level;
	private Level defaultLevel = Level.SEVERE;	// By default only severe or greater are logged.
	private String logFileName;
	private final static String DefaultLoggerPlugin = ILogRenderer.DefaultPluginID;
	static private Hashtable Loggers = new Hashtable(); // Keep track of all the Loggers
	final protected static String[] LogMark = { "*** ERROR *** ", //$NON-NLS-1$
		"[Trace] ", //$NON-NLS-1$
		"+++ Warning +++ ", //$NON-NLS-1$
		"Info " }; //$NON-NLS-1$

	final protected static String Filler = "    "; // Use this to indent msg. body //$NON-NLS-1$
	
	protected Logger() {
		this(ILogRenderer.DefaultPluginID);
	}
	
	protected Logger(String pluginID) {
		fPluginID = pluginID;
		setRenderer(new JDKConsoleRenderer(this));	// Set up default to this. Someone can change it later.
	}

	/**
	 * Return the stacktrace as a print formatted string.
	 * @param e
	 * @return the stacktrace as a string.
	 * 
	 * @since 1.0.0
	 */
	public String exceptionToString(Throwable e) {
		StringWriter stringWriter = new StringWriter();
		e.printStackTrace(new PrintWriter(stringWriter));
		return stringWriter.toString();
	}
	
	/**
	 * Get the system default logger. This is used for clients that don't know if they
	 * are running in Eclipse or outside of it. This way they have a common logger format
	 * which switch correctly.
	 * @return default logger.
	 * 
	 * @since 1.0.0
	 */
	static public Logger getLogger() {
		Logger defaultLogger = (Logger) Loggers.get(DefaultLoggerPlugin);
		if (defaultLogger == null) {
			defaultLogger = new Logger();
			defaultLogger.init();
			Loggers.put(DefaultLoggerPlugin, defaultLogger);
		}
		return defaultLogger;
	}
	
	/**
	 * Get the logger for a specific plugin.
	 * @param pluginId
	 * @return logger for a specific pluggin.
	 * 
	 * @since 1.0.0
	 */
	static public Logger getLogger(String pluginId) {
		if (pluginId == null)
			return Logger.getLogger();
		Logger Logger = (Logger) Loggers.get(pluginId);
		if (Logger == null) {
			Logger = new Logger(pluginId);
			Logger.init();
			Loggers.put(pluginId, Logger);
		}
		return Logger;
	}
	
	/**
	 * Used by subclass to get a logger if it exists, but not create one.
	 * @param pluginId
	 * @return logger.
	 * 
	 * @since 1.0.0
	 */
	static protected Logger getLoggerIfExists(String pluginId) {
		if (pluginId == null)
			return Logger.getLogger();
		else
			return (Logger) Loggers.get(pluginId);
	}

	/**
	 * Get the plugin id for this logger.
	 * @return pluginid
	 * 
	 * @since 1.0.0
	 */
	public String getPluginID() {
		return fPluginID;
	}
	
	/**
	 * Get the trace mode for this logger
	 * @return <code>true</code> if tracing is going on.
	 * 
	 * @since 1.0.0
	 */
	public boolean getTraceMode() {
		return fTraceMode;
	}
	
	/*
	 * Indent the Msg. Body to make it easier to read the log
	 */
	private void indentMsg(String msg, StringBuffer logMsg) {
		// Line seperator is different on different platform, unix = \n, windows \r\n and mac \r
		String sep = fLineSeperator;
		if (msg.indexOf("\r\n") != -1) //$NON-NLS-1$
			sep = "\r\n"; //$NON-NLS-1$
		else if (msg.indexOf("\n") != -1) //$NON-NLS-1$
			sep = "\n"; //$NON-NLS-1$
		else if (msg.indexOf("\r") != -1) //$NON-NLS-1$
			sep = "\r"; //$NON-NLS-1$
		StringTokenizer tokenizer = new StringTokenizer(msg, sep);
		boolean first = true;
		while (tokenizer.hasMoreTokens()) {
			if (first) {
				first = false;
				logMsg.append(Filler + tokenizer.nextToken());
			} else
				logMsg.append(fLineSeperator + Filler + tokenizer.nextToken());
		}
	}
	/*
	 * If Eclipse is started with the -XDebug or -debug turn traces on for this Logger
	 * Creation date: (8/23/2001 7:37:04 PM)
	 */
	private void init() {
		if (System.getProperty("debug") != null) //$NON-NLS-1$
			fTraceMode = true;
		level = defaultLevel = Level.parse(System.getProperty("logLevel", Level.WARNING.getName())); //$NON-NLS-1$
		
		try {
			fLineSeperator = System.getProperty("line.separator"); // Diff on Win/Unix/Mac //$NON-NLS-1$
		} catch (Exception e) {
			fLineSeperator = "\n"; //$NON-NLS-1$
		}
	}
	/*
	 * Generic log.
	 * Creation date: (8/24/2001 1:55:34 PM)
	 * @return java.lang.String
	 * @param msg java.lang.String
	 * @param type int
	 */
	private String logAny(String msg, int type) {
		StringBuffer logMsg = new StringBuffer();
		logMsg.append(fLineSeperator);
		logMsg.append(LogMark[type]);
		return punchLog(logRest(msg, logMsg), type);
	}
	
	/**
	 * This is to be used by renderers that want to put a msg out
	 * in a generic format. This just returns the string that
	 * should be logged. It puts things like headers on it.
	 * 
	 * @param msg
	 * @param aLevel
	 * @return The generic message for the string and level.
	 * 
	 * @since 1.0.0
	 */
	public String getGenericMsg(String msg, Level aLevel) {
		StringBuffer genMsg = new StringBuffer(msg.length()+16);
		genMsg.append(fLineSeperator);
		genMsg.append(getLevelHeader(aLevel));
		genMsg.append(": "); //$NON-NLS-1$
		genMsg.append(new Date());
		indentMsg(msg, genMsg);
		return genMsg.toString();
	}
	
	private static final Level[] LEVEL_SEARCH = new Level[] {
		Level.SEVERE,
		Level.WARNING,
		Level.INFO,
		ILogRenderer2.TRACE
	};
	
	private static final String[] LEVEL_MARK = new String[] {
		"*** ERROR ***", //$NON-NLS-1$
		"+++ Warning +++", //$NON-NLS-1$
		"Info", //$NON-NLS-1$
		"[Trace]" //$NON-NLS-1$
	};
	
	private String getLevelHeader(Level aLevel) {
		for (int i=0; i<LEVEL_SEARCH.length; i++)
			if (LEVEL_SEARCH[i] == aLevel)
				return LEVEL_MARK[i];
		return aLevel.getName();	// Not found, just use level string.
	}
	
	// The write's are here for history. Will implement using log(obj, Level) for all of the types.


	/**
	 * deprecated use log(Level, Exception)
	 * @param aLevel
	 * @param ex
	 * @return
	 * 
	 * @since 1.0.0
	 * 
	 */
	public String write(Level aLevel, Exception ex) {
		return log(aLevel, ex);
	}
	
	/**
	 * deprecated use log(Throwable)
	 * @param ex
	 * @return
	 * 
	 * @since 1.0.0
	 * 
	 */
	public String write(Throwable ex) {
		return log(ex);
	}
	
	/**
	 * deprecated use log(Object, Level)
	 * @param aLevel
	 * @param logEntry
	 * @return
	 * 
	 * @since 1.0.0
	 */
	public String write(Level aLevel, Object logEntry) {
		return log(logEntry, aLevel);
	}
	
	/**
	 * deprecated use log(String, Level)
	 * @param aLevel
	 * @param string
	 * @return
	 * 
	 * @since 1.0.0
	 */
	public String write(Level aLevel, String string) {
		return log(string, aLevel);
	}
	/**
	 * deprecated use log(Throwable, Level)
	 * @param aLevel
	 * @param ex
	 * @return
	 * 
	 * @since 1.0.0
	 */
	public String write(Level aLevel, Throwable ex) {
		return log(ex, aLevel);
	}
	/**
	 * deprecated use log(Throwable, Level)
	 * @param aLevel
	 * @param ex
	 * @return
	 * 
	 * @since 1.0.0
	 */
	public String log(Level aLevel, Exception ex) {
		return log(ex, aLevel);
	}
	/**
	 * deprecated use log(Throwable, Level)
	 * @param aLevel
	 * @param ex
	 * @return
	 * 
	 * @since 1.0.0
	 */
	public String log(Level aLevel, Throwable ex) {
		return log(ex, aLevel);
	}
	
	/**
	 * Get the logging level
	 * @return logging level
	 * 
	 * @since 1.0.0
	 */
	public Level getLevel() {
		return level;
	}
	
	/**
	 * Check if the requested level is being logged. (e.g. if current level is SEVERE, then FINE will not be logged).
	 * @param requestlevel
	 * @return <code>true</code> if the level will be logged.
	 * 
	 * @since 1.0.0
	 */
	public boolean isLoggingLevel(Level requestlevel) {
		if (requestlevel == ILogRenderer2.TRACE && !getTraceMode())
			return false;	// We aren't tracing but requested trace.
		
		return !(requestlevel.intValue() < getLevel().intValue() || getLevel() == Level.OFF);
	}

	/**
	 * Log an error string.
	 * @param msg
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String logError(String msg) {
		return log(msg, Level.SEVERE);
	}

	/**
	 * Log an error throwable
	 * @param e
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String logError(Throwable e) {
		return log(e, Level.SEVERE);
	}

	/**
	 * Log an info message.
	 * @param msg
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String logInfo(String msg) {
		return log(msg, Level.INFO);
	}
	
/**
 * Log a throwable as a warning.
 * @param e
 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
 * 
 * @since 1.0.0
 */
	public String logInfo(Throwable e) {
		return log(e, Level.INFO);
	}

	/**
	 * Append the string to logMsg buffer passed in. Append the date and format the
	 * string with nice indentation.
	 * 
	 * @param msg
	 * @param logMsg
	 * @return the string from the logMsg after logging the rest.
	 * 
	 * @since 1.0.0
	 */
	protected String logRest(String msg, StringBuffer logMsg) {
		logMsg.append(new Date());
		indentMsg(msg, logMsg);
		return logMsg.toString();
	}

	/**
	 * Log the msg as trace only.
	 * @param msg
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String logTrace(String msg) {
		if (fTraceMode)
			return log(msg, ILogRenderer2.TRACE);
		else
			return ILogRenderer.NOLOG_DESCRIPTION;
	}

	/**
	 * Log the throwable as trace only.
	 * @param e
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String logTrace(Throwable e) {
		return log(e, ILogRenderer2.TRACE);
	}

	/**
	 * Log the message as warning.
	 * @param msg
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String logWarning(String msg) {
		return log(msg, Level.WARNING);
	}
	/**
	 * Log the throwable as a warning.
	 * @param e
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String logWarning(Throwable e) {
		return log(e, Level.WARNING);
	}
	
	/**
	 * Ask the Renderer to punch the msg. in the log.. one
	 * caller at the time
	 * Creation date: (8/24/2001 9:19:17 AM)
	 * @return java.lang.String
	 * @param msg java.lang.String
	 * @param type int
	 */
	protected synchronized String punchLog(String msg, int type) {
		return fRenderer.log(msg, type);
	}
	
	/**
	 * Set the renderer to use.
	 * @param renderer
	 * 
	 * @since 1.0.0
	 */
	public void setRenderer(ILogRenderer renderer) {
		fRenderer = renderer;
		renderer2 = (renderer instanceof ILogRenderer2) ? (ILogRenderer2) renderer : null;
		renderer.setTraceMode(getTraceMode());
	}
	
	/**
	 * Set the trace mode.
	 * @param flag <code>true</code> to turn on tracing.
	 * 
	 * @since 1.0.0
	 */
	public void setTraceMode(boolean flag) {
		fTraceMode = flag;
		if (fRenderer != null)
			fRenderer.setTraceMode(flag);
	}
	
	/**
	 * Set the level cutoff for logging. Anything below this level will not log.
	 * Do not set level to <code>ILogRenderer2.TRACE</code>. It doesn't make sense.
	 * 
	 * @param level (Use <code>ILogRenderer2.DEFAULT</code> to restore to default for this logger.
	 * 
	 * @since 1.0.0
	 */
	public void setLevel(Level level) {
		this.level = level != ILogRenderer2.DEFAULT ? level : defaultLevel;
	}
	
	/**
	 * Set the default level for this logger. It won't touch the current level.
	 * 
	 * @param level
	 * 
	 * @since 1.0.0
	 */
	public void setDefaultLevel(Level level) {
		this.defaultLevel = level;
	}
	
	/**
	 * Get the log file name.
	 * @return Returns the logFileName.
	 */
	public String getLogFileName() {
		return logFileName;
	}

	/**
	 * Set the log file name.
	 * @param logFileName The logFileName to set.
	 */
	public void setLogFileName(String logFileName) {
		this.logFileName = logFileName;
	}
	
	// Now all of the log() types that use a Level.

	/**
	 * Log the throwable at the default level for a throwable. 
	 * @param e
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(Throwable e) {
		return log(e, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log the throwable at the given level.
	 * @param e
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(Throwable e, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(e, logLevel);
		} else {
			// Do it the old way.
			String stackTrace = exceptionToString(e);
			return logAny(stackTrace, getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.SEVERE));
		}
	}
	
	public String log(Object o) {
		return log(o, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log the object at the given level.
	 * @param o
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(Object o, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(o, logLevel);
		} else {
			// Do it the old way.
			return logAny(String.valueOf(o), getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.FINEST));
		}
	}	
	
	// The following are added to match up with Hyades so that primitives can be logged too.
	
	/**
	 * Log a boolean at the default level. 
	 * @param b
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(boolean b) {
		return log(b, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log a boolean at the given level.
	 * @param b
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(boolean b, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(b, logLevel);
		} else {
			// Do it the old way.
			return logAny(String.valueOf(b), getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.FINEST));
		}
	}	
	
	/**
	 * Log the character at the default level.
	 * @param c
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(char c) {
		return log(c, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log the character at the given level.
	 * @param c
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(char c, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(c, logLevel);
		} else {
			// Do it the old way.
			return logAny(String.valueOf(c), getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.FINEST));
		}
	}
	
	/**
	 * Log the byte at the default level.
	 * @param b
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(byte b) {
		return log(b, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log the byte at the given level.
	 * @param b
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(byte b, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(b, logLevel);
		} else {
			// Do it the old way.
			return logAny(String.valueOf(b), getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.FINEST));
		}
	}	
	
	/**
	 * Log the short at the default level.
	 * @param s
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(short s) {
		return log(s, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log the short at the given level.
	 * @param s
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(short s, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(s, logLevel);
		} else {
			// Do it the old way.
			return logAny(String.valueOf(s), getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.FINEST));
		}
	}
	
	/**
	 * Log the int at the default level.
	 * @param i
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(int i) {
		return log(i, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log the int at the default level.
	 * @param i
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(int i, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(i, logLevel);
		} else {
			// Do it the old way.
			return logAny(String.valueOf(i), getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.FINEST));
		}
	}
	
	/**
	 * Log the long at the default level.
	 * @param l
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(long l) {
		return log(l, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log the long at the given level.
	 * @param l
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(long l, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(l, logLevel);
		} else {
			// Do it the old way.
			return logAny(String.valueOf(l), getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.FINEST));
		}
	}	
	
	/**
	 * Log the float at the default level.
	 * @param f
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(float f) {
		return log(f, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log the float at the given level.
	 * @param f
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(float f, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(f, logLevel);
		} else {
			// Do it the old way.
			return logAny(String.valueOf(f), getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.FINEST));
		}
	}
	
	/**
	 * Log the double at the default level
	 * @param d
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(double d) {
		return log(d, ILogRenderer2.DEFAULT);
	}
	
	/**
	 * Log the double at the given level
	 * 
	 * @param d
	 * @param logLevel
	 * @return how it was logged. See <code>CONSOLE_DESCRIPTION.</code>
	 * 
	 * @since 1.0.0
	 */
	public String log(double d, Level logLevel) {
		if (renderer2 != null) {
			return renderer2.log(d, logLevel);
		} else {
			// Do it the old way.
			return logAny(String.valueOf(d), getOldType(logLevel != ILogRenderer2.DEFAULT ? level : Level.FINEST));
		}
	}	
	
	/*
	 * Turn new type into old type. The defaultLevel is the
	 * level to use if the incoming level is marked as default.
	 */
	private int getOldType(Level aLevel) {
		if (aLevel == Level.SEVERE)
			return ILogRenderer.LOG_ERROR;
		else if (aLevel == Level.WARNING)
			return ILogRenderer.LOG_WARNING;
		else if (aLevel == Level.INFO)
			return ILogRenderer.LOG_INFO;
		else if (aLevel == ILogRenderer2.TRACE)
			return ILogRenderer.LOG_TRACE;
		else
			return ILogRenderer.LOG_INFO;		
	}
}
