/**
 * Copyright (c) 2011-2012 EBM WebSourcing, 2012-2015 Linagora
 * 
 * This program/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 (at your
 * option) any later version.
 * 
 * This program/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 program/library; If not, see <http://www.gnu.org/licenses/>
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.log.handler;

import static org.ow2.petals.commons.log.PetalsExecutionContext.FLOW_INSTANCE_ID_PROPERTY_NAME;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Properties;
import java.util.logging.ErrorManager;
import java.util.logging.Handler;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.StreamHandler;

import org.ow2.petals.commons.log.Level;

import com.ebmwebsourcing.easycommons.properties.PropertiesException;
import com.ebmwebsourcing.easycommons.properties.PropertiesHelper;
import com.ebmwebsourcing.easycommons.thread.ExecutionContext;

/**
 * <p>
 * The contextual file handler of Petals to use as log handler ({@link Handler})
 * to print monitoring traces with following configuration properties:
 * <ul>
 * <li>the property <code>basedir</code>, default value set to
 * <code>${petals.log.dir}</code>,</li>
 * <li>the property <code>subdir</code>, default value set to
 * <code>flow-monitoring</code>,</li>
 * <li>the property <code>logFilename</code> is set to <code>petals.log</code></li>
 * </ul>
 * </p>
 */
public class PetalsFileHandler extends StreamHandler {

    protected static final int offValue = Level.OFF.intValue();

    protected static final String BASEDIR_PROPERTY_NAME = ".basedir";

    protected static final String PETALS_LOG_DIR_PROPERTY_NAME = "petals.log.dir";

    protected static final String DEFAULT_BASEDIR = "${" + PETALS_LOG_DIR_PROPERTY_NAME + "}";

    protected static final String FLOW_LOG_SUBDIR_PROPERTY_NAME = ".flows-subdir";

    protected static final String DEFAULT_FLOW_LOG_SUBDIR = "flow-monitoring";

    protected static final String LOGFILENAME_PROPERTY_NAME = ".logFilename";

    protected static final String DEFAULT_LOGFILENAME = "petals.log";

    protected static final String ERROR_LOGFILENAME_WITH_SEPARATOR = "The log filename cannot contain a file separator. Default value used: "
            + DEFAULT_LOGFILENAME;

    /**
     * The root directory of log files. Default value: {@value #DEFAULT_BASEDIR}
     */
    protected File basedir;

    /**
     * The root directory of sub directory of flows. If the value of the
     * configuration property {@value #FLOW_LOG_SUBDIR_PROPERTY_NAME} is not an
     * absolute path, the flow sub dir is defined as sub-dir of {@link #basedir}
     * .
     */
    protected final File flowsSubdir;

    protected String logfilename;

    /**
     * <p>
     * Create and configure the log handler from {@link LogManager} properties.
     * </p>
     * <p>
     * Note: If configuration error are detected, error are printed on the error
     * output
     * </p>
     */
    public PetalsFileHandler() {

        super();

        final LogManager manager = LogManager.getLogManager();

        // Initialize the basedir
        final String basedirPropertyValue = manager.getProperty(this.getClass().getName()
                + BASEDIR_PROPERTY_NAME);
        if (basedirPropertyValue != null && !basedirPropertyValue.trim().isEmpty()) {
            try {
                this.basedir = new File(PropertiesHelper.resolveString(basedirPropertyValue,
                        System.getProperties()));
            } catch (final PropertiesException e) {
                System.err.println(this.getClass().getName() + ": the value of "
                        + this.getClass().getName() + BASEDIR_PROPERTY_NAME + " ("
                        + basedirPropertyValue + ") is invalid, default value used: "
                        + e.getMessage());
                try {
                    this.basedir = new File(PropertiesHelper.resolveString(DEFAULT_BASEDIR,
                            System.getProperties()));
                } catch (final PropertiesException e1) {
                    System.err.println(this.getClass().getName() + ": the default value of "
                            + this.getClass().getName() + BASEDIR_PROPERTY_NAME + " ("
                            + DEFAULT_BASEDIR + ") is invalid (bug): " + e.getMessage());
                }
            }
        } else {
            try {
                this.basedir = new File(PropertiesHelper.resolveString(DEFAULT_BASEDIR,
                        System.getProperties()));
            } catch (final PropertiesException e) {
                System.err.println(this.getClass().getName() + ": the default value of "
                        + this.getClass().getName() + BASEDIR_PROPERTY_NAME + " ("
                        + DEFAULT_BASEDIR + ") is invalid (bug): " + e.getMessage());
            }
        }
        if (!this.basedir.isAbsolute()) {
            System.err.println(this.getClass().getName() + ": " + this.getClass().getName()
                    + BASEDIR_PROPERTY_NAME + " (" + this.basedir.toString()
                    + ") is not an absolute directory. Default value used");
            try {
                this.basedir = new File(PropertiesHelper.resolveString(DEFAULT_BASEDIR,
                        System.getProperties()));
            } catch (final PropertiesException e) {
                System.err.println(this.getClass().getName() + ": the default value of "
                        + this.getClass().getName() + BASEDIR_PROPERTY_NAME + " ("
                        + DEFAULT_BASEDIR + ") is invalid (bug): " + e.getMessage());
            }
        }
        if (!this.basedir.exists()) {
            if (!this.basedir.mkdirs()) {
                System.err.println(this.getClass().getName()
                        + ": Unable to create the base directory '"
                        + this.basedir.getAbsolutePath());
            }
        }

        // Initialize the flow sub dir
        final String flowsSubdirPropertyValue = manager.getProperty(this.getClass().getName()
                + FLOW_LOG_SUBDIR_PROPERTY_NAME);
        if (flowsSubdirPropertyValue != null && !flowsSubdirPropertyValue.trim().isEmpty()) {
            String flowsSubDirValue;
            try {
                flowsSubDirValue = PropertiesHelper.resolveString(flowsSubdirPropertyValue,
                        System.getProperties());
            } catch (final PropertiesException e) {
                System.err.println(this.getClass().getName() + ": the value of "
                        + this.getClass().getName() + FLOW_LOG_SUBDIR_PROPERTY_NAME + " ("
                        + flowsSubdirPropertyValue + ") is invalid, default value used: "
                        + e.getMessage());
                flowsSubDirValue = DEFAULT_FLOW_LOG_SUBDIR;
            }
            final File flowsSubDirPath = new File(flowsSubDirValue);
            if (flowsSubDirPath.isAbsolute()) {
                this.flowsSubdir = flowsSubDirPath;
            } else {
                this.flowsSubdir = new File(this.basedir, flowsSubDirValue);
            }
        } else {
            this.flowsSubdir = new File(this.basedir, DEFAULT_FLOW_LOG_SUBDIR);
        }

        final String logfilename = manager.getProperty(this.getClass().getName()
                + LOGFILENAME_PROPERTY_NAME);
        if (logfilename != null && !logfilename.trim().isEmpty()) {
            if (logfilename.contains(File.separator)) {
                System.err.println(ERROR_LOGFILENAME_WITH_SEPARATOR);
                this.logfilename = DEFAULT_LOGFILENAME;
            } else {
                try {
                    this.logfilename = PropertiesHelper.resolveString(logfilename,
                            System.getProperties());
                } catch (final PropertiesException e) {
                    System.err.println(this.getClass().getName() + ": the value of "
                            + this.getClass().getName() + LOGFILENAME_PROPERTY_NAME + " ("
                            + logfilename + ") is invalid, default value used: " + e.getMessage());
                    this.logfilename = DEFAULT_LOGFILENAME;
                }
            }
        } else {
            this.logfilename = DEFAULT_LOGFILENAME;
        }
    }

    @Override
    public synchronized void publish(final LogRecord record) {

        int levelValue = getLevel().intValue();
        if (record.getLevel().intValue() >= levelValue && levelValue != offValue) {

            this.printLog(record);
        }
    }

    protected void printLog(final LogRecord record) {

        try {
            final File logFile = this.getMonitLogFile();

            /*
             * FileOutputStream can generate java.io.IOException: Bad file
             * descriptor if this FOS has been closed too early. So, the publish
             * method must keep the file locked until the publishing process is
             * completed, in order to close() the opening logging location file
             * before another thread open new FOS base on a bad file descriptor
             * (because logging location file descriptor is already used).
             */
            if (logFile != null) {
                try {
                    // open the File Output Stream in append mode (FOS) to the
                    // log file
                    final OutputStream fos = new FileOutputStream(logFile, true);

                    // Set the FOS in order to allow the StreamHandler to write
                    // in this FOS.
                    super.setOutputStream(fos);

                    // publish the log record.
                    try {
                        super.publish(record);
                    } finally {
                        super.close();
                    }

                } catch (final FileNotFoundException e) {
                    /*
                     * We can not throw an exception here, so report the
                     * exception to any registered ErrorManager.
                     */
                    reportError(null, e, ErrorManager.OPEN_FAILURE);
                }
            }
        } catch (PropertiesException pe) {
            reportError(pe.getMessage(), pe, ErrorManager.GENERIC_FAILURE);
        }
    }

    /**
     * Get the log file where to write the MONIT log record.
     * 
     * @return The {@link File} where the log record must be written.
     * @throws PropertiesException
     *             Thrown if a property cannot be resolved.
     */
    private File getMonitLogFile() throws PropertiesException {
        final Properties contextualProperties = PropertiesHelper.flattenProperties(ExecutionContext
                .getProperties());

        final File flowInstanceSubdir = this.getFlowSubDir(contextualProperties);
        final File logFileDir;
        if (flowInstanceSubdir == null) {
            logFileDir = this.basedir;
        } else {
            logFileDir = flowInstanceSubdir;
        }
        logFileDir.mkdirs();

        return new File(logFileDir, this.logfilename);
    }

    /**
     * Return the root directory containing flow instance directories where are
     * logged MONIT traces
     * 
     * @param contextualProperties
     * @return The root directory of the flow traces or <code>null</code> if no
     *         flow instance is in progress.
     */
    private File getFlowSubDir(final Properties contextualProperties) {
        final String flowInstanceId = contextualProperties
                .getProperty(FLOW_INSTANCE_ID_PROPERTY_NAME);
        if (flowInstanceId != null) {
            return new File(this.flowsSubdir, flowInstanceId);
        } else {
            return null;
        }
    }

}
