package org.xbib.logging.log4j;

import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.spi.ExtendedLogger;
import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.spi.LoggerRegistry;
import org.xbib.logging.LogContext;

/**
 * Represents a {@link LoggerContext} backed by a {@link LogContext}.
 */
class XbibLoggerContext implements LoggerContext {

    private final LogContext logContext;

    private final Object externalContext;

    private final LoggerRegistry<XbibLogger> loggerRegistry = new LoggerRegistry<>();

    private final ConcurrentMap<String, Object> map = new ConcurrentHashMap<>();

    /**
     * Creates a new logger context.
     *
     * @param logContext      the LogManager context to use
     * @param externalContext the external context provided
     */
    XbibLoggerContext(final LogContext logContext, final Object externalContext) {
        this.logContext = logContext;
        this.externalContext = externalContext;
    }

    @Override
    public Object getExternalContext() {
        return externalContext;
    }

    @Override
    public ExtendedLogger getLogger(final String name) {
        return getLogger(name, null);
    }

    @Override
    public ExtendedLogger getLogger(final String name, final MessageFactory messageFactory) {
        XbibLogger logger = loggerRegistry.getLogger(name, messageFactory);
        if (logger != null) {
            AbstractLogger.checkMessageFactory(logger, messageFactory);
            return logger;
        }
        logger = new XbibLogger(logContext.getLogger(name), messageFactory);
        loggerRegistry.putIfAbsent(name, messageFactory, logger);
        return loggerRegistry.getLogger(name, messageFactory);
    }

    @Override
    public boolean hasLogger(final String name) {
        return loggerRegistry.hasLogger(name);
    }

    @Override
    public boolean hasLogger(final String name, final MessageFactory messageFactory) {
        return loggerRegistry.hasLogger(name, messageFactory);
    }

    @Override
    public boolean hasLogger(final String name, final Class<? extends MessageFactory> messageFactoryClass) {
        return loggerRegistry.hasLogger(name, messageFactoryClass);
    }

    @Override
    public Object getObject(final String key) {
        return map.get(key);
    }

    @Override
    public Object putObject(final String key, final Object value) {
        return map.put(key, value);
    }

    @Override
    public Object putObjectIfAbsent(final String key, final Object value) {
        return map.putIfAbsent(key, value);
    }

    @Override
    public Object removeObject(final String key) {
        return map.remove(key);
    }

    @Override
    public boolean removeObject(final String key, final Object value) {
        return map.remove(key, value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(logContext, loggerRegistry, externalContext);
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof XbibLoggerContext other)) {
            return false;
        }
        return Objects.equals(logContext, other.logContext) && Objects.equals(loggerRegistry, other.loggerRegistry)
                && Objects.equals(externalContext, other.externalContext);
    }

    /**
     * Returns the LogManager log context associated with the log4j logger context.
     *
     * @return the LogManager log context
     */
    LogContext getLogContext() {
        return logContext;
    }
}
