/*******************************************************************************
 * Copyright (c) 2016 EfficiOS Inc., Philippe Proulx
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/

package org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.function.Predicate;

import org.eclipse.tracecompass.internal.analysis.lami.core.Activator;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.LamiConfigFileStrings;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.ShellUtils;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;

/**
 * Factory which builds {@link LamiAnalysis} objects out of configuration
 * files.
 *
 * @author Philippe Proulx
 */
public final class LamiAnalysisFactoryFromConfigFile {

    private LamiAnalysisFactoryFromConfigFile() {}

    private static String getProperty(Properties props, String propName) throws LamiAnalysisFactoryException {
        String prop = props.getProperty(propName);

        if (prop == null) {
            throw new LamiAnalysisFactoryException(String.format("Cannot find \"%s\" property", propName)); //$NON-NLS-1$
        }

        prop = prop.trim();

        if (prop.isEmpty()) {
            throw new LamiAnalysisFactoryException(String.format("\"%s\" property cannot be empty", propName)); //$NON-NLS-1$
        }

        return prop;
    }

    /**
     * Builds a {@link LamiAnalysis} object from an input stream providing the
     * content of a configuration file.
     * <p>
     * The caller is responsible for opening and closing {@code inputStream}.
     *
     * @param inputStream
     *            Input stream for reading the configuration file; the stream is
     *            not closed by this method
     * @param isUserDefined
     *            {@code true} if the analysis to build is user-defined
     * @param appliesTo
     *            Predicate to use to check whether or not this analysis applies
     *            to a given trace
     * @return Built {@link LamiAnalysis} object
     * @throws LamiAnalysisFactoryException
     *             If something went wrong reading the input stream
     */
    public static LamiAnalysis buildFromInputStream(InputStream inputStream, boolean isUserDefined,
            Predicate<ITmfTrace> appliesTo) throws LamiAnalysisFactoryException {
        final Properties props = new Properties();

        // Load properties
        try {
            props.load(inputStream);
        } catch (IOException e) {
            throw new LamiAnalysisFactoryException(e);
        }

        // Get analysis' name and command
        final String name = getProperty(props, LamiConfigFileStrings.PROP_NAME);
        final String command = getProperty(props, LamiConfigFileStrings.PROP_COMMAND);

        // Get individual arguments from command string
        final List<String> args = ShellUtils.commandStringToArgs(command);

        return new LamiAnalysis(name, isUserDefined, appliesTo, args);
    }

    /**
     * Builds a {@link LamiAnalysis} object from a configuration file.
     *
     * @param configFilePath
     *            Configuration file path
     * @param isUserDefined
     *            {@code true} if the analysis to build is user-defined
     * @param appliesTo
     *            Predicate to use to check whether or not this analysis applies
     *            to a given trace
     * @return Built {@link LamiAnalysis} object
     * @throws LamiAnalysisFactoryException
     *             If something went wrong reading the file
     */
    public static LamiAnalysis buildFromConfigFile(Path configFilePath, boolean isUserDefined,
            Predicate<ITmfTrace> appliesTo) throws LamiAnalysisFactoryException {
        try (final InputStream propsStream = Files.newInputStream(configFilePath)) {
            return buildFromInputStream(propsStream, isUserDefined, appliesTo);
        } catch (IOException e) {
            throw new LamiAnalysisFactoryException(e);
        }
    }

    /**
     * Builds a list of {@link LamiAnalysis} objects from a directory containing
     * configuration files.
     *
     * @param configDir
     *            Configuration directory containing the configuration files to
     *            load
     * @param isUserDefined
     *            {@code true} if the analyses to build are user-defined
     * @param appliesTo
     *            Predicate to use to check whether or not those analyses apply
     *            to a given trace
     * @return List of built {@link LamiAnalysis} objects
     * @throws LamiAnalysisFactoryException
     *             If something went wrong reading the directory
     */
    public static List<LamiAnalysis> buildFromConfigDir(Path configDir, boolean isUserDefined,
            Predicate<ITmfTrace> appliesTo) throws LamiAnalysisFactoryException {
        final List<LamiAnalysis> analyses = new ArrayList<>();

        try (final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(configDir)) {
            for (final Path path : directoryStream) {
                try {
                    analyses.add(buildFromConfigFile(path, isUserDefined, appliesTo));
                } catch (LamiAnalysisFactoryException e) {
                    Activator.instance().logWarning(String.format("Cannot load external analysis \"%s\"", path)); //$NON-NLS-1$
                }
            }
        } catch (IOException e) {
            throw new LamiAnalysisFactoryException(e);
        }

        return analyses;
    }

}
