/*
 * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
 * Copyright (C) 2014-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.lang.ref.WeakReference;
import java.util.Enumeration;
import java.util.Iterator;

import ej.annotation.Nullable;
import ej.basictool.map.PackedMap;

/**
 * There is one {@link LogManager} object in an application. It maintains properties about loggers and manages the
 * hierarchy of loggers.
 */
public class LogManager {

	private class RootLogger extends Logger {

		protected RootLogger() {
			// The name of a root logger is ""
			super("", null);
			// Its level is given by the LogManager
			setLevel(getDefaultLevel());
		}
	}

	// Global and unique log manager
	private static final LogManager GLOBAL_LOG_MANAGER = newLogManager();

	// The root logger
	/* default */final Logger rootLogger;

	// All the available named Loggers
	private final PackedMap<String, WeakReference<Logger>> namedLoggers;

	/**
	 * Protected constructor. There must be only one {@link LogManager} in an application.
	 */
	protected LogManager() {
		this.namedLoggers = new PackedMap<>();

		this.rootLogger = new RootLogger();
		for (Handler handler : getDefaultHandlers()) {
			assert handler != null; // array returned by getDefaultHandler() shouldn't contain null elements
			this.rootLogger.addHandler(handler);
		}
		addLogger(this.rootLogger);

	}

	private static LogManager newLogManager() {
		return new LogManager();
	}

	private Handler[] getDefaultHandlers() {
		String property = System.getProperty("handlers");
		// FIXME property may contain several class names

		Handler[] handlers = new Handler[0];

		if (property != null) {
			try {
				Class<?> classes = Class.forName(property);
				Handler handler = (Handler) classes.newInstance();
				handlers = new Handler[] { handler };
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}

		return handlers;
	}

	/**
	 * Read the configuration for the default level.
	 *
	 * @return the default level for loggers
	 */
	private Level getDefaultLevel() {
		Level defaultLevel = Level.INFO; // see the reset() method in Java SE
		String property = System.getProperty(".level");

		Level[] predefined = { Level.OFF, Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG, Level.FINE,
				Level.FINER, Level.FINEST, Level.ALL };
		for (Level level : predefined) {
			if (level.getName().equals(property)) {
				defaultLevel = level;
				break;
			}
		}

		return defaultLevel;
	}

	/**
	 * Returns the LogManager object.
	 *
	 * @return the LogManager
	 */
	public static LogManager getLogManager() {
		return GLOBAL_LOG_MANAGER;
	}

	/**
	 * Add a named logger. This method does nothing if the logger had already been added.
	 * <p>
	 * The factory methods from {@link Logger} call this method for each Logger they created.
	 *
	 * @param logger
	 *            the logger to register
	 * @return true if the logger was registered, false if a logger with the same already exists
	 * @throws NullPointerException
	 *             if the logger name is null
	 */
	public boolean addLogger(Logger logger) {
		String name = logger.getName();
		String lowerCaseName = name.toLowerCase();
		WeakReference<Logger> weakLogger = this.namedLoggers.get(lowerCaseName);
		if (weakLogger != null && weakLogger.get() != null) {
			// This logger had already been added and the weak reference has not been garbage-collected
			return false;
		} else {
			// Add this logger (both case: we create a new entry OR we updated an existing entry that has been
			// garbage-collected)
			this.namedLoggers.put(lowerCaseName, new WeakReference<Logger>(logger));
			return true;
		}
	}

	/**
	 * Find a named logger.
	 *
	 * @param name
	 *            name of the logger
	 * @return the matching logger or null if there is no match
	 */
	@Nullable
	public Logger getLogger(String name) {
		String lowerCaseName = name.toLowerCase();
		WeakReference<Logger> weakLogger = this.namedLoggers.get(lowerCaseName);
		if (weakLogger == null) {
			return null; // no entry
		} else {
			return weakLogger.get(); // entry (that may be null)
		}
	}

	/**
	 * Get an enumeration of known logger names.
	 *
	 * @return an enumeration of strings
	 */
	public Enumeration<String> getLoggerNames() {

		return new Enumeration<String>() {
			private final Iterator<String> iterator = LogManager.this.namedLoggers.keySet().iterator();

			@Override
			public boolean hasMoreElements() {
				return this.iterator.hasNext();
			}

			@Override
			public String nextElement() {
				return this.iterator.next();
			}
		};
	}

}
