/*
 * Copyright 2020 Martin Winandy
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package org.tinylog.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.ServiceLoader;

import org.tinylog.core.providers.LoggingProvider;
import org.tinylog.core.providers.NopLoggingProvider;

/**
 * Storage for {@link Configuration}, {@link Hook Hooks}, and {@link LoggingProvider}.
 */
public final class Framework {

	private final Configuration configuration;
	private final Collection<Hook> hooks;
	private final Object loggingProviderMutex = new Object();

	private LoggingProvider loggingProvider;
	private boolean running;

	/**
	 * Loads the configuration from default properties file and hooks from service files.
	 */
	public Framework() {
		this.configuration = loadConfiguration();
		this.hooks = loadHooks(getClassLoader());
	}

	/**
	 * Initializes the framework with a custom configuration and custom hooks.
	 *
	 * @param configuration Configuration to store
	 * @param hooks Hooks to store
	 */
	public Framework(Configuration configuration, Collection<Hook> hooks) {
		this.configuration = configuration;
		this.hooks = hooks;
	}

	/**
	 * Gets the class loader for loading resources and services from the classpath.
	 *
	 * @return A valid and existing class loader instance
	 */
	public ClassLoader getClassLoader() {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		return classLoader == null ? Framework.class.getClassLoader() : classLoader;
	}

	/**
	 * Gets the stored configuration.
	 *
	 * @return The stored configuration
	 */
	public Configuration getConfiguration() {
		return configuration;
	}

	/**
	 * Gets the logging provider from the stored configuration.
	 *
	 * @return The logging provider implementation
	 */
	public LoggingProvider getLoggingProvider() {
		if (loggingProvider == null) {
			synchronized (loggingProviderMutex) {
				loadLoggingProvider();
			}
		}

		return loggingProvider;
	}

	/**
	 * Registers a new {@link Hook}.
	 *
	 * @param hook Hook to register
	 */
	public void registerHook(Hook hook) {
		hooks.add(hook);
	}

	/**
	 * Removes a registered {@link Hook}.
	 *
	 * @param hook Hook to unregister
	 */
	public void removeHook(Hook hook) {
		hooks.remove(hook);
	}

	/**
	 * Starts the framework and calls the start up method on all registered hooks, if the framework is not yet started.
	 */
	public void startUp() {
		synchronized (hooks) {
			running = true;

			for (Hook hook : hooks) {
				hook.startUp();
			}
		}

	}

	/**
	 * Stops the framework and calls the shut down method on all registered hooks, if the framework is not yet shut
	 * down.
	 */
	public void shutDown() {
		synchronized (hooks) {
			if (running) {
				running = false;

				for (Hook hook : hooks) {
					hook.shutDown();
				}
			}
		}
	}

	/**
	 * Creates a new {@link Configuration} and loads the settings from default properties file if available.
	 *
	 * @return The created and pre-filled configuration
	 */
	private static Configuration loadConfiguration() {
		Configuration configuration = new Configuration();
		configuration.loadPropertiesFile();
		return configuration;
	}

	/**
	 * Loads all hooks that are registered as {@link java.util.ServiceLoader service} in {@code META-INF/services}.
	 *
	 * @param classLoader The class loader to use for loading all services
	 * @return All found hooks
	 */
	private static Collection<Hook> loadHooks(ClassLoader classLoader) {
		Collection<Hook> hooks = new ArrayList<Hook>();
		for (Hook hook : ServiceLoader.load(Hook.class, classLoader)) {
			hooks.add(hook);
		}
		return hooks;
	}

	/**
	 * Freezes the stored configuration and loads the logging provider.
	 */
	private void loadLoggingProvider() {
		configuration.freeze();
		loggingProvider = new NopLoggingProvider();
	}

}

@@ -663,16 +663,39 @@
 ection;%0A
+import java.util.List;%0A
 import j
@@ -753,16 +753,22 @@
 oviders.
+Bundle
 LoggingP
@@ -810,19 +810,16 @@
 oviders.
-Nop
 LoggingP
@@ -818,24 +818,197 @@
 LoggingProvi
+der;%0Aimport org.tinylog.core.providers.LoggingProviderBuilder;%0Aimport org.tinylog.core.providers.NopLoggingProvider;%0Aimport org.tinylog.core.providers.NopLoggingProviderBuil
 der;%0A%0A/**%0A *
@@ -4471,53 +4471,858 @@
 ();%0A
-%09%09loggingProvider = new NopLoggingProvider();
+%0A%09%09List%3CString%3E names = configuration.getList(%22backend%22);%0A%09%09List%3CLoggingProvider%3E providers = new ArrayList%3CLoggingProvider%3E();%0A%0A%09%09for (LoggingProviderBuilder builder : ServiceLoader.load(LoggingProviderBuilder.class, getClassLoader())) %7B%0A%09%09%09if ((names.isEmpty() && !(builder instanceof NopLoggingProviderBuilder))%0A%09%09%09%09%09%7C%7C names.contains(builder.getName())) %7B%0A%09%09%09%09providers.add(builder.create(this));%0A%09%09%09%7D%0A%09%09%7D%0A%0A%09%09if (providers.isEmpty()) %7B%0A%09%09%09System.err.println(%22No logging back end could be found in the classpath. Therefore, no log entries will be %22%0A%09%09%09%09%09+ %22output. Please add tinylog-impl or any other logging back end for outputting log entries.%22);%0A%09%09%09loggingProvider = new NopLoggingProvider();%0A%09%09%7D else if (providers.size() == 1) %7B%0A%09%09%09loggingProvider = providers.get(0);%0A%09%09%7D else %7B%0A%09%09%09loggingProvider = new BundleLoggingProvider(providers);%0A%09%09%7D
 %0A%09%7D%0A

Load logging provider from configuration