package org.xbib.logging;

import java.util.Iterator;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import org.xbib.logging.util.StandardOutputStreams;

/**
 * Mapped diagnostic context. This is a thread-local map used to hold loggable information.
 */
public final class MDC {
    private static final MDCProvider mdcProvider = getDefaultMDCProvider();

    private MDC() {
    }

    static MDCProvider getMDCProvider() {
        return mdcProvider;
    }

    private static MDCProvider getDefaultMDCProvider() {
        return doGetDefaultMDCProvider();
    }

    private static MDCProvider doGetDefaultMDCProvider() {
        final ServiceLoader<MDCProvider> configLoader = ServiceLoader.load(MDCProvider.class, MDC.class.getClassLoader());
        final Iterator<MDCProvider> iterator = configLoader.iterator();
        for (; ; )
            try {
                if (!iterator.hasNext()) {
                    return new ThreadLocalMDC();
                }
                return iterator.next();
            } catch (ServiceConfigurationError | RuntimeException e) {
                StandardOutputStreams.printError(e, "Warning: failed to load MDC Provider: " + e.getMessage());
            }
    }

    /**
     * Get the value for a key, or {@code null} if there is no mapping.
     *
     * @param key the key
     * @return the value
     */
    public static String get(String key) {
        return mdcProvider.get(key);
    }

    /**
     * Get the value for a key, or {@code null} if there is no mapping.
     *
     * @param key the key
     * @return the value
     */
    public static Object getObject(String key) {
        return mdcProvider.getObject(key);
    }

    /**
     * Set the value of a key, returning the old value (if any) or {@code null} if there was none.
     *
     * @param key   the key
     * @param value the new value
     * @return the old value or {@code null} if there was none
     */
    public static String put(String key, String value) {
        return mdcProvider.put(key, value);
    }

    /**
     * Set the value of a key, returning the old value (if any) or {@code null} if there was none.
     *
     * @param key   the key
     * @param value the new value
     * @return the old value or {@code null} if there was none
     */
    public static Object putObject(String key, Object value) {
        return mdcProvider.putObject(key, value);
    }

    /**
     * Remove a key.
     *
     * @param key the key
     * @return the old value or {@code null} if there was none
     */
    public static String remove(String key) {
        return mdcProvider.remove(key);
    }

    /**
     * Remove a key.
     *
     * @param key the key
     * @return the old value or {@code null} if there was none
     */
    public static Object removeObject(String key) {
        return mdcProvider.removeObject(key);
    }

    /**
     * Get a copy of the MDC map. This is a relatively expensive operation.
     *
     * @return a copy of the map
     */
    public static Map<String, String> copy() {
        return mdcProvider.copy();
    }

    /**
     * Get a copy of the MDC map. This is a relatively expensive operation.
     *
     * @return a copy of the map
     */
    public static Map<String, Object> copyObject() {
        return mdcProvider.copyObject();
    }

    /**
     * Checks of the MDC map is empty.
     *
     * @return {@code true} if the MDC map is empty, otherwise {@code false}
     */
    public static boolean isEmpty() {
        return mdcProvider.isEmpty();
    }

    /**
     * Clear the current MDC map.
     */
    public static void clear() {
        mdcProvider.clear();
    }
}
