/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.shell;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mucommander.command.Command;
import com.mucommander.commons.conf.ConfigurationEvent;
import com.mucommander.commons.conf.ConfigurationListener;
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.protocol.local.LocalFile;
import com.mucommander.conf.MuConfigurations;
import com.mucommander.conf.MuPreference;
import com.mucommander.conf.MuPreferences;
import com.mucommander.desktop.DesktopManager;
import com.mucommander.process.AbstractProcess;
import com.mucommander.process.ProcessListener;
import com.mucommander.process.ProcessListenerList;
import com.mucommander.process.ProcessRunner;

/**
 * Used to execute shell commands.
 * @author Maxence Bernard, Nicolas Rinaudo
 */
public class Shell implements ConfigurationListener {
	private static final Logger LOGGER = LoggerFactory.getLogger(Shell.class);
	
    // - Class variables -----------------------------------------------------
    // -----------------------------------------------------------------------
    /** Encoding used by the shell. */
    private static String   encoding;
    /** Whether encoding should be auto-detected or not. */
    private static boolean  autoDetectEncoding;
    /** Tokens that compose the shell command. */
    private static String[] tokens;
    /** Tokens that compose remote shell commands. */
    private static String[] remoteTokens;
    /** Instance of configuration listener. */
    private static Shell    confListener;



    // - Initialization ------------------------------------------------------
    // -----------------------------------------------------------------------
    /**
     * Initializes the shell.
     */
    static {
    	MuConfigurations.addPreferencesListener(confListener = new Shell());

        // This could in theory also be written without the confListener reference.
        // It turns out, however, that proGuard is a bit too keen when removing fields
        // he thinks are not used. This code is written that way to make sure
        // confListener is not taken out, and the ConfigurationListener instance removed
        // instantly as there is only a WeakReference on it.
        // The things we have to do...
        Shell.setShellCommand();

        remoteTokens = new String[1];
    }

    /**
     * Prevents instances of Shell from being created.
     */
    private Shell() {}



    // - Shell interaction ---------------------------------------------------
    // -----------------------------------------------------------------------
    /**
     * Executes the specified command in the specified folder.
     * <p>
     * The <code>currentFolder</code> folder parameter will only be used if it's neither a
     * remote directory nor an archive. Otherwise, the command will run from the user's
     * home directory.
     * </p>
     * @param     command       command to run.
     * @param     currentFolder where to run the command from.
     * @return                  the resulting process.
     * @exception IOException   thrown if any error occurs while trying to run the command.
     */
    public static AbstractProcess execute(String command, AbstractFile currentFolder) throws IOException {return execute(command, currentFolder, null);}

    /**
     * Executes the specified command in the specified folder.
     * <p>
     * The <code>currentFolder</code> folder parameter will only be used if it's neither a
     * remote directory nor an archive. Otherwise, the command will run from the user's
     * home directory.
     * </p>
     * <p>
     * Information about the resulting process will be sent to the specified <code>listener</code>.
     * </p>
     * @param     command       command to run.
     * @param     currentFolder where to run the command from.
     * @param     listener      where to send information about the resulting process.
     * @return                  the resulting process.
     * @exception IOException   thrown if any error occurs while trying to run the command.
     */
    public static synchronized AbstractProcess execute(String command, AbstractFile currentFolder, ProcessListener listener) throws IOException {
        String[] commandTokens;

        LOGGER.debug("Executing " + command);

        // Adds the command to history.
        ShellHistoryManager.add(command);

        // Builds the shell command.
        // Local files use the configuration defined shell. Remote files
        // will execute the command as-is.
        if(currentFolder.hasAncestor(LocalFile.class)) {
            tokens[tokens.length - 1] = command;
            commandTokens             = tokens;
        }
        else {
            remoteTokens[0] = command;
            commandTokens   = remoteTokens;
        }

        // Starts the process.
        if(autoDetectEncoding) {
            if(listener == null)
                listener = new ShellEncodingListener();
            else {
                ProcessListenerList listeners;

                listeners = new ProcessListenerList();
                listeners.add(listener);
                listeners.add(new ShellEncodingListener());
                listener = listeners;
            }
        }
        return (encoding == null) ? ProcessRunner.execute(commandTokens, currentFolder, listener) : ProcessRunner.execute(commandTokens, currentFolder, listener, encoding);
    }



    // - Configuration management --------------------------------------------
    // -----------------------------------------------------------------------
    /**
     * Extracts the shell command from configuration.
     */
    private static synchronized void setShellCommand() {
        String   shellCommand;
        String[] buffer;

        // Retrieves the configuration defined shell command.
        if(MuConfigurations.getPreferences().getVariable(MuPreference.USE_CUSTOM_SHELL, MuPreferences.DEFAULT_USE_CUSTOM_SHELL))
            shellCommand = MuConfigurations.getPreferences().getVariable(MuPreference.CUSTOM_SHELL, DesktopManager.getDefaultShell());
        else
            shellCommand = DesktopManager.getDefaultShell();

        // Splits the command into tokens, leaving room for the argument.
        buffer = Command.getTokens(shellCommand);
        tokens = new String[buffer.length + 1];
        System.arraycopy(buffer, 0, tokens, 0, buffer.length);

        // Retrieves encoding configuration.
        encoding           = MuConfigurations.getPreferences().getVariable(MuPreference.SHELL_ENCODING);
        autoDetectEncoding = MuConfigurations.getPreferences().getVariable(MuPreference.AUTODETECT_SHELL_ENCODING, MuPreferences.DEFAULT_AUTODETECT_SHELL_ENCODING);
    }

    /**
     * Reacts to configuration changes.
     */
    public void configurationChanged(ConfigurationEvent event) {
        if(event.getVariable().startsWith(MuPreferences.SHELL_SECTION))
            setShellCommand();
    }
}
