/**
 * 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.IOException;
import java.util.Properties;
import java.util.logging.ErrorManager;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;

import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.NormalizedMessage;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;

import org.ow2.petals.commons.log.FlowLogData;
import org.ow2.petals.commons.log.LogData;
import org.ow2.petals.commons.log.PetalsExecutionContext;
import org.ow2.petals.commons.log.TraceCode;
import org.ow2.petals.jbi.messaging.exchange.MessageExchangeImpl;

import com.ebmwebsourcing.easycommons.io.FileSystemHelper;
import com.ebmwebsourcing.easycommons.properties.PropertiesException;
import com.ebmwebsourcing.easycommons.properties.PropertiesHelper;
import com.ebmwebsourcing.easycommons.thread.ExecutionContext;
import com.ebmwebsourcing.easycommons.xml.SourceHelper;

/**
 * <p>
 * A contextual file handler based on {@link PetalsFileHandler} and dumping
 * message content.
 * </p>
 */
public class PetalsPayloadDumperFileHandler extends PetalsFileHandler {

    private static final String DUMP_FILE_EXTENSION = ".xml";

    private static final String FILENAME_SEPARATOR = "_";

    public static final String PAYLOAD_CONTENT_DUMP_FILE_LOGDATA_NAME = "payloadContentDumpFile";

    protected static final String DUMP_BASEDIR_PROPERTY_NAME = ".dump-basedir";

    /**
     * The root directory of sub directory of dumps. If the value of the
     * configuration property {@value #DUMP_BASEDIR_PROPERTY_NAME} is not an
     * absolute path, the dump dir is defined as relative to
     * {@link #flowsSubdir} .
     */
    protected final File dumpBasedir;

    public PetalsPayloadDumperFileHandler() throws PropertiesException {
        super();

        // Initialize the dump base dir
        final LogManager manager = LogManager.getLogManager();
        final String dumpBaseDirPropertyValue = manager.getProperty(this.getClass().getName()
                + DUMP_BASEDIR_PROPERTY_NAME);
        if (dumpBaseDirPropertyValue != null && !dumpBaseDirPropertyValue.trim().isEmpty()) {
            String dumpBaseDirValue;
            try {
                dumpBaseDirValue = PropertiesHelper.resolveString(dumpBaseDirPropertyValue,
                        System.getProperties());
            } catch (final PropertiesException e) {
                System.err.println(this.getClass().getName() + ": the value of "
                        + this.getClass().getName() + DUMP_BASEDIR_PROPERTY_NAME + " ("
                        + dumpBaseDirPropertyValue + ") is invalid, default value used: "
                        + e.getMessage());
                dumpBaseDirValue = this.flowsSubdir.getAbsolutePath();
            }
            final File dumpBasedirPath = new File(dumpBaseDirValue);
            if (dumpBasedirPath.isAbsolute()) {
                this.dumpBasedir = dumpBasedirPath;
            } else {
                this.dumpBasedir = new File(this.flowsSubdir, dumpBaseDirValue);
            }
        } else {
            this.dumpBasedir = this.flowsSubdir;
        }
    }

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

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

            this.dumpMessageExchange(record);

            this.printLog(record);
        }
    }

    private void dumpMessageExchange(final LogRecord record) {

        final Object[] logParameters = record.getParameters();
        if ((logParameters != null) && (logParameters.length != 0) && logParameters[0] != null
                && logParameters[0] instanceof LogData) {

            final LogData logData = (LogData) logParameters[0];
            final String flowStepId = (String) logData
                    .get(PetalsExecutionContext.FLOW_STEP_ID_PROPERTY_NAME);
            final MessageExchange exchange = (MessageExchange) logData
                    .get(PetalsExecutionContext.FLOW_EXCHANGE_PROPERTY_NAME);
            if (exchange != null) {
                final NormalizedMessage outNm = exchange.getMessage(MessageExchangeImpl.OUT_MSG);
                final NormalizedMessage inNm = exchange.getMessage(MessageExchangeImpl.IN_MSG);
                final NormalizedMessage faultNm = exchange.getFault();
                
                final Source source =
                        faultNm != null ? faultNm.getContent() :
                            outNm != null ? outNm.getContent() :
                                (inNm != null && exchange.getStatus().equals(ExchangeStatus.ACTIVE)) ? inNm.getContent() : null;
                                
                if (source != null) {
                    final TraceCode traceCode = (TraceCode) logData
                            .get(FlowLogData.FLOW_STEP_TRACE_CODE);
    
                    final File dumpFile = this.getDumpFile(traceCode, flowStepId);
                    logData.putData(PAYLOAD_CONTENT_DUMP_FILE_LOGDATA_NAME,
                            FileSystemHelper.getRelativePath(this.basedir, dumpFile));
                    dumpFile.getParentFile().mkdirs();
    
                    try {
                        Source faultMessageContentAsSource = SourceHelper.fork(source);
                        SourceHelper.toFile(faultMessageContentAsSource, dumpFile);
                    } catch (final IOException e) {
                        reportError(e.getMessage(), e, ErrorManager.WRITE_FAILURE);
                    } catch (final TransformerException e) {
                        reportError(e.getMessage(), e, ErrorManager.WRITE_FAILURE);
                    }
                }
            }
        }
    }

    /**
     * <p>
     * Generate a {@link File} with an absolute path to dump message.
     * </p>
     * <p>
     * Note: A flow instance identifier <b>MUST</b> be set in the Petals MDC.
     * </p>
     * 
     * @return
     * @throws IOException
     */
    private final File getDumpFile(TraceCode traceCode, final String flowStepId) {

        final Properties contextualProperties = PropertiesHelper.flattenProperties(ExecutionContext
                .getProperties());
        final String flowInstanceId = contextualProperties
                .getProperty(FLOW_INSTANCE_ID_PROPERTY_NAME);
        final String dumpFileName = PetalsPayloadDumperFileHandler.getContextualDumpFileName(
                traceCode, flowStepId);
        return new File(new File(this.dumpBasedir, flowInstanceId), dumpFileName);
    }

    /**
     * Compute the dump file name:
     * <code>&lt;flow-step-id&gt;_&lt;trace-code&gt;.xml</code> where:
     * <ul>
     * <li><code>flow-step-id</code> is the flow step identifier, or
     * <code>unknown</code> if the flow step identifier exists,</li>
     * <li><code>trace-code</code> is a code defining the beginning or ending of
     * the current flow step.</li>
     * </ul>
     * 
     * @param traceCode
     *            A code defining the beginning or ending of the current flow
     *            step
     * @return The dump file name to save the content of the message
     */
    private static final String getContextualDumpFileName(final TraceCode traceCode,
            final String flowStepId) {
        return (flowStepId == null ? "unknown" : flowStepId) + FILENAME_SEPARATOR + traceCode
                + DUMP_FILE_EXTENSION;
    }
}
