/*
 * Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
 * Copyright (C) 2013-2020 MicroEJ Corp. - EDC compliance and optimizations.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util.logging;

import java.util.ArrayList;
import java.util.List;

import ej.annotation.Nullable;

/**
 * A Logger is used to log messages.
 * <p>
 * Loggers normally have a name, which is an arbitrary string. Named Loggers are created with the factory
 * {@link #getLogger(String)}. This method will either create a new Logger or return a suitable existing Logger, using
 * the name.
 * <p>
 * It also possible to create anonymous Loggers which are independent from the named Loggers and independent from one
 * another.
 * <p>
 * Each Logger has an associated {@link Level}. This reflects a minimum level that this Logger will care about. If the
 * level is high, the Logger will only care about important messages. When it low, it will care about more messages.
 * Caring means logging. The Level can be changed dynamically, which is useful to augment or diminish the amount of
 * messages that are actually logged.
 * <p>
 * The names of the logging methods, such as {@link #severe(String)} or {@link #finer(String)} denote the importance of
 * the message. Their names match the standard levels given by the class {@link Level}. When using one of these methods,
 * the Logger performs a test to compare its own Level and the Level associated with the logging request. If the level
 * of the request is lower than the Logger's level, nothing happens. Otherwise, the Logger forwards the message to a
 * list of {@link Handler}s that have been added with the method {@link #addHandler(Handler)}.
 */
public class Logger {

	/**
	 * This is a name for the global logger.
	 */
	public static final String GLOBAL_LOGGER_NAME = "global";

	/*
	 * The public level is the value set with setLevel() and returned by getLevel(). The effective level is the value
	 * used to determine if a message must be log or not. Effective and public levels are the same unless the public
	 * level is null. In this case, the effective level is taken from the parent.
	 */
	/**
	 * This is the real level of the logger.
	 */
	@Nullable
	protected Level effectiveLevel;

	/**
	 * This is the level set with {@link #setLevel(Level)} and returned by {@link #getLevel()}. It may be null, that's
	 * why implementation will use the {@link #effectiveLevel} to determine whether a message must be logged or not.
	 */
	@Nullable
	protected Level publicLevel;

	@Nullable
	private final String name;
	@Nullable
	private Logger parent;
	private final List<Handler> handlers;

	/**
	 * Protected method to create a logger. The second parameter is silently ignored, hence any call to this method is
	 * equivalent to {@code getLogger(name, null)}.
	 *
	 * @param name
	 *            used to identify the logger.
	 * @param resourceBundleName
	 *            not used.
	 */
	protected Logger(@Nullable String name, @Nullable String resourceBundleName) {
		this.name = name;
		this.handlers = new ArrayList<>();
		this.publicLevel = null;
		this.effectiveLevel = null;
		// Effective level will be updated in setParent(). Each factory method calls setParent() after creating a
		// Logger.
	}

	/**
	 * Each call to this method creates a new Logger. Its level is set to Level.ALL. An anonymous logger is not
	 * registered in the LogManager.
	 *
	 * @return a new anonymous Logger, whose name is null
	 */
	public static Logger getAnonymousLogger() {
		Logger logger = new Logger(null, null);
		logger.setParent(LogManager.getLogManager().rootLogger);
		logger.setLevel(Level.ALL);
		return logger;
	}

	/**
	 * Find or create a logger.
	 * <p>
	 * If a logger has already been with the same name, this logger is returned (note that case is not taken into
	 * account). Otherwise a new logger is created.
	 * <p>
	 * If a new logger is created its log level will be configured based on the LogManager configuration and it will
	 * configured to also send logging output to its parent's handlers. It will be registered in the LogManager global
	 * namespace.
	 * <p>
	 * Note: The LogManager may only retain a weak reference to the newly created logger. It is important to understand
	 * that a previously created logger with the given name may be garbage collected at any time if there is no strong
	 * reference to the Logger. In particular, this means that two back-to-back calls like
	 * {@code getLogger("MyLogger").log(...)} may use different Logger objects named "MyLogger" if there is no strong
	 * reference to the Logger named "MyLogger" elsewhere in the program.
	 *
	 * @param name
	 *            used to identify a logger. Case is not taken into account.
	 * @return a suitable Logger.
	 * @throws NullPointerException
	 *             if name is {@code null}.
	 */
	public static Logger getLogger(String name) {
		// Check whether this logger has already been registered in the LogManager
		String lowerCaseName = name.toLowerCase(); // can throw a NullPointerException
		Logger logger = LogManager.getLogManager().getLogger(lowerCaseName);

		// If not, create it
		if (logger == null) {
			logger = new Logger(lowerCaseName, null);
			logger.setParent(LogManager.getLogManager().rootLogger); // FIXME improve parent management
			LogManager.getLogManager().addLogger(logger);
		}

		// Return the requested logger
		return logger;
	}

	/**
	 * Get the logger with the name Logger.GLOBAL_LOGGER_NAME.
	 *
	 * @return the global logger
	 */
	public static final Logger getGlobal() {
		return Logger.getLogger(GLOBAL_LOGGER_NAME);
	}

	/**
	 * Add a log Handler to the list of Handlers known by the Logger.
	 *
	 * @param handler
	 *            a new Handler for the Logger
	 */
	public void addHandler(Handler handler) {
		this.handlers.add(handler);
	}

	/*
	 * Methods to deal with handlers
	 */
	/**
	 * Log a CONFIG message.
	 * <p>
	 * If the logger is currently enabled for the CONFIG message level then the given message is forwarded to all the
	 * registered output handler objects.
	 *
	 * @param msg
	 *            message to log.
	 */
	public void config(@Nullable String msg) {
		log(Level.CONFIG, msg);
	}

	/**
	 * Log a FINE message.
	 * <p>
	 * If the logger is currently enabled for the FINE message level then the given message is forwarded to all the
	 * registered output handler objects.
	 *
	 * @param msg
	 *            message to log.
	 */
	public void fine(@Nullable String msg) {
		log(Level.FINE, msg);
	}

	/**
	 * Log a FINER message.
	 * <p>
	 * If the logger is currently enabled for the FINER message level then the given message is forwarded to all the
	 * registered output handler objects.
	 *
	 * @param msg
	 *            message to log.
	 */
	public void finer(@Nullable String msg) {
		log(Level.FINER, msg);
	}

	/**
	 * Log a FINEST message.
	 * <p>
	 * If the logger is currently enabled for the FINEST message level then the given message is forwarded to all the
	 * registered output handler objects.
	 *
	 * @param msg
	 *            message to log.
	 */
	public void finest(@Nullable String msg) {
		log(Level.FINEST, msg);
	}

	/**
	 * Get the current level of this Logger.
	 *
	 * @return the Logger's current level.
	 */
	@Nullable
	public Level getLevel() {
		return this.publicLevel;
	}

	/**
	 * Get the name of this logger. It is the name used for its creation or null for anonymous Loggers.
	 *
	 * @return the Logger's name (or null for anonymous Loggers).
	 */
	@Nullable
	public String getName() {
		return this.name;
	}

	/**
	 * Get the parent of this logger.
	 *
	 * @return the parent of this logger; <code>null</code> for the root logger
	 */
	@Nullable
	public Logger getParent() {
		return this.parent;
	}

	/**
	 * Log an INFO message.
	 * <p>
	 * If the logger is currently enabled for the INFO message level then the given message is forwarded to all the
	 * registered output handler objects.
	 *
	 * @param msg
	 *            message to log.
	 */
	public void info(@Nullable String msg) {
		log(Level.INFO, msg);
	}

	/**
	 * Check if a message of the given level would be logged by this Logger. This depends on the Logger current level.
	 * If the Logger's current level is lower than 'level', the message would be logged ; otherwise, it would not.
	 *
	 * @param level
	 *            a logging Level.
	 * @return {@code true} if the message would be logged.
	 */
	public boolean isLoggable(Level level) {
		Level effectiveLevel = this.effectiveLevel;
		assert effectiveLevel != null; // see comment in constructor
		return level.intValue() >= effectiveLevel.intValue();
	}

	/**
	 * Log a message, with no arguments.
	 *
	 * If the logger is currently enabled for the given message level then the given message is forwarded to all the
	 * registered output Handler objects.
	 *
	 * @param level
	 *            One of the message level identifiers, e.g. SEVERE
	 * @param msg
	 *            The string message (or a key in the message catalog)
	 */
	public void log(Level level, @Nullable String msg) {
		log(level, msg, null);
	}

	/**
	 * Log a message, with an associated throwable object.
	 *
	 * If the logger is currently enabled for the given message level then the given message is forwarded to all the
	 * registered output Handler objects.
	 *
	 * @param level
	 *            One of the message level identifiers, e.g. SEVERE
	 * @param msg
	 *            The string message (or a key in the message catalog)
	 * @param thrown
	 *            The throwable associated with the message
	 */
	public void log(Level level, @Nullable String msg, @Nullable Throwable thrown) {
		// Create record
		LogRecord record = new LogRecord(level, msg);
		record.setThrown(thrown);

		// Log it
		log(record);
	}

	/**
	 * Log a LogRecord.
	 *
	 * @param record
	 *            the log record to publish
	 */
	public void log(LogRecord record) {
		/*
		 * Implementation comment: this method is the one that really logs. Other methods (log(), fine(), severe())
		 * after just wrappers around this method. These other methods should create a correct LogRecord and call the
		 * present method.
		 */
		Level effectiveLevel = this.effectiveLevel;
		assert effectiveLevel != null; // see comment in constructor
		if (effectiveLevel.intValue() <= record.getLevel().intValue()) {
			// Add our name to this record
			if (record.getLoggerName() == null) {
				record.setLoggerName(this.name);
			}
			// else : we got this record from somebody else, we are are polite and keep their name

			// Pass the record to our handlers
			passToHandlers(record);

			// Forward to our parent
			Logger parent = this.parent;
			if (parent != null) {
				parent.logFromChild(record);
			}
		}
	}

	/**
	 * Children will call this method when they want this logger to log something for them. This logger is not supposed
	 * to change the log record content. It also doesn't check the level and assumes that its children really want this
	 * record to be published.
	 *
	 * @param record
	 *            the log record to publish
	 */
	private void logFromChild(LogRecord record) {
		// Pass the record to our handlers, as is
		passToHandlers(record);

		// Forward to our parent
		Logger parent = this.parent;
		if (parent != null) {
			parent.logFromChild(record);
		}
	}

	/**
	 * When levels are compliant, use this method in order to publish a message with every registered Handler. It
	 * creates an intermediate LogRecord to do so.
	 *
	 * @param record
	 *            the LogRecord to log.
	 */
	private void passToHandlers(LogRecord record) {
		for (Handler handler : this.handlers) {
			handler.publish(record);
		}
	}

	/**
	 * Remove the given Handler from the Logger's list. If the Handlers is null or is not know by the Logger, then the
	 * method does nothing.
	 *
	 * @param handler
	 *            the Handler to remove
	 */
	public void removeHandler(Handler handler) {
		this.handlers.remove(handler);
	}

	/**
	 * Set the specified Level to the Logger. This level specifies which message levels will be logged by this Logger
	 * from now on.
	 * <p>
	 * Level.OFF can be used to turn off logging.
	 *
	 * @param newLevel
	 *            the new Level for the Logger (may be null)
	 */
	public void setLevel(@Nullable Level newLevel) {
		this.publicLevel = newLevel;
		updateLevel();
	}

	/* public */void setParent(Logger parent) {
		if (parent == null) {
			throw new NullPointerException("Parent cannot be null");
		}
		if (this != parent) {
			// Oracle doesn't check this ? --> infinite loop ?
			this.parent = parent;
			updateLevel();
		}
	}

	/**
	 * Log a SEVERE message.
	 * <p>
	 * If the logger is currently enabled for the SEVERE message level then the given message is forwarded to all the
	 * registered output handler objects.
	 *
	 * @param msg
	 *            message to log.
	 */
	public void severe(@Nullable String msg) {
		log(Level.SEVERE, msg);
	}

	/**
	 * Log a that a method is terminating by throwing an Exception. The level FINER is used.
	 *
	 * @param sourceClass
	 *            name of the class that requested the log
	 * @param sourceMethod
	 *            name of the method inside this class
	 * @param thrown
	 *            the throwable being thrown
	 */
	public void throwing(String sourceClass, String sourceMethod, Throwable thrown) {
		log(Level.FINER, sourceClass + "." + sourceMethod, thrown);
	}

	/**
	 * Update the effective level using the parent logger and the public level.
	 *
	 * @param newLevel
	 *            the new public level
	 */
	private void updateLevel() {
		if (this.publicLevel == null) {
			Logger parent = this.parent;
			if (parent != null) {
				this.effectiveLevel = parent.getLevel();
			}
		} else {
			this.effectiveLevel = this.publicLevel;
		}
	}

	/**
	 * Log a WARNING message.
	 * <p>
	 * If the logger is currently enabled for the WARNING message level then the given message is forwarded to all the
	 * registered output handler objects.
	 *
	 * @param msg
	 *            message to log.
	 */
	public void warning(@Nullable String msg) {
		log(Level.WARNING, msg);
	}

}
