/*******************************************************************************
 * Copyright (c) 2007 EclipseGraphviz contributors.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     abstratt technologies
 *     Scott Bronson
 *******************************************************************************/
package com.abstratt.graphviz;

import java.io.File;
import java.io.FileFilter;
import java.nio.file.Files;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

import com.abstratt.pluginutils.LogUtils;

public class GraphVizActivator implements BundleActivator {

    /**
     * DotLocation keeps track of how the user wants to select the dot
     * executable. We include strings for each term so that the settings files
     * remain human readable.
     */
    public enum DotMethod {
        AUTO, MANUAL;

        /**
         * Given a string, looks up the corresponding DotMethod. If no match,
         * returns the default AUTO.
         */
        static public DotMethod find(String term) {
            for (DotMethod p : DotMethod.values()) {
                if (p.name().equals(term)) {
                    return p;
                }
            }
            return AUTO;
        }
    }

    public static final String DOT_SEARCH_METHOD = "dotSearchMethod";
    // The manual path is entered by the user. It should never be changed or
    // deleted except by the user.
    public static final String DOT_MANUAL_PATH = "dotManualPath";

    public static final String DOT_FILE_NAME = "dot";

    public static final String COMMAND_LINE = "commandLineExtension";

    public static String ID = GraphVizActivator.class.getPackage().getName();

    private static GraphVizActivator instance;

    public static GraphVizActivator getInstance() {
        return instance;
    }

    /**
     * Returns whether the given file is executable. Depending on the platform
     * we might not get this right.
     * 
     * TODO find a better home for this function
     */
    public static boolean isExecutable(File file) {
        if (!file.isFile())
            return false;
        if (Platform.getOS().equals(Platform.OS_WIN32))
            // executable attribute is a *ix thing, on Windows all files are
            // executable
            return true;
        return Files.isExecutable(file.toPath());
    }

    public static void logUnexpected(String message, Exception e) {
        LogUtils.logError(ID, message, e);
    }

    // store paths as strings so they won't get screwed up by platform issues.

    /**
     * Path to autodetected dot or null if it can't be found. See
     * autodetectDots().
     */
    private String autodetectedDotLocation;

    /**
     * The path the bundled Graphviz install was extracted to (null if not
     * found/looked up).
     */

    public GraphVizActivator() {
        instance = this;
    }

    /**
     * This routine browses through the user's PATH looking for dot executables.
     * 
     * @return the absolute path if a suitable dot is found, null if not. This
     *         is normally called once at plugin startup but it can also be
     *         called while the plugin is running (in case user has installed
     *         dot without restarting Eclipse).
     */
    public String autodetectDots() {
        autodetectedDotLocation = null;
        String paths = System.getenv("PATH");
        for (String path : paths.split(File.pathSeparator)) {
            File directory = new File(path);
            File[] matchingFiles = directory.listFiles(new ExecutableFinder(DOT_FILE_NAME));
            if (matchingFiles != null && matchingFiles.length > 0) {
                File found = matchingFiles[0];
                autodetectedDotLocation = found.getAbsolutePath();
                break;
            }
        }
        return autodetectedDotLocation;
    }

    private static class ExecutableFinder implements FileFilter {
        private String nameToMatch;

        public ExecutableFinder(String nameToMatch) {
            this.nameToMatch = nameToMatch;
        }

        public boolean accept(File candidate) {
            if (!candidate.getName().equalsIgnoreCase(nameToMatch)
                    && !candidate.getName().startsWith(nameToMatch + '.'))
                return false;
            boolean executable = isExecutable(candidate);
            return executable;
        }
    }

    // preference getters and setters

    /**
     * Gets the path to the dot executable. It takes user's preferences into
     * account so it should always do the right thing.
     */
    public IPath getDotLocation() {
        final String manualLocation = getManualDotPath();
        DotMethod dotSearchMethod = getDotSearchMethod();
		switch (dotSearchMethod) {
        case AUTO:
        	return autodetectedDotLocation != null ? new Path(autodetectedDotLocation) : null;
        case MANUAL:
            return manualLocation != null ? new Path(manualLocation) : null;
        }
        // can't never get here
        throw new IllegalStateException("Unexpected value for dotSearchMethod: " + dotSearchMethod);
    }

    public DotMethod getDotSearchMethod() {
        String value = getPreference(DOT_SEARCH_METHOD);
        return value != null ? DotMethod.find(value) : DotMethod.AUTO;
    }

    public File getGraphVizDirectory() {
        IPath dotLocation = getDotLocation();
        return dotLocation == null ? null : dotLocation.removeLastSegments(1).toFile();
    }

    public String getManualDotPath() {
        return getPreference(DOT_MANUAL_PATH);
    }

    public String getCommandLineExtension() {
        return getPreference(COMMAND_LINE);
    }

    public void setCommandLineExtension(String commandLineExtension) {
        setPreference(COMMAND_LINE, commandLineExtension);
    }

    /** Returns the preference with the given name */
    public String getPreference(String preference_name) {
        Preferences node = Platform.getPreferencesService().getRootNode().node(InstanceScope.SCOPE)
                .node(GraphVizActivator.ID);
        return node.get(preference_name, null);
    }

    public void setDotSearchMethod(DotMethod dotMethod) {
        setPreference(DOT_SEARCH_METHOD, dotMethod.name());
    }

    public void setManualDotPath(String newLocation) {
        setPreference(DOT_MANUAL_PATH, newLocation);
    }

    /** Sets the given preference to the given value */
    public void setPreference(String preferenceName, String value) {
        IEclipsePreferences root = Platform.getPreferencesService().getRootNode();
        Preferences node = root.node(InstanceScope.SCOPE).node(GraphVizActivator.ID);
        try {
            node.sync();
            node.put(preferenceName, value);
            node.flush();
        } catch (BackingStoreException e) {
            LogUtils.logError(ID, "Error updating preferences.", e);
        }
    }

    public void start(BundleContext context) throws Exception {
        // try to find any installed copies of dot
        autodetectDots();
        if (autodetectedDotLocation != null) {
            LogUtils.logInfo(getClass().getPackage().getName(), "Detected dot at " + autodetectedDotLocation, null);
        } else if (getDotSearchMethod() == DotMethod.AUTO) {
            LogUtils.logWarning(
                    ID,
                    "Could not find a suitable dot executable.  Please specify one using Window -> Preferences -> Graphviz.",
                    null);
        }
    }

    public void stop(BundleContext context) throws Exception {
        // nothing to do
    }
}