// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.logger.backend;

import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.FileHandler;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import io.sentry.IScopes;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import com.google.common.flogger.backend.system.SimpleLogRecord;
import com.google.common.flogger.backend.LogData;
import java.util.logging.LogRecord;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import com.hypixel.hytale.logger.sentry.HytaleSentryHandler;
import java.util.function.BiConsumer;
import javax.annotation.Nonnull;
import java.util.Map;
import java.io.PrintStream;
import java.util.logging.Level;
import java.util.function.Function;
import com.google.common.flogger.backend.LoggerBackend;

public class HytaleLoggerBackend extends LoggerBackend
{
    public static Function<String, Level> LOG_LEVEL_LOADER;
    public static final PrintStream REAL_SOUT;
    public static final PrintStream REAL_SERR;
    private static final Map<String, HytaleLoggerBackend> CACHE;
    private static final HytaleLoggerBackend ROOT_LOGGER;
    private static final int OFF_VALUE;
    private final String name;
    private final HytaleLoggerBackend parent;
    @Nonnull
    private Level level;
    private BiConsumer<Level, Level> onLevelChange;
    @Nullable
    private HytaleSentryHandler sentryHandler;
    private boolean propagateSentryToParent;
    @Nonnull
    private CopyOnWriteArrayList<CopyOnWriteArrayList<LogRecord>> subscribers;
    
    protected HytaleLoggerBackend(final String name) {
        this.level = Level.INFO;
        this.propagateSentryToParent = true;
        this.subscribers = new CopyOnWriteArrayList<CopyOnWriteArrayList<LogRecord>>();
        this.name = name;
        this.parent = HytaleLoggerBackend.ROOT_LOGGER;
    }
    
    protected HytaleLoggerBackend(final String name, final HytaleLoggerBackend parent) {
        this.level = Level.INFO;
        this.propagateSentryToParent = true;
        this.subscribers = new CopyOnWriteArrayList<CopyOnWriteArrayList<LogRecord>>();
        this.name = name;
        this.parent = parent;
    }
    
    @Override
    public String getLoggerName() {
        return this.name;
    }
    
    @Nonnull
    public Level getLevel() {
        return this.level;
    }
    
    @Override
    public boolean isLoggable(@Nonnull final Level lvl) {
        final int levelValue = this.level.intValue();
        return lvl.intValue() >= levelValue && levelValue != HytaleLoggerBackend.OFF_VALUE;
    }
    
    @Override
    public void log(@Nonnull final LogData data) {
        this.log(SimpleLogRecord.create(data));
    }
    
    @Override
    public void handleError(@Nonnull final RuntimeException error, @Nonnull final LogData badData) {
        this.log(SimpleLogRecord.error(error, badData));
    }
    
    public void log(@Nonnull final LogRecord logRecord) {
        this.log(logRecord, false);
    }
    
    public void log(@Nonnull final LogRecord logRecord, boolean sentryHandled) {
        if (this.sentryHandler != null && !sentryHandled && logRecord.getThrown() != null && !SkipSentryException.hasSkipSentry(logRecord.getThrown())) {
            this.sentryHandler.publish(logRecord);
            sentryHandled = true;
        }
        if (!this.propagateSentryToParent && !sentryHandled && logRecord.getThrown() != null) {
            sentryHandled = true;
        }
        if (this.parent != null) {
            this.parent.log(logRecord, sentryHandled);
        }
        else {
            HytaleFileHandler.INSTANCE.log(logRecord);
            HytaleConsole.INSTANCE.publish(logRecord);
            for (int i = 0; i < this.subscribers.size(); ++i) {
                this.subscribers.get(i).add(logRecord);
            }
        }
    }
    
    public static void subscribe(final CopyOnWriteArrayList<LogRecord> subscriber) {
        if (HytaleLoggerBackend.ROOT_LOGGER.subscribers.contains(subscriber)) {
            return;
        }
        HytaleLoggerBackend.ROOT_LOGGER.subscribers.add(subscriber);
    }
    
    public static void unsubscribe(final CopyOnWriteArrayList<LogRecord> subscriber) {
        if (!HytaleLoggerBackend.ROOT_LOGGER.subscribers.contains(subscriber)) {
            return;
        }
        HytaleLoggerBackend.ROOT_LOGGER.subscribers.remove(subscriber);
    }
    
    @Nonnull
    public HytaleLoggerBackend getSubLogger(final String name) {
        final HytaleLoggerBackend hytaleLoggerBackend = new HytaleLoggerBackend(this.name + "][" + name, this);
        hytaleLoggerBackend.loadLogLevel();
        return hytaleLoggerBackend;
    }
    
    public void setSentryClient(@Nullable final IScopes scope) {
        if (scope != null) {
            (this.sentryHandler = new HytaleSentryHandler(scope)).setLevel(Level.ALL);
        }
        else {
            this.sentryHandler = null;
        }
    }
    
    public void setPropagatesSentryToParent(final boolean propagate) {
        this.propagateSentryToParent = propagate;
    }
    
    public void setOnLevelChange(final BiConsumer<Level, Level> onLevelChange) {
        this.onLevelChange = onLevelChange;
    }
    
    public void setLevel(@Nonnull final Level newLevel) {
        final Level old = this.level;
        this.level = newLevel;
        if (this.onLevelChange != null && !Objects.equals(old, newLevel)) {
            this.onLevelChange.accept(old, newLevel);
        }
    }
    
    public void loadLogLevel() {
        if (this.name == null || HytaleLoggerBackend.LOG_LEVEL_LOADER == null) {
            return;
        }
        final Level level = HytaleLoggerBackend.LOG_LEVEL_LOADER.apply(this.name);
        if (level != null) {
            this.setLevel(level);
        }
    }
    
    public static void loadLevels(@Nonnull final List<Map.Entry<String, Level>> list) {
        for (final Map.Entry<String, Level> e : list) {
            getLogger(e.getKey()).setLevel(e.getValue());
        }
    }
    
    public static void reloadLogLevels() {
        HytaleLoggerBackend.CACHE.values().forEach(HytaleLoggerBackend::loadLogLevel);
    }
    
    public static HytaleLoggerBackend getLogger() {
        return HytaleLoggerBackend.ROOT_LOGGER;
    }
    
    public static HytaleLoggerBackend getLogger(@Nonnull final String name) {
        if (name.isEmpty()) {
            return getLogger();
        }
        final HytaleLoggerBackend logger = HytaleLoggerBackend.CACHE.computeIfAbsent(name, HytaleLoggerBackend::new);
        logger.loadLogLevel();
        return logger;
    }
    
    @Nonnull
    public static HytaleLoggerBackend getLogger(final String name, final BiConsumer<Level, Level> onLevelChange) {
        final HytaleLoggerBackend logger = HytaleLoggerBackend.CACHE.computeIfAbsent(name, HytaleLoggerBackend::new);
        logger.setOnLevelChange(onLevelChange);
        logger.loadLogLevel();
        return logger;
    }
    
    public static void setIndent(final int indent) {
        HytaleConsole.INSTANCE.getFormatter().maxModuleName = indent;
        final FileHandler fileHandler = HytaleFileHandler.INSTANCE.getFileHandler();
        if (fileHandler != null) {
            ((HytaleLogFormatter)fileHandler.getFormatter()).maxModuleName = indent;
        }
    }
    
    public static boolean isJunitTest() {
        for (final StackTraceElement element : Thread.currentThread().getStackTrace()) {
            if (element.getClassName().startsWith("org.junit.")) {
                return true;
            }
        }
        return false;
    }
    
    public static void rawLog(final String message) {
        HytaleLoggerBackend.ROOT_LOGGER.log(new RawLogRecord(Level.ALL, message));
    }
    
    static {
        REAL_SOUT = System.out;
        REAL_SERR = System.err;
        CACHE = new ConcurrentHashMap<String, HytaleLoggerBackend>();
        ROOT_LOGGER = new HytaleLoggerBackend("Hytale", null);
        OFF_VALUE = Level.OFF.intValue();
    }
    
    public static class RawLogRecord extends LogRecord
    {
        public RawLogRecord(@Nonnull final Level level, final String msg) {
            super(level, msg);
        }
    }
}
