/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2007 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.lib.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

import org.objectweb.util.monolog.Monolog;
import org.objectweb.util.monolog.api.Handler;
import org.objectweb.util.monolog.api.HandlerFactory;
import org.objectweb.util.monolog.api.Level;
import org.objectweb.util.monolog.api.LevelFactory;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.LoggerFactory;
import org.objectweb.util.monolog.api.MonologFactory;
import org.objectweb.util.monolog.file.DottedStringTools;
import org.objectweb.util.monolog.file.monolog.PropertiesConfAccess;
import org.objectweb.util.monolog.wrapper.printwriter.PrintWriterImpl;
import org.ow2.jonas.lib.bootstrap.JProp;

/**
 *  This class provides utility method for using Monolog.
 * @author Philippe Coq.
 * @author Sebastien Chassande-Barrioz sebastien.chassande@inrialpes.fr
 * @author Philippe Durieux
 * @author Florent Benoit & Ludovic Bert
 * @author Adriana Danes : change MBean implementation model from inheritance to delegation
 */
public class Log {

    /**
     * name of the config file - initialized by the configure method.
     */
    private static String configFile = "trace";

    /**
     * This class is actually a wrapper on the unique loggerFactory
     */
    private static LoggerFactory lf = null;

    /**
     * Properties used for Logging system.
     * Kept here mainly for debugging
     */
    private static Properties props = null;

    /**
     * Config file name for the Logging system.
     */
    private static String configFileName = null;

    // List of topic used inside JOnAS
    // This list may be not exhaustive since many topics can be added for
    // different purposes or to refine debugging.
    // To get the complete list, use "jonas admin -t"
    public static final String JONAS_ADMIN_PREFIX = "org.ow2.jonas.admin";
    public static final String JONAS_BOOTSTRAP_PREFIX = "org.ow2.jonas.bootstrap";
    public static final String JONAS_CLUSTER_DAEMON = "org.ow2.jonas.cluster.daemon";
    public static final String JONAS_DB_PREFIX = "org.ow2.jonas.db";
    public static final String JONAS_DBM_PREFIX = "org.ow2.jonas.dbm";
    public static final String JONAS_DEPLOY_PREFIX = "org.ow2.jonas.deployment";
    public static final String JONAS_DISCOVERY_PREFIX = "org.ow2.jonas.discovery";
    public static final String JONAS_EAR_PREFIX = "org.ow2.jonas.ear";
    public static final String JONAS_EJB_PREFIX = "org.ow2.jonas.ejb";
    public static final String SPY_LOGGER_NAME = "org.ow2.jonas.ee.jdbc.sql";
    public static final String JONAS_JAXR_PREFIX = "org.ow2.jonas.ee.scout";
    public static final String JONAS_GENBASE_PREFIX = "org.ow2.jonas.generators.genbase";
    public static final String JONAS_GENIC_PREFIX = "org.ow2.jonas.generators.genic";
    public static final String JONAS_GENIC_VELOCITY_PREFIX = "org.ow2.jonas.generators.genic.velocity";
    public static final String JONAS_WSGEN_PREFIX = "org.ow2.jonas.generators.wsgen";
    public static final String JONAS_WSGEN_EWS_PREFIX = "org.ow2.jonas.generators.wsgen.ews";
    public static final String JONAS_JCA_PREFIX = "org.ow2.jonas.jca";
    public static final String JONAS_JMX_PREFIX = "org.ow2.jonas.jmx";
    public static final String JONAS_WORK_MGR_PREFIX = "org.ow2.jonas.lib.work";
    public static final String JONAS_LOADER_PREFIX = "org.ow2.jonas.loader";
    public static final String JONAS_MAIL_PREFIX = "org.ow2.jonas.mail";
    public static final String JONAS_MANAGEMENT_PREFIX = "org.ow2.jonas.management";
    public static final String JONAS_MANAGEMENT_EVENT_PREFIX = "org.ow2.jonas.management.event";
    public static final String JONAS_NAMING_PREFIX = "org.ow2.jonas.naming";
    public static final String JONAS_PROPCTX_PREFIX = "org.ow2.jonas.propagation";
    public static final String JONAS_REGISTRY_PREFIX = "org.ow2.jonas.registry";
    public static final String JONAS_SECURITY_PREFIX = "org.ow2.jonas.security";
    public static final String JONAS_CSIV2_SECURITY_PREFIX = "org.ow2.jonas.security.csiv2";
    public static final String JONAS_CSIV2_DETAILS_SECURITY_PREFIX = "org.ow2.jonas.security.csiv2_details";
    public static final String JONAS_JACC_SECURITY_PREFIX = "org.ow2.jonas.security.jacc";
    public static final String JONAS_WS_SECURITY_PREFIX = "org.ow2.jonas.security.ws";
    public static final String JONAS_SERVER_PREFIX = "org.ow2.jonas.server";
    public static final String JONAS_WEB_PREFIX = "org.ow2.jonas.web";
    public static final String JONAS_DEPLOY_WORK_PREFIX = "org.ow2.jonas.work";
    public static final String JONAS_TESTS_PREFIX = "org.ow2.jonas_tests";

    /**
     * Output tag for the handler
     */
    private static final String HANDLER_OUTPUT_ATTRIBUTE = "output";

    /**
     * Output tag which must be replaced by JOnAS
     */
    private static final String AUTOMATIC_CONFIG = "automatic";

    /**
     * Directory for the logs $JONAS_BASE/logs
     */
    private static final String LOG_DIRECTORY = "logs";

    /**
     * Format of a timestamp log.
     */
    private static final String TIMESTAMP_FORMAT = "-yyyy-MM-dd";

    /**
     * Suffix of a log file.
     */
    private static final String SUFFIX_LOGFILE = "log";

    /**
     * Default logger factory
     */
    private static final String DEFAULT_LOGGERFACTORY = "org.objectweb.util.monolog.wrapper.javaLog.LoggerFactory";

    /**
     * Is in ClientContainer ?
     */
    private static boolean clientcontainer = false;

    /**
     * JProp fully qualified Classname
     */
    private static final String JPROP_CLASSNAME = "org.ow2.jonas.lib.bootstrap.JProp";

    /**
     * Default Private Constructor for utility classes
     */
    private Log() { }

    /**
     * Configure Logger.
     * @param file The configuration file for monolog (usually: trace.properties)
     */
    public static void configure(final String file) {
        configFile = file;
        getLoggerFactory();
        if (!clientcontainer) {
            // TODO find a good place for this initialization
            // to allow using Monolog when want traces of P6Spy usage in SQL requests
            //P6SpyLogger.logger = lf.getLogger(SPY_LOGGER_NAME);
            // -----------
            // Comment out call until JONAS uses Log4j
            // TraceTm.configure(lf);
        }
    }



    /**
     * Use a Monolog class ?
     */
    private static String JMX_HANDLER_TYPE = "jmx";

    /**
     * @return a jmx handler. If several, return the first one.
     * If none, return null;
     */
    public static Handler getJmxHandler() {
        Handler[] handlers = Monolog.getMonologFactory().getHandlers();
        for (int i = 0; i < handlers.length; i++) {
            Handler handler = handlers[i];
            if (handler.getType().equals(JMX_HANDLER_TYPE)) {
                return handler;
            }
        }
        return null;
    }

    /**
     * Return a given Monolog handler
     * @param handlerName the name of the handler
     * @return the corresponding Monolog handler
     */
    public static Handler getHandler(final String handlerName) {
        Handler[] handlers = Monolog.getMonologFactory().getHandlers();
        for (int i = 0; i < handlers.length; i++) {
            Handler handler = handlers[i];
            String handType = handler.getType();
            String handName = handler.getName();
            String[] attNames = handler.getAttributeNames();
        }
        return Monolog.getMonologFactory().getHandler(handlerName);

    }

    /**
     * It returns the unique LoggerFactory used in JOnAS.
     * initialize it if not already done.
     * @return the unique LoggerFactory used in JOnAS.
     */
    public static synchronized LoggerFactory getLoggerFactory() {
        if (lf == null) {
            try {
                // Detect client case = no JONAS_BASE
                if (isClient()) {
                    clientcontainer = true;
                    InputStream is = null;

                    // Is there a specific file to use instead the default name
                    String traceClient = System.getProperty("jonas.client.trace.file");
                    if (traceClient != null) {
                        try {
                            is = new FileInputStream(new File(traceClient));
                        } catch (FileNotFoundException fne) {
                            System.err.println("Can not init logger with the supplied file '" + traceClient + "'" +
                                    ", this file doesn't exist. Init with default values.");

                        }
                    } else {
                        //Client
                        //Is there a trace properties file in the classpath ?
                        is = Thread.currentThread().getContextClassLoader().getResourceAsStream("traceclient.properties");
                    }
                    if (is == null) {
                        // no traceclient.properties found
                        // using a default configuration
                        props = new Properties();
                        props.put("log.config.classname", DEFAULT_LOGGERFACTORY);
                        props.put("logger.root.level", "INFO");
                    } else {
                        // use the configuration of the properties file.
                        props = new Properties();
                        props.load(is);
                    }
                    lf = Monolog.getMonologFactory(props);
                    return lf;
                }
                // server case
                // get jonas configuration properties
                Object jProp = null;
                try {
                    jProp = JProp.getInstance();
                    // Run jonas
                    // Create a JProp object for the configFile
                    JProp.getInstance(configFile);
                } catch (Exception e) {
                    System.err.println("Can't read jonas.properties. Check that you have defined a $JONAS_BASE variable");
                }


                // load properties from config file
                props = JProp.getInstance(configFile).getConfigFileEnv();
                configFileName = JProp.getInstance(configFile).getPropFileName();

                String jonasBase = null;
                String nameOfServer = null;
                if (jProp != null) {
                    jonasBase = JProp.getJonasBase();
                    nameOfServer = ((JProp) jProp).getValue("jonas.name", "jonas");
                } else {
                    // Set default
                    jonasBase = ".";
                    nameOfServer = "jonas";
                }

                // Before sending props, replace AUTOMATIC pattern by a file in $JONAS_BASE/logs/
                // File are named <JOnAS_server_name>TIMESTAMP_FORMAT.SUFFIX_LOGFILE
                File logDirFile = new File(jonasBase + File.separator + LOG_DIRECTORY);

                // Properties may be updated if AUTOMATIC_CONFIG is used
                // Also, path to the log files are set by using a relative path to JONAS_BASE/logs
                Properties updatedProps = (Properties) props.clone();

                //Create a date format
                SimpleDateFormat sdf = new SimpleDateFormat(TIMESTAMP_FORMAT);
                String date = sdf.format(new Date());
                // How many AUTOMATIC field have been replaced
                int nbAut = 0;

                File logFile = null;
                // Handlers alredy updated
                List<String> handlersUpdated = new ArrayList<String>();



                for (Enumeration keys = props.keys(); keys.hasMoreElements();) {
                    String key = (String) keys.nextElement();
                    if (key == null) {
                        continue;
                    }

                    if (key.startsWith(PropertiesConfAccess.HANDLER_FIELD)) {
                        String temp = DottedStringTools.getFirst(key);

                        if (temp == null) {
                            continue;
                        }

                        temp = DottedStringTools.getEnd(key);
                        if (temp == null) {
                            continue;
                        }

                        String handlerName = DottedStringTools.getBegin(temp);

                        // handler already updated
                        if (handlersUpdated.contains(handlerName)) {
                            continue;
                        }

                        // Add handler as handled
                        handlersUpdated.add(handlerName);

                        // Now, get the type and output for the given handler
                        String stringType = props.getProperty(PropertiesConfAccess.HANDLER_FIELD + PropertiesConfAccess.DOT + handlerName + PropertiesConfAccess.DOT + PropertiesConfAccess.HANDLER_TYPE_ATTRIBUTE, null);
                        String stringOutput = props.getProperty(PropertiesConfAccess.HANDLER_FIELD + PropertiesConfAccess.DOT + handlerName + PropertiesConfAccess.DOT + HANDLER_OUTPUT_ATTRIBUTE, null);
                        // Got the type and the output
                        // --> Compare with the pattern AUTOMATIC_CONFIG for the FILE type.
                        if ((stringType != null) && (stringOutput != null)) {
                            if (PropertiesConfAccess.HANDLER_TYPE_ATTRIBUTE_FILE_VALUE.equalsIgnoreCase(stringType)
                                || (PropertiesConfAccess.HANDLER_TYPE_ATTRIBUTE_ROLLING_FILE_VALUE.equalsIgnoreCase(stringType))) {
                                if (stringOutput.equalsIgnoreCase(AUTOMATIC_CONFIG)) {
                                    // OK, it's match
                                    String fileName = null;

                                    if (nbAut > 0) {
                                        // Add an unique entry by adding nbAut value.
                                        fileName = nameOfServer + date + PropertiesConfAccess.DOT + nbAut + PropertiesConfAccess.DOT + SUFFIX_LOGFILE;
                                    } else {
                                        fileName = nameOfServer + date + PropertiesConfAccess.DOT + SUFFIX_LOGFILE;
                                    }

                                    // Create the directory if it doesn't exists
                                    if (!logDirFile.exists()) {
                                        logDirFile.mkdirs();
                                    }
                                    logFile = new File(logDirFile, fileName);

                                    updatedProps.setProperty(PropertiesConfAccess.HANDLER_FIELD + PropertiesConfAccess.DOT + handlerName + PropertiesConfAccess.DOT + HANDLER_OUTPUT_ATTRIBUTE, logFile.getPath());
                                    nbAut++;

                                }
                                else {
                                    // not automatic -- change relative file name to be relative to JONAS_BASE/logs
                                    logFile = new File(stringOutput);
                                    if (!logFile.isAbsolute()) {
                                        // file name is relative so try to create in JONAS_BASE/logs
                                        // Create the directory if it doesn't exists
                                        if (!logDirFile.exists()) {
                                            logDirFile.mkdirs();
                                        }
                                        logFile = new File(logDirFile, stringOutput);
                                        if (stringOutput.endsWith(".automatic") ||
                                            stringOutput.endsWith(".auto")) {
                                            nbAut++;
                                            String autoStr = logFile.getPath().substring(0, logFile.getPath().lastIndexOf('.')) + date +
                                                             PropertiesConfAccess.DOT + nbAut + PropertiesConfAccess.DOT + SUFFIX_LOGFILE;
                                            logFile = new File(autoStr);
                                        }
                                        updatedProps.setProperty(PropertiesConfAccess.HANDLER_FIELD + PropertiesConfAccess.DOT + handlerName + PropertiesConfAccess.DOT + HANDLER_OUTPUT_ATTRIBUTE, logFile.getPath());
                                    } else {
                                        File logFileParent = logFile.getParentFile();
                                        if (!logFileParent.exists()) {
                                            logFileParent.mkdirs();
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                // Instanciate the LoggerFactory
                String b = props.getProperty("log.config.classname", null);
                if (b == null) {
                    System.err.println("Malformed configuration log file: log.config.classname not available");
                    return null;
                }
                lf = (LoggerFactory) Class.forName(b).newInstance();

                // Configure the LoggerFactory with the updated properties
                PropertiesConfAccess.load(updatedProps, lf, (HandlerFactory) lf, (LevelFactory) lf);

                //Register the monolog factory in Monolog
                Monolog.monologFactory = (MonologFactory) lf;
            } catch (Exception e) {
                System.err.println("Cannot get LoggerFactory:" + e);
                e.printStackTrace();
            }
        }
        return lf;
    }

    /**
     * @return Returns true if we are in a client context
     */
    private static boolean isClient() {
        boolean hasJonasBase = System.getProperty("jonas.base") != null;
        boolean hasJonasRoot = System.getProperty("jonas.root") != null;
        boolean hasJProp = true;
        try {
            Log.class.getClassLoader().loadClass(JPROP_CLASSNAME);
        } catch (ClassNotFoundException cnfe) {
            hasJProp = false;
        }
        // a server has the 3 params, all other case are clients case
        return !(hasJProp && hasJonasBase && hasJonasRoot);
    }

    /**
     * Reset logger factory
     */
    public static void reset() {
        lf = null;
    }

    /**
     * Returns the standard PrintWriter associated to the logger defined by
     * its topic.
     * This is mainly used for DBM and Connectors.
     */
    public static PrintWriter getLogWriter(final String topic) {
        // TODO : should not create a new object at each call
        return new PrintWriterImpl(getLogger(topic), getLoggerFactory());
    }

    /**
     * Shortcut that returns the LevelFactory
     */
    public static LevelFactory getLevelFactory() {
        return (LevelFactory) getLoggerFactory();
    }

    /**
     * Shortcut to get the Logger by its topic name.
     * @param topic the topic of the returned logger
     * @return always a logger instance (never null value).
     */
    public static Logger getLogger(final String topic) {
        return getLoggerFactory().getLogger(topic);
    }

    public static Properties getProperties() {
        return props;
    }

    public static String getConfigFileName() {
        return configFileName;
    }

    public static void setConfigFileName(final String configFileName) {
        Log.configFileName = configFileName;
    }

    /**
     * Set the given log level to the specified logger.
     * @param loggerName name of the loger to be configured
     * @param level target level
     * @return the previous level of the logger
     */
    public static Level setComponentLogLevel(final String loggerName,
                                             final Level level) {
        Logger log = Log.getLogger(loggerName);
        Level old = log.getCurrentLevel();
        log.setLevel(level);
        return old;
    }


}
