package org.fxmisc.cssfx.impl.log;

/*
 * #%L
 * CSSFX
 * %%
 * Copyright (C) 2014 CSSFX by Matthieu Brouillard
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */


import java.io.PrintStream;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.logging.Level;

public class CSSFXLogger {
    private static final Clock clock = Clock.systemDefaultZone();

    public static void setLoggerFactory(LoggerFactory factory) {
        loggerFactory = factory;
    }

    public static boolean isInitialized() {
        return loggerFactory != null;
    }
    
    public static void setLogLevel(LogLevel newLevel) {
        logLevel = newLevel;
    }

    public static void console() {
        loggerFactory = CONSOLE_LOGGER_FACTORY;
    }
    
    public static void jul() {
        loggerFactory = JUL_LOGGER_FACTORY;
    }
    
    public static void noop() {
        loggerFactory = NOOP_LOGGER_FACTORY;
    }
    
    /*
     * Always use one of the 2 following methods and never keep static references to a logger. 
     */
    public static Logger logger(String loggerName) {
        return loggerFactory.getLogger(loggerName);
    }
    public static Logger logger(Class<?> loggerClass) {
        return loggerFactory.getLogger(loggerClass);
    }
    
    public static enum LogLevel {
        NONE,
        ERROR,
        WARN,
        INFO,
        DEBUG        
    } 
    
    @FunctionalInterface
    public static interface Logger {
        public void log(LogLevel level, String message, Object ... args);
        public default void info(String message, Object ... args) {log(LogLevel.INFO, message, args);}; 
        public default void debug(String message, Object ... args) {log(LogLevel.DEBUG, message, args);}; 
        public default void warn(String message, Object ... args) {log(LogLevel.WARN, message, args);}; 
        public default void error(String message, Object ... args) {log(LogLevel.ERROR, message, args);}; 
    }

    @FunctionalInterface
    public static interface LoggerFactory {
        public default Logger getLogger(Class<?> loggerClass) {
            return getLogger(loggerClass.getName());
        }
        public Logger getLogger(String loggerName);
    }
    
    private static final Logger CONSOLE_LOGGER = new Logger() {
        private void printLastThrowableArgument(PrintStream output, Object ...args) {
            if (args.length > 0 && Throwable.class.isAssignableFrom(args[args.length-1].getClass())) {
                Throwable t = (Throwable) args[args.length-1];
                t.printStackTrace(output);
            }
        }
        
        @Override
        public void log(LogLevel askedLevel, String message, Object ... args) {
            if (askedLevel.ordinal() <= logLevel.ordinal()) {
                List<Object> params = new ArrayList<Object>(args.length + 2);
                params.add(clock.instant());
                params.add(askedLevel);
                params.addAll(Arrays.asList(args));
                final PrintStream writer = (LogLevel.ERROR.equals(askedLevel))?System.err:System.out;
                writer.println(String.format("%s [%5s] " + message, params.toArray()));
                printLastThrowableArgument(writer, args);
            }
        };
    };
    
    private final static LoggerFactory JUL_LOGGER_FACTORY = new LoggerFactory() {
        @SuppressWarnings("serial")
        Map<LogLevel, Level> levelMapping = new HashMap<CSSFXLogger.LogLevel, Level>(){{
            put(LogLevel.NONE, Level.OFF);
            put(LogLevel.ERROR, Level.SEVERE);
            put(LogLevel.WARN, Level.WARNING);
            put(LogLevel.INFO, Level.INFO);
            put(LogLevel.DEBUG, Level.FINE);
        }};
        @Override
        public Logger getLogger(String loggerName) {
            java.util.logging.Logger delegate = java.util.logging.Logger.getLogger(loggerName);
            
            return new Logger() {
                @Override
                public void log(LogLevel askedLevel, String message, Object ... args) {
                    Level julLevel = levelMapping.get(askedLevel);
                    
                    Supplier<String> log = () -> String.format(message, args);
                    if (args.length > 0 && Throwable.class.isAssignableFrom(args[args.length-1].getClass())) {
                        Throwable t = (Throwable) args[args.length-1];
                        delegate.log(julLevel, t, log);
                    } else {
                        delegate.log(julLevel, log);
                    }
                };
            };
        }
    };  

    private final static LoggerFactory CONSOLE_LOGGER_FACTORY = (s) -> CONSOLE_LOGGER;  
    
    private final static Logger NOOP_LOGGER = (level, message, args) -> {};  
    private final static LoggerFactory NOOP_LOGGER_FACTORY = (s) -> NOOP_LOGGER;  

    private static LoggerFactory loggerFactory = null;
    private static LogLevel logLevel = LogLevel.INFO;
}

@@ -1735,32 +1735,94 @@
 g loggerName) %7B%0A
+        if (!isInitialized()) %7B%0A            noop();%0A        %7D%0A
         return l
@@ -1911,32 +1911,94 @@
  loggerClass) %7B%0A
+        if (!isInitialized()) %7B%0A            noop();%0A        %7D%0A
         return l

allow for non initialization of CSSFXLogger