/*
 * Copyright 2016 Julien Ponge, René Krell and the IzPack team.
 *
 * 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.
 */

package com.izforge.izpack.util;

import com.izforge.izpack.api.data.Variables;
import com.izforge.izpack.api.substitutor.SubstitutionType;
import com.izforge.izpack.core.resource.ResourceManager;
import com.izforge.izpack.core.substitutor.VariableSubstitutorInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.util.Properties;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;

/**
 * Utility methods for logging
 */
public class LogUtils
{
    private static final String LOGGING_BASE_CONFIGURATION = "/com/izforge/izpack/installer/logging/logging.base.properties";
    private static final String LOGGING_CONFIGURATION = "/com/izforge/izpack/installer/logging/logging.properties";

    private static final String FILEHANDLER_CLASSNAME = FileHandler.class.getName();
    private static final String CONSOLEHANDLER_CLASSNAME = ConsoleHandler.class.getName();


    private static final boolean OVERRIDE =
            System.getProperty("java.util.logging.config.class") == null
            && System.getProperty("java.util.logging.config.file") == null;

    public static void loadConfiguration() throws IOException
    {
        if (OVERRIDE)
        {
            loadConfiguration(LOGGING_CONFIGURATION, null);
        }
    }

    public static void loadConfiguration(final String resource, Variables variables) throws IOException
    {
        if (OVERRIDE)
        {
            InputStream resourceStream = null;
            try
            {
                resourceStream = LogUtils.class.getResourceAsStream(resource);
                loadLoggingResource(resourceStream, variables);
            }
            finally
            {
                IOUtils.closeQuietly(resourceStream);
            }
        }
    }

    public static void loadConfiguration(final Properties configuration) throws IOException
    {
        if (OVERRIDE)
        {
            LogManager manager = LogManager.getLogManager();

            // Merge global logging properties
            InputStream baseResourceStream = null;
            try
            {
                baseResourceStream = LogUtils.class.getResourceAsStream(LOGGING_BASE_CONFIGURATION);
                final Properties baseProps = new Properties();
                baseProps.load(baseResourceStream);
                mergeLoggingConfiguration(configuration, baseProps);
            }
            finally
            {
                IOUtils.closeQuietly(baseResourceStream);
            }

            boolean mkdirs = false;
            String pattern = null;
            if (configuration.getProperty("handlers").contains(FILEHANDLER_CLASSNAME) && manager.getProperty("handlers").contains(FILEHANDLER_CLASSNAME))
            {
                // IzPack maintains just one log file, don't override the existing handler type of it.
                // Special use case: Command line argument -logfile "wins" over the <log-file> tag.
                // Assumption at the moment for optimization: Just FileHandler is used for configurations from install.xml.
                return;
            }
            for (String key : configuration.stringPropertyNames())
            {
                if (key.equals(FILEHANDLER_CLASSNAME + ".pattern"))
                {
                    // Workaround for not normalized file paths, for example ${INSTALL_PATH}/../install_log/name.log
                    // to get them working before creating ${INSTALL_PATH} in the
                    // com.izforge.izpack.installer.unpacker.UnpackerBase.preUnpack phase
                    // otherwise the FileHandler will fail when opening files already in constructor and not recover from that.
                    pattern = FilenameUtils.normalize(configuration.getProperty(key));
                    configuration.setProperty(key, pattern);
                }
                else if (key.equals(FILEHANDLER_CLASSNAME + ".mkdirs"))
                {
                    // This key goes beyond the capabilities of java.util.logging.FileHandler
                    mkdirs = Boolean.parseBoolean(configuration.getProperty(key));
                    configuration.remove(key);
                }
            }
            if (mkdirs && pattern != null)
            {
                FileUtils.forceMkdirParent(new File(pattern));
            }

            // Merge user settings compiled in
            final Properties userProps = new Properties();
            InputStream userPropsStream = LogUtils.class.getResourceAsStream(ResourceManager.getInstallLoggingConfigurationResourceName());
            try
            {
                if (userPropsStream != null)
                {
                    userProps.load(userPropsStream);
                    for (String userPropName : userProps.stringPropertyNames())
                    {
                        if (userPropName.endsWith(".level") && !userPropName.startsWith(FILEHANDLER_CLASSNAME))
                        {
                            String level = userProps.getProperty(userPropName);
                            if (level != null)
                            {
                                configuration.setProperty(userPropName, level);
                            }
                        }
                    }
                }
            }
            finally
            {
                IOUtils.closeQuietly(userPropsStream);
            }

            InputStream defaultResourceStream = null;
            try
            {
                defaultResourceStream = LogUtils.class.getResourceAsStream(LOGGING_CONFIGURATION);
                final Properties defaultProps = new Properties();
                defaultProps.load(defaultResourceStream);
                mergeLoggingConfiguration(configuration, defaultProps);
            }
            finally
            {
                IOUtils.closeQuietly(defaultResourceStream);
            }

            if (Debug.isDEBUG())
            {
                configuration.setProperty(FILEHANDLER_CLASSNAME + ".level", Level.FINE.toString());
                configuration.setProperty(ConsoleHandler.class.getName() + ".level", Level.FINE.toString());
            }

            // Set general log level which acts as filter in front of all handlers
            String fileLevelName = configuration.getProperty(FILEHANDLER_CLASSNAME + ".level", Level.ALL.toString());
            Level fileLevel = Level.ALL;
            if (fileLevelName != null)
            {
                fileLevel = Level.parse(fileLevelName);
            }
            
            String consoleLevelName = configuration.getProperty(CONSOLEHANDLER_CLASSNAME + ".level", Level.INFO.toString());
            Level consoleLevel = Level.INFO;
            if (consoleLevelName != null)
            {
                consoleLevel = Level.parse(consoleLevelName);
            }
            
            configuration.setProperty(".level", (fileLevel.intValue() < consoleLevel.intValue()) ? fileLevelName : consoleLevelName);

            final PipedOutputStream out = new PipedOutputStream();
            final PipedInputStream in = new PipedInputStream(out);
            try
            {
                new Thread(
                        new Runnable()
                        {
                            public void run()
                            {
                                try
                                {
                                    configuration.store(out, null);
                                }
                                catch (IOException e)
                                {
                                    e.printStackTrace();
                                }
                                finally
                                {
                                    IOUtils.closeQuietly(out);
                                }
                            }
                        }
                ).start();

                manager.readConfiguration(in);
            }
            finally
            {
                IOUtils.closeQuietly(in);
            }
        }
    }

    private static void loadLoggingResource(InputStream resourceStream, Variables variables) throws IOException
    {
        if (resourceStream != null)
        {
            InputStream is = variables != null
                    ? new VariableSubstitutorInputStream(
                    resourceStream, null,
                    variables, SubstitutionType.TYPE_JAVA_PROPERTIES, false)
                    : resourceStream;
            final Properties props = new Properties();
            props.load(is);

            loadConfiguration(props);
        }
    }

    private static void mergeLoggingConfiguration(Properties to, Properties from)
    {
        for (String fromName : from.stringPropertyNames())
        {
            String fromValue = from.getProperty(fromName);
            if (fromName.matches("\\.?handlers") && to.containsKey(fromName))
            {
                String oldValue = to.getProperty(fromName);
                if (!fromValue.equals(oldValue))
                {
                    to.setProperty(fromName, oldValue + ", " + fromValue);
                }
                continue;
            }
            if (!to.containsKey(fromName))
            {
                to.setProperty(fromName, fromValue);
            }
        }
    }
}

@@ -3406,32 +3406,81 @@
 erty(%22handlers%22)
+ != null && configuration.getProperty(%22handlers%22)
 .contains(FILEHA

Fix NPE if '<logging level="OFF" />' is configured