/*
 * 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.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.WeakHashMap;

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

import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileFactory;
import com.mucommander.conf.MuConfigurations;
import com.mucommander.conf.MuPreference;
import com.mucommander.conf.MuPreferences;
import com.mucommander.conf.PlatformManager;
import com.mucommander.io.backup.BackupInputStream;
import com.mucommander.io.backup.BackupOutputStream;

/**
 * Used to manage shell history.
 * <p>
 * Using this class is fairly basic: you can add elements to the shell history through
 * {@link #add(String)} and browse it through {@link #getHistoryIterator()}.
 * </p>
 * @author Nicolas Rinaudo
 */
public class ShellHistoryManager {
	private static final Logger LOGGER = LoggerFactory.getLogger(ShellHistoryManager.class);
	
    // - History configuration -----------------------------------------------
    // -----------------------------------------------------------------------
    /** File in which to store the shell history. */
    private static final String DEFAULT_HISTORY_FILE_NAME = "shell_history.xml";



    // - Class fields ---------------------------------------------------------------
    // ------------------------------------------------------------------------------
    /** List of shell history registered listeners. */
    private static WeakHashMap<ShellHistoryListener, ?> listeners;
    /** Stores the shell history. */
    private static String[]                             history;
    /** Index of the first element of the history. */
    private static int                                  historyStart;
    /** Index of the last element of the history. */
    private static int                                  historyEnd;
    /** Path to the history file. */
    private static AbstractFile                         historyFile;



    // - Initialisation -------------------------------------------------------------
    // ------------------------------------------------------------------------------
    /**
     * Prevents instantiations of the class.
     */
    private ShellHistoryManager() {}

    /*
     * Initialises history.
     */
    static {
        history   = new String[MuConfigurations.getPreferences().getVariable(MuPreference.SHELL_HISTORY_SIZE, MuPreferences.DEFAULT_SHELL_HISTORY_SIZE)];
        listeners = new WeakHashMap<ShellHistoryListener, Object>();
    }



    // - Listener code --------------------------------------------------------------
    // ------------------------------------------------------------------------------
    /**
     * Registers a listener to changes in the shell history.
     * @param listener listener to register.
     */
    public static void addListener(ShellHistoryListener listener) {listeners.put(listener, null);}

    /**
     * Propagates shell history events to all registered listeners.
     * @param command command that was added to the shell history.
     */
    private static void triggerEvent(String command) {
        for(ShellHistoryListener listener : listeners.keySet())
            listener.historyChanged(command);
    }



    // - History access -------------------------------------------------------------
    // ------------------------------------------------------------------------------
    /**
     * Completely empties the shell history.
     */
    public static void clear() {
        // Empties history.
        historyStart = 0;
        historyEnd   = 0;

        // Notifies listeners.
        for(ShellHistoryListener listener : listeners.keySet())
            listener.historyCleared();
    }

    /**
     * Returns a <b>non thread-safe</b> iterator on the history.
     * @return an iterator on the history.
     */
    public static Iterator<String> getHistoryIterator() {return new HistoryIterator();}

    /**
     * Adds the specified command to shell history.
     * @param command command to add to the shell history.
     */
    public static void add(String command) {
        // Ignores empty commands.
        if(command.trim().equals(""))
            return;
        // Ignores the command if it's the same as the last one.
        // There is no last command if history is empty.
        if(historyEnd != historyStart) {
            int lastIndex;

            // Computes the index of the previous command.
            if(historyEnd == 0)
                lastIndex = history.length - 1;
            else
                lastIndex = historyEnd - 1;

            if(command.equals(history[lastIndex]))
                return;
        }

        LOGGER.debug("Adding  " + command + " to shell history.");

        // Updates the history buffer.
        history[historyEnd] = command;
        historyEnd++;

        // Wraps around the history buffer.
        if(historyEnd == history.length)
            historyEnd = 0;

        // Clears items from the beginning of the buffer if necessary.
        if(historyEnd == historyStart) {
            if(++historyStart == history.length)
                historyStart = 0;
        }

        // Propagates the event.
        triggerEvent(command);
    }



    // - History saving / loading ---------------------------------------------------
    // ------------------------------------------------------------------------------
    /**
     * Sets the path of the shell history file.
     * @param     path                  where to load the shell history from.
     * @exception FileNotFoundException if <code>path</code> is not accessible.
     * @see                             #getHistoryFile()
     * @see                             #setHistoryFile(File)
     * @see                             #setHistoryFile(AbstractFile)
     */
    public static void setHistoryFile(String path) throws FileNotFoundException {
        AbstractFile file;

        if((file = FileFactory.getFile(path)) == null)
            setHistoryFile(new File(path));
        else
            setHistoryFile(file);
    }

    /**
     * Sets the path of the shell history file.
     * @param     file                  where to load the shell history from.
     * @exception FileNotFoundException if <code>path</code> is not accessible.
     * @see                             #getHistoryFile()
     * @see                             #setHistoryFile(AbstractFile)
     * @see                             #setHistoryFile(String)
     */
    public static void setHistoryFile(File file) throws FileNotFoundException {setHistoryFile(FileFactory.getFile(file.getAbsolutePath()));}

    /**
     * Sets the path of the shell history file.
     * @param     file                  where to load the shell history from.
     * @exception FileNotFoundException if <code>path</code> is not accessible.
     * @see                             #getHistoryFile()
     * @see                             #setHistoryFile(File)
     * @see                             #setHistoryFile(String)
     */
    public static void setHistoryFile(AbstractFile file) throws FileNotFoundException {
        // Makes sure file can be used as a shell history file.
        if(file.isBrowsable())
            throw new FileNotFoundException("Not a valid file: " + file.getAbsolutePath());
        historyFile = file;
    }

    /**
     * Returns the path to the shell history file.
     * <p>
     * This method cannot guarantee the file's existence, and it's up to the caller
     * to deal with the fact that the user might not actually have created a history file yet.
     * </p>
     * <p>
     * This method's return value can be modified through {@link #setHistoryFile(String)}.
     * If this wasn't called, the default path will be used: {@link #DEFAULT_HISTORY_FILE_NAME}
     * in the {@link com.mucommander.conf.PlatformManager#getPreferencesFolder() preferences} folder.
     * </p>
     * @return             the path to the shell history file.
     * @throws IOException if an error occurred while locating the default shell history file.
     * @see                #setHistoryFile(File)
     * @see                #setHistoryFile(String)
     * @see                #setHistoryFile(AbstractFile)
     */
    public static AbstractFile getHistoryFile() throws IOException {
        if(historyFile == null)
            return PlatformManager.getPreferencesFolder().getChild(DEFAULT_HISTORY_FILE_NAME);
        return historyFile;
    }

    /**
     * Writes the shell history to hard drive.
     * @throws IOException if an I/O error occurs.
     */
    public static void writeHistory() throws IOException {
        BackupOutputStream out;

        out = null;
        try {ShellHistoryWriter.write(out = new BackupOutputStream(getHistoryFile()));}
        finally {
            if(out != null) {
                try {out.close();}
                catch(Exception e) {}
            }
        }
    }

    /**
     * Loads the shell history.
     * @throws Exception if an error occurs.
     */
    public static void loadHistory() throws Exception {
        BackupInputStream in;

        in = null;
        try {ShellHistoryReader.read(in = new BackupInputStream(getHistoryFile()));}
        finally {
            if(in != null) {
                try {in.close();}
                catch(Exception e2) {}
            }
        }
    }

    /**
     * Iterator used to browse history.
     * @author Nicolas Rinaudo
     */
    static class HistoryIterator implements Iterator<String> {
        /** Index in the history. */
        private int index;

        /**
         * Creates a new history iterator.
         */
        public HistoryIterator() {index = ShellHistoryManager.historyStart;}

        /**
         * Returns <code>true</code> if there are more elements to iterate through.
         * @return <code>true</code> if there are more elements to iterate through, <code>false</code> otherwise.
         */
        public boolean hasNext() {return index != ShellHistoryManager.historyEnd;}

        /**
         * Returns the next element in the history.
         * @return the next element in the history.
         */
        public String next() throws NoSuchElementException {
            String value;

            if(!hasNext())
                throw new NoSuchElementException();

            value = ShellHistoryManager.history[index];
            if(++index == ShellHistoryManager.history.length)
                index = 0;
            return value;
        }

        /**
         * Operation not supported.
         */
        public void remove() throws UnsupportedOperationException {throw new UnsupportedOperationException();}
    }

}
