/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.utils;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.*;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;

import com.mucommander.conf.MuConfigurations;
import com.mucommander.conf.MuPreference;
import com.mucommander.conf.MuPreferences;
import com.mucommander.ui.dialog.debug.DebugConsoleAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * This class manages logging issues within mucommander
 *
 * @author Maxence Bernard, Arik Hadas
 */
public class MuLogging {

	/** Levels of log printings */
	public enum LogLevel {
		OFF,
		SEVERE,
		WARNING,
		INFO,
		CONFIG,
		FINE,
		FINER,
		FINEST;

		/**
		 * This method maps logback levels to mucommander log levels
		 * 
		 * @param logbackLevel logback log level
		 * @return <code>LogLevel</code> corresponding to the given logback log level
		 */
		public static LogLevel valueOf(Level logbackLevel) {
			switch(logbackLevel.toInt()) {
	    	case ch.qos.logback.classic.Level.OFF_INT:
	    		return LogLevel.OFF;
	    	case ch.qos.logback.classic.Level.ERROR_INT:
	    		return LogLevel.SEVERE;
	    	case ch.qos.logback.classic.Level.WARN_INT:
	    		return LogLevel.WARNING;
	    	case ch.qos.logback.classic.Level.INFO_INT:
	    		return LogLevel.INFO;
	    	case ch.qos.logback.classic.Level.DEBUG_INT:
	    		return LogLevel.FINE;
	    	case ch.qos.logback.classic.Level.TRACE_INT:
	    		return LogLevel.FINEST;
	    	default:
	    		return LogLevel.OFF;
			}
		}
		
		/**
		 * This method maps mucommander log levels to logback levels
		 * 
		 * @return logback level corresponding to this <code>LogLevel</code>
		 */
		public Level toLogbackLevel() {
			switch (this) {
			case SEVERE:
				return ch.qos.logback.classic.Level.ERROR;
			case WARNING:
				return ch.qos.logback.classic.Level.WARN;
			case INFO:
			case CONFIG:
				return ch.qos.logback.classic.Level.INFO;
			case FINE:
			case FINER:
				return ch.qos.logback.classic.Level.DEBUG;
			case FINEST:
				return ch.qos.logback.classic.Level.TRACE;
			case OFF:
			default:
				return ch.qos.logback.classic.Level.OFF;
			}	
		}
	}
	
	/** Appender that writes log printings to the standard console */
	private static ConsoleAppender<ILoggingEvent> consoleAppender;
	
	/** Appender that writes log printings to the debug console dialog */
	private static DebugConsoleAppender debugConsoleAppender;

	/**
	 * Sets the level of all muCommander loggers.
	 *
	 * @param level the new log level
	 */
	private static void updateLogLevel(LogLevel level) {
		ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
		logger.setLevel(level.toLogbackLevel());
	}
	
	/**
	 * Returns the log level, in mucommander terms, that match the level of a given logback logging event
	 * 
	 * @param loggingEvent logback logging event
	 * @return log level, in mucommander terms, that match the level of the given logback logging event
	 */
	public static LogLevel getLevel(ILoggingEvent loggingEvent) {
    	return LogLevel.valueOf(loggingEvent.getLevel());
    }

	/**
	 * Returns the current log level used by all <code>org.slf4j</code> loggers.
	 *
	 * @return the current log level used by all <code>org.slf4j</code> loggers.
	 */
	public static LogLevel getLogLevel() {
		return LogLevel.valueOf(MuConfigurations.getPreferences().getVariable(MuPreference.LOG_LEVEL, MuPreferences.DEFAULT_LOG_LEVEL));
	}

	/**
	 * Sets the new log level to be used by all <code>org.slf4j</code> loggers, and persists it in the
	 * application preferences.
	 *
	 * @param level the new log level to be used by all <code>org.slf4j</code> loggers.
	 */
	public static void setLogLevel(LogLevel level) {
		MuConfigurations.getPreferences().setVariable(MuPreference.LOG_LEVEL, level.toString());
		updateLogLevel(level);
	}
	
	public static DebugConsoleAppender getDebugConsoleAppender() {
		return debugConsoleAppender;
	}
	
	public static ConsoleAppender<ILoggingEvent> getConsoleAppender() {
		return consoleAppender;
	}

	public static void configureLogging() throws IOException {
		// We're no longer using LogManager and a logging.properties file to initialize java.util.logging, because of
		// a limitation with Webstart limiting the use of handlers and formatters residing in the system's classpath,
		// i.e. built-in ones.

		// Get root logger
		ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
		
		// we are not interested in auto-configuration
		LoggerContext loggerContext = rootLogger.getLoggerContext();
		loggerContext.reset();
		
		// Remove default appenders
		rootLogger.detachAndStopAllAppenders();
		
		// and add ours
		Appender<ILoggingEvent>[] appenders = createAppenders(loggerContext);
		for (Appender<ILoggingEvent> appender : appenders)
			rootLogger.addAppender(appender);
		
		// Set the log level to the value defined in the configuration
		updateLogLevel(getLogLevel());
	}
	
	private static Appender<ILoggingEvent>[] createAppenders(LoggerContext loggerContext) {
		Layout<ILoggingEvent> layout = new CustomLoggingLayout();

		consoleAppender = createConsoleAppender(loggerContext, layout);
		debugConsoleAppender = createDebugConsoleAppender(loggerContext, layout);
		
		return new Appender[] { consoleAppender, debugConsoleAppender };
	}

	private static ConsoleAppender<ILoggingEvent> createConsoleAppender(LoggerContext loggerContext, Layout<ILoggingEvent> layout) {
		ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<ILoggingEvent>();

		LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<ILoggingEvent>();
		encoder.setContext(loggerContext);
	    encoder.setLayout(layout);
	    encoder.start();

	    consoleAppender.setContext(loggerContext);
	    consoleAppender.setEncoder(encoder);
	    consoleAppender.start();

	    return consoleAppender;
	}
	
	private static DebugConsoleAppender createDebugConsoleAppender(LoggerContext loggerContext, Layout<ILoggingEvent> layout) {
		DebugConsoleAppender debugConsoleAppender = new DebugConsoleAppender(layout);
		
		debugConsoleAppender.setContext(loggerContext);
		debugConsoleAppender.start();
		
		return debugConsoleAppender;
	}

	private static class CustomLoggingLayout extends LayoutBase<ILoggingEvent> {

		private final static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		
		public String doLayout(ILoggingEvent event) {
			StackTraceElement stackTraceElement = event.getCallerData()[0];
			
			StringBuilder sbuf = new StringBuilder(128);
			sbuf.append("[");
			sbuf.append(SIMPLE_DATE_FORMAT.format(new Date(event.getTimeStamp())));
			sbuf.append("] ");
			sbuf.append(getLevel(event));
			sbuf.append(" ");
			sbuf.append(stackTraceElement.getFileName());
			sbuf.append("#");
			sbuf.append(stackTraceElement.getMethodName());
			sbuf.append(",");
			sbuf.append(stackTraceElement.getLineNumber());
			sbuf.append(" ");
			sbuf.append(event.getFormattedMessage());
			sbuf.append(CoreConstants.LINE_SEPARATOR);
			return sbuf.toString();
		}
	}
}
