package be.iminds.ilabt.jfed.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.security.InvalidParameterException;

/**
 * Slf4jHelpers
 *   These are NOT normal slf4j logger implementation like for example LogBack provides.
 *   These helpers are meant to help use the org.slf4j.Logger interface for other purposes than intended.
 *
 *   IMPORTANT: To simplify things, this does not support MARKER (except for the multiplexer)
 */
public class Slf4jHelper {
    private static final Logger LOG = LoggerFactory.getLogger(Slf4jHelper.class);

    public enum Level {
        TRACE(0), DEBUG(1), INFO(2), WARN(3), ERROR(4);

        private final int level;

        Level(int level) {
            this.level = level;
        }

        public int getLevel() {
            return level;
        }

        public boolean includes(Level level) {
            return this.getLevel() <= level.getLevel();
        }
    }

    private Slf4jHelper() { throw new RuntimeException("This is static only class"); }

    public static org.slf4j.Logger createPrintStreamLogger(PrintStream printStream, Level level) {
        return new PrintStreamLogger(printStream, level);
    }
    public static org.slf4j.Logger createPrintWriterLogger(PrintWriter printWriter, Level level) {
        return new PrintWriterLogger(printWriter, level);
    }

    public static org.slf4j.Logger createMultiplexLogger(org.slf4j.Logger...loggers) {
        return new MultiplexLogger(loggers);
    }







    private static class PrintStreamLogger extends AbstractSlf4JLoggerImpl {
        private final PrintStream out;

        public PrintStreamLogger(PrintStream out) {
            this(out, Level.INFO);
        }
        public PrintStreamLogger(PrintStream out, Level level) {
            super(level);
            this.out = out;
        }

        @Override
        public String getName() {
            return "Test Output Logger";
        }

        @Override
        protected void handle(Level msgLevel, String message, Throwable t) {
            if (!level.includes(msgLevel))
                return;

            if (t != null)
                message += "\n"+TextUtil.exceptionToString(t);

            out.println(getName()+" - "+msgLevel+" - "+message);
        }
    }

    private static class PrintWriterLogger extends AbstractSlf4JLoggerImpl {
        private final PrintWriter out;

        public PrintWriterLogger(PrintWriter out) {
            this(out, Level.INFO);
        }
        public PrintWriterLogger(PrintWriter out, Level level) {
            super(level);
            this.out = out;
        }

        @Override
        public String getName() {
            return "Test Output Logger";
        }

        @Override
        protected void handle(Level msgLevel, String message, Throwable t) {
            if (!level.includes(msgLevel))
                return;

            if (t != null)
                message += "\n"+TextUtil.exceptionToString(t);

            out.println(getName()+" - "+msgLevel+" - "+message);
        }
    }

    public static abstract class AbstractSlf4JLoggerImpl implements org.slf4j.Logger {
        protected final Level level;

        public AbstractSlf4JLoggerImpl(Level level) {
            if (level == null)
                throw new InvalidParameterException("Logger level may not be null");
            this.level = level;
        }

        protected abstract void handle(Level level, String message, Throwable t);


        @Override public boolean isTraceEnabled() { return level.includes(Level.TRACE); }
        @Override public boolean isTraceEnabled(Marker marker) { return level.includes(Level.TRACE); }
        @Override public boolean isDebugEnabled() { return level.includes(Level.DEBUG); }
        @Override public boolean isDebugEnabled(Marker marker) { return level.includes(Level.DEBUG); }
        @Override public boolean isInfoEnabled() { return level.includes(Level.INFO); }
        @Override public boolean isInfoEnabled(Marker marker) { return level.includes(Level.INFO); }
        @Override public boolean isWarnEnabled() { return level.includes(Level.WARN); }
        @Override public boolean isWarnEnabled(Marker marker) { return level.includes(Level.WARN); }
        @Override public boolean isErrorEnabled() { return level.includes(Level.ERROR); }
        @Override public boolean isErrorEnabled(Marker marker) { return level.includes(Level.ERROR); }


        private boolean handledExceptionInLogging = false;
        private void callHandle(Level level, String message, Throwable t) {
            try {
                handle(level, message, t);
            } catch (Exception e) {
                //make sure an error here does not disrupt the code using the Logger!
                if (!handledExceptionInLogging) {
                    LOG.error("Warning: Exception in "+getClass()+".handle(). Logging will possibly stop working. (NOTE: this error is reported only once.)", e);
                    handledExceptionInLogging = true;
                }
            }
        }


        @Override
        public void trace(String s) {
            callHandle(Level.TRACE, s, null);
        }

        @Override
        public void trace(String s, Object o) {
            callHandle(Level.TRACE, String.format(s, o), null);
        }

        @Override
        public void trace(String s, Object o, Object o1) {
            callHandle(Level.TRACE, String.format(s, o, o1), null);
        }

        @Override
        public void trace(String s, Object... objects) {
            callHandle(Level.TRACE, String.format(s, objects), null);
        }

        @Override
        public void trace(String s, Throwable t) {
            callHandle(Level.TRACE, s, t);
        }

        @Override
        public void trace(Marker m, String s) {
            callHandle(Level.TRACE, s, null);
        }

        @Override
        public void trace(Marker m, String s, Object o) {
            callHandle(Level.TRACE, String.format(s, o), null);
        }

        @Override
        public void trace(Marker m, String s, Object o, Object o1) {
            callHandle(Level.TRACE, String.format(s, o, o1), null);
        }

        @Override
        public void trace(Marker m, String s, Object... objects) {
            callHandle(Level.TRACE, String.format(s, objects), null);
        }

        @Override
        public void trace(Marker m, String s, Throwable throwable) {
            callHandle(Level.TRACE, s + "\n" + TextUtil.exceptionToString(throwable), null);
        }


        @Override
        public void debug(String s) {
            callHandle(Level.DEBUG, s, null);
        }

        @Override
        public void debug(String s, Object o) {
            callHandle(Level.DEBUG, String.format(s, o), null);
        }

        @Override
        public void debug(String s, Object o, Object o1) {
            callHandle(Level.DEBUG, String.format(s, o, o1), null);
        }

        @Override
        public void debug(String s, Object... objects) {
            callHandle(Level.DEBUG, String.format(s, objects), null);
        }

        @Override
        public void debug(String s, Throwable t) {
            callHandle(Level.DEBUG, s, t);
        }

        @Override
        public void debug(Marker m, String s) {
            callHandle(Level.DEBUG, s, null);
        }

        @Override
        public void debug(Marker m, String s, Object o) {
            callHandle(Level.DEBUG, String.format(s, o), null);
        }

        @Override
        public void debug(Marker m, String s, Object o, Object o1) {
            callHandle(Level.DEBUG, String.format(s, o, o1), null);
        }

        @Override
        public void debug(Marker m, String s, Object... objects) {
            callHandle(Level.DEBUG, String.format(s, objects), null);
        }

        @Override
        public void debug(Marker m, String s, Throwable throwable) {
            callHandle(Level.DEBUG, s + "\n" + TextUtil.exceptionToString(throwable), null);
        }


        @Override
        public void info(String s) {
            callHandle(Level.INFO, s, null);
        }

        @Override
        public void info(String s, Object o) {
            callHandle(Level.INFO, String.format(s, o), null);
        }

        @Override
        public void info(String s, Object o, Object o1) {
            callHandle(Level.INFO, String.format(s, o, o1), null);
        }

        @Override
        public void info(String s, Object... objects) {
            callHandle(Level.INFO, String.format(s, objects), null);
        }

        @Override
        public void info(String s, Throwable t) {
            callHandle(Level.INFO, s, t);
        }

        @Override
        public void info(Marker m, String s) {
            callHandle(Level.INFO, s, null);
        }

        @Override
        public void info(Marker m, String s, Object o) {
            callHandle(Level.INFO, String.format(s, o), null);
        }

        @Override
        public void info(Marker m, String s, Object o, Object o1) {
            callHandle(Level.INFO, String.format(s, o, o1), null);
        }

        @Override
        public void info(Marker m, String s, Object... objects) {
            callHandle(Level.INFO, String.format(s, objects), null);
        }

        @Override
        public void info(Marker m, String s, Throwable throwable) {
            callHandle(Level.INFO, s + "\n" + TextUtil.exceptionToString(throwable), null);
        }


        @Override
        public void warn(String s) {
            callHandle(Level.WARN, s, null);
        }

        @Override
        public void warn(String s, Object o) {
            callHandle(Level.WARN, String.format(s, o), null);
        }

        @Override
        public void warn(String s, Object o, Object o1) {
            callHandle(Level.WARN, String.format(s, o, o1), null);
        }

        @Override
        public void warn(String s, Object... objects) {
            callHandle(Level.WARN, String.format(s, objects), null);
        }

        @Override
        public void warn(String s, Throwable t) {
            callHandle(Level.WARN, s, t);
        }

        @Override
        public void warn(Marker m, String s) {
            callHandle(Level.WARN, s, null);
        }

        @Override
        public void warn(Marker m, String s, Object o) {
            callHandle(Level.WARN, String.format(s, o), null);
        }

        @Override
        public void warn(Marker m, String s, Object o, Object o1) {
            callHandle(Level.WARN, String.format(s, o, o1), null);
        }

        @Override
        public void warn(Marker m, String s, Object... objects) {
            callHandle(Level.WARN, String.format(s, objects), null);
        }

        @Override
        public void warn(Marker m, String s, Throwable throwable) {
            callHandle(Level.WARN, s + "\n" + TextUtil.exceptionToString(throwable), null);
        }


        @Override
        public void error(String s) {
            callHandle(Level.ERROR, s, null);
        }

        @Override
        public void error(String s, Object o) {
            callHandle(Level.ERROR, String.format(s, o), null);
        }

        @Override
        public void error(String s, Object o, Object o1) {
            callHandle(Level.ERROR, String.format(s, o, o1), null);
        }

        @Override
        public void error(String s, Object... objects) {
            callHandle(Level.ERROR, String.format(s, objects), null);
        }

        @Override
        public void error(String s, Throwable t) {
            callHandle(Level.ERROR, s, t);
        }

        @Override
        public void error(Marker m, String s) {
            callHandle(Level.ERROR, s, null);
        }

        @Override
        public void error(Marker m, String s, Object o) {
            callHandle(Level.ERROR, String.format(s, o), null);
        }

        @Override
        public void error(Marker m, String s, Object o, Object o1) {
            callHandle(Level.ERROR, String.format(s, o, o1), null);
        }

        @Override
        public void error(Marker m, String s, Object... objects) {
            callHandle(Level.ERROR, String.format(s, objects), null);
        }

        @Override
        public void error(Marker m, String s, Throwable throwable) {
            callHandle(Level.ERROR, s + "\n" + TextUtil.exceptionToString(throwable), null);
        }
    }


    private static class MultiplexLogger implements Logger {
        private final Logger[] loggers;

        private MultiplexLogger(Logger[] loggers) {
            this.loggers = loggers;
        }

        @Override
        public String getName() {
            return "MultiplexLogger";
        }

        @Override public boolean isTraceEnabled() {
            for (Logger l : loggers) if (l.isTraceEnabled()) return true;
            return false;
        }
        @Override public boolean isTraceEnabled(Marker marker) {
            for (Logger l : loggers) if (l.isTraceEnabled(marker)) return true;
            return false;
        }

        @Override public boolean isDebugEnabled() {
            for (Logger l : loggers) if (l.isDebugEnabled()) return true;
            return false;
        }
        @Override public boolean isDebugEnabled(Marker marker) {
            for (Logger l : loggers) if (l.isDebugEnabled(marker)) return true;
            return false;
        }

        @Override public boolean isInfoEnabled() {
            for (Logger l : loggers) if (l.isInfoEnabled()) return true;
            return false;
        }
        @Override public boolean isInfoEnabled(Marker marker) {
            for (Logger l : loggers) if (l.isInfoEnabled(marker)) return true;
            return false;
        }

        @Override public boolean isWarnEnabled() {
            for (Logger l : loggers) if (l.isWarnEnabled()) return true;
            return false;
        }
        @Override public boolean isWarnEnabled(Marker marker) {
            for (Logger l : loggers) if (l.isWarnEnabled(marker)) return true;
            return false;
        }

        @Override public boolean isErrorEnabled() {
            for (Logger l : loggers) if (l.isErrorEnabled()) return true;
            return false;
        }
        @Override public boolean isErrorEnabled(Marker marker) {
            for (Logger l : loggers) if (l.isErrorEnabled(marker)) return true;
            return false;
        }




        @Override
        public void trace(String s) {
            for (Logger l : loggers)
                l.trace(s);
        }

        @Override
        public void trace(String s, Object o) {
            for (Logger l : loggers)
                l.trace(s, o);
        }

        @Override
        public void trace(String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.trace(s, o, o1);
        }

        @Override
        public void trace(String s, Object... objects) {
            for (Logger l : loggers)
                l.trace(s, objects);
        }

        @Override
        public void trace(String s, Throwable t) {
            for (Logger l : loggers)
                l.trace(s, t);
        }

        @Override
        public void trace(Marker m, String s) {
            for (Logger l : loggers)
                l.trace(m, s);
        }

        @Override
        public void trace(Marker m, String s, Object o) {
            for (Logger l : loggers)
                l.trace(m, s, o);
        }

        @Override
        public void trace(Marker m, String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.trace(m, s, o, o1);
        }

        @Override
        public void trace(Marker m, String s, Object... objects) {
            for (Logger l : loggers)
                l.trace(m, s, objects);
        }

        @Override
        public void trace(Marker m, String s, Throwable t) {
            for (Logger l : loggers)
                l.trace(m, s, t);
        }



        @Override
        public void debug(String s) {
            for (Logger l : loggers)
                l.debug(s);
        }

        @Override
        public void debug(String s, Object o) {
            for (Logger l : loggers)
                l.debug(s, o);
        }

        @Override
        public void debug(String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.debug(s, o, o1);
        }

        @Override
        public void debug(String s, Object... objects) {
            for (Logger l : loggers)
                l.debug(s, objects);
        }

        @Override
        public void debug(String s, Throwable t) {
            for (Logger l : loggers)
                l.debug(s, t);
        }

        @Override
        public void debug(Marker m, String s) {
            for (Logger l : loggers)
                l.debug(m, s);
        }

        @Override
        public void debug(Marker m, String s, Object o) {
            for (Logger l : loggers)
                l.debug(m, s, o);
        }

        @Override
        public void debug(Marker m, String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.debug(m, s, o, o1);
        }

        @Override
        public void debug(Marker m, String s, Object... objects) {
            for (Logger l : loggers)
                l.debug(m, s, objects);
        }

        @Override
        public void debug(Marker m, String s, Throwable t) {
            for (Logger l : loggers)
                l.debug(m, s, t);
        }



        @Override
        public void info(String s) {
            for (Logger l : loggers)
                l.info(s);
        }

        @Override
        public void info(String s, Object o) {
            for (Logger l : loggers)
                l.info(s, o);
        }

        @Override
        public void info(String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.info(s, o, o1);
        }

        @Override
        public void info(String s, Object... objects) {
            for (Logger l : loggers)
                l.info(s, objects);
        }

        @Override
        public void info(String s, Throwable t) {
            for (Logger l : loggers)
                l.info(s, t);
        }

        @Override
        public void info(Marker m, String s) {
            for (Logger l : loggers)
                l.info(m, s);
        }

        @Override
        public void info(Marker m, String s, Object o) {
            for (Logger l : loggers)
                l.info(m, s, o);
        }

        @Override
        public void info(Marker m, String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.info(m, s, o, o1);
        }

        @Override
        public void info(Marker m, String s, Object... objects) {
            for (Logger l : loggers)
                l.info(m, s, objects);
        }

        @Override
        public void info(Marker m, String s, Throwable t) {
            for (Logger l : loggers)
                l.info(m, s, t);
        }



        @Override
        public void warn(String s) {
            for (Logger l : loggers)
                l.warn(s);
        }

        @Override
        public void warn(String s, Object o) {
            for (Logger l : loggers)
                l.warn(s, o);
        }

        @Override
        public void warn(String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.warn(s, o, o1);
        }

        @Override
        public void warn(String s, Object... objects) {
            for (Logger l : loggers)
                l.warn(s, objects);
        }

        @Override
        public void warn(String s, Throwable t) {
            for (Logger l : loggers)
                l.warn(s, t);
        }

        @Override
        public void warn(Marker m, String s) {
            for (Logger l : loggers)
                l.warn(m, s);
        }

        @Override
        public void warn(Marker m, String s, Object o) {
            for (Logger l : loggers)
                l.warn(m, s, o);
        }

        @Override
        public void warn(Marker m, String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.warn(m, s, o, o1);
        }

        @Override
        public void warn(Marker m, String s, Object... objects) {
            for (Logger l : loggers)
                l.warn(m, s, objects);
        }

        @Override
        public void warn(Marker m, String s, Throwable t) {
            for (Logger l : loggers)
                l.warn(m, s, t);
        }






        @Override
        public void error(String s) {
            for (Logger l : loggers)
                l.error(s);
        }

        @Override
        public void error(String s, Object o) {
            for (Logger l : loggers)
                l.error(s, o);
        }

        @Override
        public void error(String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.error(s, o, o1);
        }

        @Override
        public void error(String s, Object... objects) {
            for (Logger l : loggers)
                l.error(s, objects);
        }

        @Override
        public void error(String s, Throwable t) {
            for (Logger l : loggers)
                l.error(s, t);
        }

        @Override
        public void error(Marker m, String s) {
            for (Logger l : loggers)
                l.error(m, s);
        }

        @Override
        public void error(Marker m, String s, Object o) {
            for (Logger l : loggers)
                l.error(m, s, o);
        }

        @Override
        public void error(Marker m, String s, Object o, Object o1) {
            for (Logger l : loggers)
                l.error(m, s, o, o1);
        }

        @Override
        public void error(Marker m, String s, Object... objects) {
            for (Logger l : loggers)
                l.error(m, s, objects);
        }

        @Override
        public void error(Marker m, String s, Throwable t) {
            for (Logger l : loggers)
                l.error(m, s, t);
        }
    }
}
