/**
 * JOnAS
 * Copyright (C) 2010 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.audit.internal.logger.util;

import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;

import org.objectweb.util.monolog.Monolog;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.MonologFactory;


/**
 * A class for manage Monolog loggers.
 * To format log messages, it is possible to specify a pattern on the handlers.
 * A pattern can be composed of elements. This element is prefixed by the % character.
 * Here is the list of the possible items:
 *     l: The level of the message,
 *     t: The topic of the logger,
 *     m: The message,
 *     d: The date of the message,
 *     h: The thread name where the message has been logged,
 *     n: A new line,
 *     O: The class name of the object which invokes the log method (expensive),
 *     M: The method name which invoked the log method (expensive),
 *     L: The line number in the source code where the log method was invoked (expensive).
 *
 * Example : %l - %O{1}.%M: %m%n   =   INFO - MyClass.myMethod(): my message
 *
 * @author Vincent Michaud
 */
public class LogConfigurator {

    /**
     * All recorded properties.
     */
    private Properties properties = new Properties();

    /**
     * The name of the class.
     */
    private String className;

    /**
     * The name of the class prefixed by "logger".
     */
    private String loggerClassName;

    /**
     * Numbers of handlers.
     */
    private int nbHandlers = 0;

    /**
     * Number of topics.
     */
    private int nbTopics = 0;

    /**
     * The Monolog factory.
     */
    private MonologFactory factory = null;

    /**
     * Value use for console handler.
     */
    public static final int SYSTEM_OUT = 0;

    /**
     * Value use for console handler.
     */
    public static final int SYSTEM_ERR = 1;

    /**
     * Value determining the type of the file handler.
     */
    public static final int SIMPLE_FILE = 0;

    /**
     * Value determining the type of the file handler.
     */
    public static final int ROLLING_FILE = 1;

    /**
     * Define an unlimited size of the file.
     */
    public static final long UNLIMITED_SIZE = 0;

    /**
     * Name of the root logger.
     */
    public static final String ROOT_LOGGER = "root";


    /**
     * The Log4j wrapper.
     */
    public static final int WRAPPER_LOG4J = 0;

    /**
     * The JDK wrapper.
     */
    public static final int WRAPPER_JDK = 1;

    /**
     * The Log4jMini wrapper.
     */
    public static final int WRAPPER_LOG4J_MINI = 2;

    /**
     * The PrintWriter wrapper.
     */
    public static final int WRAPPER_PRINTWRITER = 3;

    /**
     * Implementation of wrappers.
     */
    private static final String[] WRAPPERS = {
        Monolog.LOG4J_WRAPPER_CLASS_NAME,
        Monolog.JDK_WRAPPER_CLASS_NAME,
        Monolog.LOG4JMini_WRAPPER_CLASS_NAME,
        "org.objectweb.util.monolog.wrapper.printwriter.LoggerImpl"
    };


    /**
     * Create a logger factory for root logger.
     */
    public LogConfigurator() {
        this.className = ROOT_LOGGER;
        this.loggerClassName  = "logger."  + ROOT_LOGGER;
    }

    /**
     * Create a logger factory for logger specified.
     * @param className Name of the logger
     */
    public LogConfigurator(final String className) {
        this.className = className;
        this.loggerClassName  = "logger."  + className;
    }

    /**
     * Create a logger factory for logger specified.
     * @param className Name of the logger
     * @param level The level of the logger
     * @param inheritance If the logger inherits others parent loggers
     */
    public LogConfigurator(final String className, final String level, final boolean inheritance) {
        this.className = className;
        this.loggerClassName  = "logger."  + className;
        setLoggerInheritance(inheritance);
        setLoggerLevel(level);
    }

    /**
     * Apply configuration specified before and return the corresponding logger.
     * @return The logger
     */
    public Logger apply() {
        if (this.factory == null) {
            this.factory = Monolog.getMonologFactory(this.properties);
        }
        return this.factory.getLogger(this.className);
    }

    /**
     * Apply configuration specified in the configuration file.
     * @param classname Name of the logger
     * @param filename Name of the Monolog configuration file
     * @return The logger
     */
    public static Logger apply(final String classname, final String filename) {
        return Monolog.getMonologFactory(filename).getLogger(classname);
    }

    /**
     * Create a new level for the logger.
     * @param levelName The name of the level
     * @param level The level
     */
    public void createLevel(final String levelName, final String level) {
        putProperty("level." + levelName, level);
    }

    /**
     * Set the level of the logger.
     * The level must be created before.
     * @param level The level
     */
    public void setLoggerLevel(final String level) {
        putProperty(this.loggerClassName + ".level", level);
    }

    /**
     * Set the inheritance of the logger.
     * @param inheritance The inheritance
     */
    public void setLoggerInheritance(final boolean inheritance) {
        if (!inheritance) {
            putProperty(this.loggerClassName + ".additivity", "false");
        } else if (this.properties.remove(this.loggerClassName + ".additivity") != null) {
            this.factory = null;
        }
    }

    /**
     * Define the wrapper used.
     * @param wrapper The wrapper. Value can be LogConfigurator.WRAPPER_LOG4J,
     *                LogConfigurator.WRAPPER_LOG4J_MINI or LogConfigurator.WRAPPER_JDK
     */
    public void setLoggerWrapper(final int wrapper) {
        switch (wrapper) {
            case WRAPPER_LOG4J: case WRAPPER_JDK: case WRAPPER_LOG4J_MINI:
                putProperty(Monolog.MONOLOG_CLASS_NAME, WRAPPERS[wrapper]);
                break;
            default:
                putProperty(Monolog.MONOLOG_CLASS_NAME, WRAPPERS[WRAPPER_PRINTWRITER]);
                break;
        }
    }

    /**
     * Add a new name for the logger.
     * @param name The name added
     */
    public void addLoggerTopic(final String name) {
        putProperty(this.loggerClassName + ".topic." + String.valueOf(this.nbTopics), name);
        this.nbTopics++;
    }


    /**
     * Set a basic handler for the logger.
     * @param type Type of the handler : Console or jmx
     * @param handlerName The name of the handler
     * @param output Type of output (LogConfigurator.SYSTEM_OUT / LogConfigurator.SYSTEM_ERR)
     * @param pattern The pattern of the output message
     * @param level The level of the handler. Can be null.
     */
    private void setBasicHandler(final String type, final String handlerName, final int output,
                                 final String pattern, final String level) {

        String handlerClassName = "handler." + handlerName;
        putProperty(handlerClassName + ".type", type);
        putProperty(handlerClassName + ".pattern", pattern);

        if (output == SYSTEM_OUT) {
            putProperty(handlerClassName + ".output", "System.out");
        } else {
            putProperty(handlerClassName + ".output", "System.err");
        }
        if (level != null) {
            putProperty(handlerClassName + ".level", level);
        }

        putProperty(this.loggerClassName + ".handler." + String.valueOf(this.nbHandlers), handlerName);
        this.nbHandlers++;
    }

    /**
     * Set a console handler for the logger.
     * @param handlerName The name of the handler
     * @param output Type of output (LogConfigurator.SYSTEM_OUT / LogConfigurator.SYSTEM_ERR)
     * @param pattern The pattern of the output message
     * @param level The level of the handler. Can be null.
     */
    public void setConsoleHandler(final String handlerName, final int output,
                                  final String pattern, final String level) {

        setBasicHandler("Console", handlerName, output, pattern, level);
    }

    /**
     * Set a jmx handler for the logger.
     * @param handlerName The name of the handler
     * @param output Type of output (LogConfigurator.SYSTEM_OUT / LogConfigurator.SYSTEM_ERR)
     * @param pattern The pattern of the output message
     * @param level The level of the handler. Can be null.
     */
    public void setJMXHandler(final String handlerName, final int output,
                              final String pattern, final String level) {

        setBasicHandler("jmx", handlerName, output, pattern, level);
    }

    /**
     * Set a file handler for the logger.
     * @param type Type of the file handler (LogConfigurator.SIMPLE_FILE / LogConfigurator.ROLLING_FILE)
     * @param handlerName The name of the handler
     * @param output Path of the output file
     * @param pattern The pattern of the output message
     * @param level The level of the handler. Can be null.
     * @param appendMode Indicates if the logs must be added at the end of the file or not
     * @param fileNumber The number of file to use
     * @param maxSize The maximal size of the file, or LogConfigurator.UNLIMITED_SIZE
     */
    public void setFileHandler(final int type, final String handlerName, final String output,
                               final String pattern, final String level, final boolean appendMode,
                               final int fileNumber, final long maxSize) {

        String handlerClassName = "handler." + handlerName;
        putProperty(handlerClassName + ".pattern", pattern);
        putProperty(handlerClassName + ".output", output);

        if (type == SIMPLE_FILE) {
            putProperty(handlerClassName + ".type", "File");
        } else {
            putProperty(handlerClassName + ".type", "Rollingfile");
        }
        if (level != null) {
            putProperty(handlerClassName + ".level", level);
        }
        if (appendMode) {
            putProperty(handlerClassName + ".appendMode", "");
        } else if (this.properties.remove(handlerClassName + ".appendMode") != null) {
            this.factory = null;
        }
        if (fileNumber > 1) {
            putProperty(handlerClassName + ".fileNumber", String.valueOf(fileNumber));
        }
        if (maxSize > 0) {
            putProperty(handlerClassName + ".maxSize", String.valueOf(maxSize));
        }

        putProperty(this.loggerClassName + ".handler." + String.valueOf(this.nbHandlers), handlerName);
        this.nbHandlers++;
    }


    /**
     * Set a simple file handler for the logger.
     * @param handlerName The name of the handler
     * @param output Path of the output file
     * @param pattern The pattern of the output message
     * @param level The level of the handler. Can be null.
     * @param appendMode Indicates if the logs must be added at the end of the file or not
     */
    public void setSimpleFileHandler(final String handlerName, final String output, final String pattern,
                                     final String level, final boolean appendMode) {
        setFileHandler(SIMPLE_FILE, handlerName, output, pattern, level, appendMode, 1, 0);
    }


    /**
     * Set a rolling file handler for the logger.
     * @param handlerName The name of the handler
     * @param output Path of the output file
     * @param pattern The pattern of the output message
     * @param level The level of the handler. Can be null.
     * @param appendMode Indicates if the logs must be added at the end of the file or not
     * @param fileNumber The number of file to use
     * @param maxSize The maximal size of the file
     */
    public void setRollingFileHandler(final String handlerName, final String output,
                                      final String pattern, final String level, final boolean appendMode,
                                      final int fileNumber, final long maxSize) {
        setFileHandler(ROLLING_FILE, handlerName, output, pattern, level, appendMode, fileNumber, maxSize);
    }

    /**
     * Change the output of the specified handler. If the handler has
     * not been created before, this function has no effect.
     * @param handlerName The name of the handler
     * @param output Path of the output file
     */
    public void changeHandlerOutput(final String handlerName, final String output) {
        putProperty("handler." + handlerName + ".output", output);
    }

    /**
     * Change the level of the specified handler. If the handler has
     * not been created before, this function has no effect.
     * @param handlerName The name of the handler
     * @param level The level of the handler. Can be null.
     */
    public void changeHandlerLevel(final String handlerName, final String level) {
        putProperty("handler." + handlerName + ".level", level);
    }

    /**
     * Change the pattern of the specified handler. If the handler has
     * not been created before, this function has no effect.
     * @param handlerName The name of the handler
     * @param pattern The pattern of the output message
     */
    public void changeHandlerPattern(final String handlerName, final String pattern) {
        putProperty("handler." + handlerName + ".pattern", pattern);
    }

    /**
     * Change the appendMode of the specified handler. If the handler has
     * not been created before or is not a file handler, this function has no effect.
     * @param handlerName The name of the handler
     * @param appendMode Indicates if the logs must be added at the end of the file or not
     */
    public void changeHandlerAppendMode(final String handlerName, final boolean appendMode) {
        if (appendMode) {
            putProperty("handler." + handlerName + ".appendMode", "");
        } else if (this.properties.remove("handler." + handlerName + ".appendMode") != null) {
            this.factory = null;
        }
    }

    /**
     * Change the number of files of the specified handler. If the handler has
     * not been created before or is not a file handler, this function has no effect.
     * @param handlerName The name of the handler
     * @param fileNumber The number of file to use
     */
    public void changeHandlerFileNumber(final String handlerName, final int fileNumber) {
        putProperty("handler." + handlerName + ".fileNumber", String.valueOf(fileNumber));
    }

    /**
     * Change the max size of the file of the specified handler. If the handler has
     * not been created before or is not a file handler, this function has no effect.
     * @param handlerName The name of the handler
     * @param maxSize The maximal size of the file
     */
    public void changeHandlerFileMaxSize(final String handlerName, final long maxSize) {
        putProperty("handler." + handlerName + ".maxSize", String.valueOf(maxSize));
    }

    /**
     * Put a property in the properties map.
     * If properties map has changed, the logger will be recreated.
     * @param key The key of the property
     * @param value The value of the property
     */
    private void putProperty(final String key, final String value) {
        Object last = this.properties.put(key, value);
        if (last == null || !value.equals(last)) {
            this.factory = null;
        }
    }

    /**
     * Get a string representation of the object.
     * @return A string
     */
    @Override
    public String toString() {
        Set<Entry<Object, Object>> setEnt = properties.entrySet();
        StringBuffer buffer = new StringBuffer();
        buffer.append("[\n");
        for (Entry<Object, Object> entry : setEnt) {
            buffer.append("   ");
            buffer.append(entry.getKey().toString());
            buffer.append("  :  ");
            buffer.append(entry.getValue().toString());
            buffer.append("\n");
        }
        buffer.append("]\n");
        return buffer.toString();
    }
}
