package logback_bundle.core.strategy;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Marker;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.core.spi.FilterReply;
import logback_bundle.core.TurboFilterStrategy;

public class MultiLevelOverrideStrategy implements TurboFilterStrategy {

    private static final int NO_LEVEL = -1;  // this value must be different from all other Logback levels

    private final Level defaultRootLevel;
    private volatile Callable<Boolean> validator = null;
    private final Map<String, Level> overridingLogLevels = new ConcurrentHashMap<>();  // {partial-logger-name : level}
    private final Map<String, Integer> cachedLoggerLevels = new ConcurrentHashMap<>();  // {full-logger-name : intLevel}

    public MultiLevelOverrideStrategy(Level defaultRootLevel) {
        this.defaultRootLevel = defaultRootLevel;
        reset();
    }

    public void reset() {
        this.validator = null;
        this.overridingLogLevels.clear();
        this.cachedLoggerLevels.clear();
        setRootLogLevel(defaultRootLevel);
    }

    public void removeValidator() {
        this.validator = null;
    }

    public void setValidator(Callable<Boolean> validator) {
        this.validator = validator;
    }

    public Map<String, Level> getLogLevels() {
        return Collections.unmodifiableMap(overridingLogLevels);
    }

    public void removeLogLevel(String partialLoggerName) {
        this.overridingLogLevels.remove(partialLoggerName);
        invalidateCachedLoggerName(partialLoggerName);
    }

    public void setLogLevel(String partialLoggerName, Level logLevel) {
        this.overridingLogLevels.put(partialLoggerName, logLevel);
        invalidateCachedLoggerName(partialLoggerName);
    }

    public void setRootLogLevel(Level logLevel) {
        this.overridingLogLevels.put("", logLevel);
        invalidateCachedLoggerName("");
    }

    private void invalidateCachedLoggerName(String partialLoggerName) {
        for (String fullLoggerName: cachedLoggerLevels.keySet()) {
            if (fullLoggerName.startsWith(partialLoggerName)) {
                cachedLoggerLevels.remove(fullLoggerName);
            }
        }
    }

    /**
     * Cache specified full-logger-name associated with a boolean match against all configured log levels. Return
     * cached value if available, else linear-scan specified log levels for given full-logger-name. Return true on
     * match, false otherwise.
     * @param fullLoggerName full logger name
     * @return integer log-level if found matching, {@link #NO_LEVEL} otherwise
     */
    private Integer cacheLoggerNameMatch(String fullLoggerName) {
        final Integer match = cachedLoggerLevels.get(fullLoggerName);
        if (match != null) {
            return match;
        }
        Integer highestIntLevel = NO_LEVEL;
        for (Map.Entry<String, Level> overridingLogger: overridingLogLevels.entrySet()) {
            final String partialLoggerName = overridingLogger.getKey();
            final Level overridingLevel = overridingLogger.getValue();
            if (fullLoggerName.startsWith(partialLoggerName)) {
                final Integer intLevel = overridingLevel.toInteger();
                if (intLevel > highestIntLevel) {
                    highestIntLevel = intLevel;
                }
            }
        }
        cachedLoggerLevels.put(fullLoggerName, highestIntLevel);
        return highestIntLevel;
    }

    @Override
    public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
        if (validator == null) {
            return FilterReply.NEUTRAL;
        }
        Boolean isValid;
        try {
            isValid = validator.call();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        if (isValid == null || !isValid.booleanValue()) {
            return FilterReply.NEUTRAL;
        }
        final String loggerName = logger.getName();
        final Integer intLevel = cacheLoggerNameMatch(loggerName);
        if (intLevel == NO_LEVEL) {
            return FilterReply.NEUTRAL;
        }
        final Level overridingLevel = Level.toLevel(intLevel);
        if (level.isGreaterOrEqual(overridingLevel)) {
            return FilterReply.ACCEPT;
        }
        return FilterReply.DENY;
    }

}

@@ -2950,26 +2950,56 @@
     
-Integer highestInt
+int matchRank = -1;%0A        Integer matchInteger
 Leve
@@ -3309,134 +3309,110 @@
 ame)
-) %7B%0A                final Integer intLevel = overridingLevel.toInteger();%0A                if (intLevel %3E highestIntLevel) %7B%0A  
+ && partialLoggerName.length() %3E matchRank) %7B%0A                matchRank = partialLoggerName.length();%0A
     
@@ -3427,28 +3427,28 @@
     
-  highestInt
+matchInteger
 Level = 
 intL
@@ -3447,35 +3447,36 @@
 l = 
+overrid
 in
-t
+g
 Level
-;%0A                %7D
+.toInteger();
 %0A   
@@ -3543,26 +3543,28 @@
 erName, 
-highestInt
+matchInteger
 Level);%0A
@@ -3582,18 +3582,20 @@
 urn 
-highestInt
+matchInteger
 Leve

use ranking to match logger names