/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
/*
 * Created on May 17, 2005
 *
 * @author Fabio Zadrozny
 */
package org.python.pydev.ui.interpreters;

import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.preference.IPersistentPreferenceStore;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.progress.UIJob;
import org.python.copiedfromeclipsesrc.JDTNotAvailableException;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.IInterpreterInfo;
import org.python.pydev.core.IInterpreterManager;
import org.python.pydev.core.IInterpreterManagerListener;
import org.python.pydev.core.IModule;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IPythonPathNature;
import org.python.pydev.core.ISystemModulesManager;
import org.python.pydev.core.IToken;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.NotConfiguredInterpreterException;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper;
import org.python.pydev.editor.codecompletion.revisited.SyncSystemModulesManagerScheduler;
import org.python.pydev.editor.codecompletion.shell.AbstractShell;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.plugin.nature.PythonNature;
import org.python.pydev.plugin.nature.PythonNatureListenersManager;
import org.python.pydev.shared_core.callbacks.ListenerList;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_ui.EditorUtils;
import org.python.pydev.ui.dialogs.PyDialogHelpers;
import org.python.pydev.ui.pythonpathconf.AutoConfigMaker;
import org.python.pydev.ui.pythonpathconf.IInterpreterProviderFactory.InterpreterType;
import org.python.pydev.ui.pythonpathconf.InterpreterConfigHelpers;
import org.python.pydev.ui.pythonpathconf.InterpreterInfo;

/**
 * Does not write directly in INTERPRETER_PATH, just loads from it and works with it.
 *
 * @author Fabio Zadrozny
 */
public abstract class AbstractInterpreterManager implements IInterpreterManager {

    private int modificationStamp = 0;

    /**
     * This is the cache, that points from an interpreter to its information.
     */
    protected final Map<String, InterpreterInfo> exeToInfo = new HashMap<String, InterpreterInfo>();
    private final IPreferenceStore prefs;

    //caches that are filled at runtime -------------------------------------------------------------------------------
    /**
     * This is used to keep the builtin completions
     */
    protected final Map<String, IToken[]> builtinCompletions = new HashMap<String, IToken[]>();

    /**
     * This is used to keep the builtin module
     */
    protected final Map<String, IModule> builtinMod = new HashMap<String, IModule>();

    @Override
    public void clearBuiltinCompletions(String projectInterpreterName) {
        this.builtinCompletions.remove(projectInterpreterName);
    }

    private ListenerList<IInterpreterManagerListener> listeners = new ListenerList<>(
            IInterpreterManagerListener.class);

    @Override
    public void addListener(IInterpreterManagerListener listener) {
        listeners.add(listener);
    }

    @Override
    public IToken[] getBuiltinCompletions(String projectInterpreterName) {
        //Cache with the internal name.
        projectInterpreterName = getInternalName(projectInterpreterName);
        if (projectInterpreterName == null) {
            return null;
        }

        IToken[] toks = this.builtinCompletions.get(projectInterpreterName);

        if (toks == null || toks.length == 0) {
            IModule builtMod = getBuiltinMod(projectInterpreterName);
            if (builtMod != null) {
                toks = builtMod.getGlobalTokens();
                this.builtinCompletions.put(projectInterpreterName, toks);
            }
        }
        return this.builtinCompletions.get(projectInterpreterName);
    }

    @Override
    public IModule getBuiltinMod(String projectInterpreterName) {
        //Cache with the internal name.
        projectInterpreterName = getInternalName(projectInterpreterName);
        if (projectInterpreterName == null) {
            return null;
        }
        IModule mod = builtinMod.get(projectInterpreterName);
        if (mod != null) {
            return mod;
        }

        try {
            InterpreterInfo interpreterInfo = this.getInterpreterInfo(projectInterpreterName, null);
            ISystemModulesManager modulesManager = interpreterInfo.getModulesManager();

            mod = modulesManager.getBuiltinModule("__builtin__", false);
            if (mod == null) {
                //Python 3.0 has builtins and not __builtin__
                mod = modulesManager.getBuiltinModule("builtins", false);
            }
            if (mod != null) {
                builtinMod.put(projectInterpreterName, mod);
            }

        } catch (MisconfigurationException e) {
            Log.log(e);
        }
        return builtinMod.get(projectInterpreterName);
    }

    private String getInternalName(String projectInterpreterName) {
        if (IPythonNature.DEFAULT_INTERPRETER.equals(projectInterpreterName)) {
            //if it's the default, let's translate it to the outside world
            try {
                return this.getDefaultInterpreterInfo(true).getExecutableOrJar();
            } catch (NotConfiguredInterpreterException e) {
                Log.log(e);
                return projectInterpreterName;
            }
        }
        return projectInterpreterName;
    }

    @Override
    public void clearBuiltinMod(String projectInterpreterName) {
        this.builtinMod.remove(projectInterpreterName);
    }

    /**
     * Constructor
     */
    @SuppressWarnings("unchecked")
    public AbstractInterpreterManager(IPreferenceStore prefs) {
        this.prefs = prefs;
        prefs.setDefault(getPreferenceName(), "");

        //Just called to force the information to be recreated!
        this.getInterpreterInfos();

        List<IInterpreterObserver> participants = ExtensionHelper
                .getParticipants(ExtensionHelper.PYDEV_INTERPRETER_OBSERVER);
        for (IInterpreterObserver observer : participants) {
            observer.notifyInterpreterManagerRecreated(this);
        }
    }

    @Override
    public boolean isConfigured() {
        try {
            String defaultInterpreter = getDefaultInterpreterInfo(false).getExecutableOrJar();
            if (defaultInterpreter == null) {
                return false;
            }
            if (defaultInterpreter.length() == 0) {
                return false;
            }
        } catch (NotConfiguredInterpreterException e) {
            return false;
        }
        return true;
    }

    @Override
    public void clearCaches() {
        builtinMod.clear();
        builtinCompletions.clear();
        clearInterpretersFromPersistedString();
    }

    /**
     * @return the preference name where the options for this interpreter manager should be stored
     */
    protected abstract String getPreferenceName();

    /**
     * @throws NotConfiguredInterpreterException
     * @see org.python.pydev.core.IInterpreterManager#getDefaultInterpreterInfo()
     */
    @Override
    public IInterpreterInfo getDefaultInterpreterInfo(boolean autoConfigureIfNotConfigured)
            throws NotConfiguredInterpreterException {
        IInterpreterInfo[] interpreters = getInterpreterInfos();
        String errorMsg = null;
        if (interpreters.length > 0) {
            IInterpreterInfo defaultInfo = interpreters[0];
            String interpreter = defaultInfo.getExecutableOrJar();
            if (interpreter == null) {
                errorMsg = "The configured interpreter for " + getInterpreterUIName()
                        + " is null, some error happened getting it.";
            }
            return defaultInfo;
        } else {
            errorMsg = getInterpreterUIName() + " not configured.";
        }

        if (autoConfigureIfNotConfigured) {
            //If we got here, the interpreter is not properly configured, let's try to auto-configure it
            if (PyDialogHelpers.getAskAgainInterpreter(this)) {
                configureInterpreterJob.addInterpreter(this);
                configureInterpreterJob.schedule(50);
            }
        }
        throw new NotConfiguredInterpreterException(errorMsg);
    }

    private static class ConfigureInterpreterJob extends UIJob {

        private volatile Set<AbstractInterpreterManager> interpreters = new HashSet<AbstractInterpreterManager>();

        public void addInterpreter(AbstractInterpreterManager abstractInterpreterManager) {
            this.interpreters.add(abstractInterpreterManager);
        }

        public ConfigureInterpreterJob() {
            super("Configure interpreter");
        }

        @Override
        public IStatus runInUIThread(IProgressMonitor monitor) {
            Set<AbstractInterpreterManager> current = interpreters;
            interpreters = new HashSet<AbstractInterpreterManager>();
            for (AbstractInterpreterManager m : current) {
                try {
                    m.getDefaultInterpreterInfo(false);
                    continue; //Maybe it got configured at some other point...
                } catch (NotConfiguredInterpreterException e) {
                    int ret = PyDialogHelpers.openQuestionConfigureInterpreter(m);
                    if (ret == InterpreterConfigHelpers.CONFIG_MANUAL) {
                        PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null,
                                m.getPreferencesPageId(), null, null);
                        dialog.open();
                    }
                    else if (ret != PyDialogHelpers.INTERPRETER_CANCEL_CONFIG) {
                        InterpreterType interpreterType;
                        switch (m.getInterpreterType()) {
                            case IPythonNature.INTERPRETER_TYPE_JYTHON:
                                interpreterType = InterpreterType.JYTHON;
                                break;

                            case IPythonNature.INTERPRETER_TYPE_IRONPYTHON:
                                interpreterType = InterpreterType.IRONPYTHON;
                                break;

                            default:
                                interpreterType = InterpreterType.PYTHON;
                        }
                        boolean advanced = ret == InterpreterConfigHelpers.CONFIG_ADV_AUTO;
                        Shell shell = EditorUtils.getShell();
                        AutoConfigMaker a = new AutoConfigMaker(interpreterType, advanced, null, null);
                        a.autoConfigSingleApply(null);
                    }
                }
            }
            return Status.OK_STATUS;
        }

    }

    private static ConfigureInterpreterJob configureInterpreterJob = new ConfigureInterpreterJob();

    /**
     * @return
     */
    protected abstract String getPreferencesPageId();

    /**
     * @return a message to show to the user when there is no configured interpreter
     */
    public abstract String getInterpreterUIName();

    private void clearInterpretersFromPersistedString() {
        synchronized (lock) {
            if (interpreterInfosFromPersistedString != null) {
                this.exeToInfo.clear();
                interpreterInfosFromPersistedString = null;
            }
        }

    }

    private volatile IInterpreterInfo[] interpreterInfosFromPersistedString;

    @Override
    public IInterpreterInfo[] getInterpreterInfos() {
        return internalRecreateCacheGetInterpreterInfos();

    }

    private IInterpreterInfo[] internalRecreateCacheGetInterpreterInfos() {
        IInterpreterInfo[] interpreters = interpreterInfosFromPersistedString;
        if (interpreters == null) {
            synchronized (lock) {
                if (interpreterInfosFromPersistedString != null) {
                    //Some other thread restored it while we're locked.
                    interpreters = interpreterInfosFromPersistedString;

                } else {
                    interpreters = getInterpretersFromPersistedString(getPersistedString());
                    try {
                        this.exeToInfo.clear();
                        for (IInterpreterInfo info : interpreters) {
                            exeToInfo.put(info.getExecutableOrJar(), (InterpreterInfo) info);
                        }

                    } finally {
                        interpreterInfosFromPersistedString = interpreters;
                    }
                }

                for (IInterpreterInfo iInterpreterInfo : interpreterInfosFromPersistedString) {
                    iInterpreterInfo.setModificationStamp(modificationStamp);
                }
            }
        }
        return interpreters;
    }

    /**
     * Given an executable, should create the interpreter info that corresponds to it
     *
     * @param executable the executable that should be used to create the info
     * @param monitor a monitor to keep track of the info
     *
     * @return the interpreter info for the executable
     * @throws CoreException
     * @throws JDTNotAvailableException
     */
    protected abstract Tuple<InterpreterInfo, String> internalCreateInterpreterInfo(String executable,
            IProgressMonitor monitor, boolean askUser) throws CoreException, JDTNotAvailableException;

    /**
     * Creates the information for the passed interpreter.
     */
    @Override
    public IInterpreterInfo createInterpreterInfo(String executable, IProgressMonitor monitor, boolean askUser) {

        monitor.worked(5);
        //ok, we have to get the info from the executable (and let's cache results for future use)...
        Tuple<InterpreterInfo, String> tup = null;
        InterpreterInfo info;
        try {

            tup = internalCreateInterpreterInfo(executable, monitor, askUser);
            if (tup == null) {
                //Canceled (in the dialog that asks the user to choose the valid paths)
                return null;
            }
            info = tup.o1;

        } catch (RuntimeException e) {
            Log.log(e);
            throw e;
        } catch (Exception e) {
            Log.log(e);
            throw new RuntimeException(e);
        }
        if (info.executableOrJar == null || info.executableOrJar.trim().length() == 0) {
            //it is null or empty
            final String title = "Invalid interpreter:" + executable;
            final String msg = "Unable to get information on interpreter!";
            String reasonCreation = "The interpreter (or jar): '" + executable
                    + "' is not valid - info.executable found: " + info.executableOrJar + "\n";
            if (tup != null) {
                reasonCreation += "The standard output gotten from the executed shell was: >>" + tup.o2 + "<<";
            }
            final String reason = reasonCreation;

            try {
                final Display disp = Display.getDefault();
                disp.asyncExec(new Runnable() {
                    @Override
                    public void run() {
                        ErrorDialog.openError(null, title, msg, new Status(Status.ERROR, PydevPlugin.getPluginID(), 0,
                                reason, null));
                    }
                });
            } catch (Throwable e) {
                // ignore error communication error
            }
            throw new RuntimeException(reason);
        }

        return info;

    }

    /**
     * Creates the interpreter info from the output. Checks for errors.
     */
    protected static InterpreterInfo createInfoFromOutput(IProgressMonitor monitor, Tuple<String, String> outTup,
            boolean askUser, String executableName, boolean executableIsUserSpecified) {
        if (outTup.o1 == null || outTup.o1.trim().length() == 0) {
            throw new RuntimeException(
                    "No output was in the standard output when"
                            + "\ntrying to create the interpreter info for: " + executableName
                            + "\nThe error output contains:>>" + outTup.o2 + "<<");
        }
        String executableToUse = executableIsUserSpecified ? executableName : null;
        if (executableToUse != null) {
            if (!new File(executableToUse).exists()) {
                //Only use the user-specified version if the executable does exist (otherwise use the internal info).
                executableToUse = null;
            }
        }
        InterpreterInfo info = InterpreterInfo.fromString(outTup.o1, askUser,
                executableToUse);
        return info;
    }

    /**
     * @throws MisconfigurationException
     * @see org.python.pydev.core.IInterpreterManager#getInterpreterInfo(java.lang.String)
     */
    @Override
    public InterpreterInfo getInterpreterInfo(String nameOrExecutableOrJar, IProgressMonitor monitor)
            throws MisconfigurationException {
        synchronized (lock) {
            if (interpreterInfosFromPersistedString == null) {
                internalRecreateCacheGetInterpreterInfos(); //recreate cache!
            }
            for (IInterpreterInfo info : this.exeToInfo.values()) {
                if (info != null) {
                    if (info.matchNameBackwardCompatible(nameOrExecutableOrJar)) {
                        return (InterpreterInfo) info;
                    }
                }
            }
        }

        throw new MisconfigurationException(StringUtils.format(
                "Interpreter: %s not found", nameOrExecutableOrJar));
    }

    private Object lock = new Object();
    //little cache...
    private String persistedCache;
    private IInterpreterInfo[] persistedCacheRet;

    /**
     * @see org.python.pydev.core.IInterpreterManager#getInterpretersFromPersistedString(java.lang.String)
     */
    @Override
    public IInterpreterInfo[] getInterpretersFromPersistedString(String persisted) {
        synchronized (lock) {
            if (persisted == null || persisted.trim().length() == 0) {
                return new IInterpreterInfo[0];
            }

            if (persistedCache == null || persistedCache.equals(persisted) == false) {
                List<IInterpreterInfo> ret = new ArrayList<IInterpreterInfo>();

                try {
                    List<InterpreterInfo> list = new ArrayList<InterpreterInfo>();
                    String[] strings = persisted.split("&&&&&");

                    //first, get it...
                    for (String string : strings) {
                        try {
                            list.add(InterpreterInfo.fromString(string, false));
                        } catch (Exception e) {
                            //ok, its format might have changed
                            String errMsg = "Interpreter storage changed.\r\n"
                                    + "Please restore it (window > preferences > Pydev > Interpreter)";
                            Log.log(errMsg, e);

                            return new IInterpreterInfo[0];
                        }
                    }

                    //then, put it in the list to be returned
                    for (InterpreterInfo info : list) {
                        if (info != null && info.executableOrJar != null) {
                            ret.add(info);
                        }
                    }

                    //and at last, restore the system info
                    for (final InterpreterInfo info : list) {
                        try {
                            info.getModulesManager().load();
                        } catch (Exception e) {
                            Log.logInfo(new RuntimeException("Restoring info for: " + info.getExecutableOrJar(), e));

                            info.setLoadFinished(false);
                            try {
                                //if it does not work it (probably) means that the internal storage format changed among versions,
                                //so, we have to recreate that info.

                                IProgressMonitor monitor = new NullProgressMonitor();
                                //ok, maybe its file-format changed... let's re-create it then.
                                info.restorePythonpath(monitor);
                                //after restoring it, let's save it.
                                info.getModulesManager().save();

                                //Note: All the code below is the same as the 2 lines above. It's no longer done needing the
                                //UI access because of issue: https://sourceforge.net/tracker/?func=detail&aid=3515102&group_id=85796&atid=577329
                                //(Can hang Eclipse at startup updating interpreter info)

                                //                                final Display def = Display.getDefault();
                                //                                def.syncExec(new Runnable(){
                                //
                                //                                    public void run() {
                                //                                        Shell shell = def.getActiveShell();
                                //                                        ProgressMonitorDialog dialog = new AsynchronousProgressMonitorDialog(shell);
                                //                                        dialog.setBlockOnOpen(false);
                                //                                        try {
                                //                                            dialog.run(false, false, new IRunnableWithProgress(){
                                //
                                //                                                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                                //                                                    monitor.beginTask("Updating the interpreter info.", 100);
                                //                                                    //ok, maybe its file-format changed... let's re-create it then.
                                //                                                    info.restorePythonpath(monitor);
                                //                                                    //after restoring it, let's save it.
                                //                                                    info.getModulesManager().save();
                                //                                                    monitor.done();
                                //                                                }}
                                //                                            );
                                //                                        } catch (Exception e) {
                                //                                            throw new RuntimeException(e);
                                //                                        }
                                //                                    }
                                //
                                //                                });
                            } finally {
                                info.setLoadFinished(true);
                            }
                            Log.logInfo(("Finished restoring information for: " + info.executableOrJar + " at: " + info
                                    .getModulesManager().getIoDirectory()));
                        }
                    }

                } catch (Exception e) {
                    Log.log(e);

                    //ok, some error happened (maybe it's not configured)
                    return new IInterpreterInfo[0];
                }

                persistedCache = persisted;
                persistedCacheRet = ret.toArray(new IInterpreterInfo[0]);
            }
        }
        return persistedCacheRet;
    }

    /**
     * @param executables executables that should be persisted
     * @return string to persist with the passed executables.
     */
    public static String getStringToPersist(IInterpreterInfo[] executables) {
        FastStringBuffer buf = new FastStringBuffer();
        for (IInterpreterInfo info : executables) {
            if (info != null) {
                buf.append(info.toString());
                buf.append("&&&&&");
            }
        }

        return buf.toString();
    }

    protected static File getInterpreterInfoPy() throws CoreException {
        File script = PydevPlugin.getScriptWithinPySrc("interpreterInfo.py");
        if (!script.exists()) {
            throw new RuntimeException("The file specified does not exist: " + script);
        }
        return script;
    }

    String persistedString;

    @Override
    public String getPersistedString() {
        if (persistedString == null) {
            persistedString = prefs.getString(getPreferenceName());
        }
        return persistedString;
    }

    /* (non-Javadoc)
     * @see org.python.pydev.core.IInterpreterManager#setInfos(org.python.pydev.core.IInterpreterInfo[], java.util.Set, org.eclipse.core.runtime.IProgressMonitor)
     */
    @Override
    public void setInfos(IInterpreterInfo[] infos, Set<String> interpreterNamesToRestore, IProgressMonitor monitor) {
        //Set the string to persist!
        String s = AbstractInterpreterManager.getStringToPersist(infos);
        prefs.setValue(getPreferenceName(), s);
        if (prefs instanceof IPersistentPreferenceStore) {
            try {
                //expected in tests: java.io.IOException: File name not specified
                ((IPersistentPreferenceStore) prefs).save();
            } catch (Exception e) {
                String message = e.getMessage();
                if (message == null || message.indexOf("File name not specified") == -1) {
                    Log.log(e);
                }
            }
        }

        IInterpreterInfo[] interpreterInfos;

        try {
            synchronized (this.lock) {
                modificationStamp += 1;
                clearInterpretersFromPersistedString();
                persistedString = s;
                //After setting the preference, get the actual infos (will be recreated).
                interpreterInfos = internalRecreateCacheGetInterpreterInfos();

                this.restorePythopathForInterpreters(monitor, interpreterNamesToRestore);
                //When we call performOk, the editor is going to store its values, but after actually restoring the modules, we
                //need to serialize the SystemModulesManager to be used when reloading the PydevPlugin

                //This method persists all the modules managers that are within this interpreter manager
                //(so, all the SystemModulesManagers will be saved -- and can be later restored).

                for (InterpreterInfo info : this.exeToInfo.values()) {
                    try {
                        ISystemModulesManager modulesManager = info.getModulesManager();
                        Object pythonPathHelper = modulesManager.getPythonPathHelper();
                        if (!(pythonPathHelper instanceof PythonPathHelper)) {
                            continue;
                        }
                        PythonPathHelper pathHelper = (PythonPathHelper) pythonPathHelper;
                        List<String> pythonpath = pathHelper.getPythonpath();
                        if (pythonpath == null || pythonpath.size() == 0) {
                            continue;
                        }
                        modulesManager.save();
                    } catch (Throwable e) {
                        Log.log(e);
                    }
                }
            }

            //Now, last step is updating the natures (the call must NOT be locked in this case).
            this.restorePythopathForNatures(monitor);

            //We also need to restart our code-completion shell after doing that, as we may have new environment variables!
            //And in jython, changing the classpath also needs to restore it.
            for (IInterpreterInfo interpreter : interpreterInfos) {
                for (int id : AbstractShell.getAllShellIds()) {
                    AbstractShell.stopServerShell(interpreter, id);
                }
            }
            IInterpreterManagerListener[] managerListeners = listeners.getListeners();
            for (IInterpreterManagerListener iInterpreterManagerListener : managerListeners) {
                iInterpreterManagerListener.afterSetInfos(this, interpreterInfos);
            }

        } finally {
            AbstractShell.restartAllShells();
        }

        //In the regular process we do not create the global indexing for forced builtins, thus, we schedule a process
        //now which will be able to do that when checking if things are correct in the configuration.
        PydevPlugin plugin = PydevPlugin.getDefault();
        if (plugin != null && interpreterNamesToRestore != null && interpreterNamesToRestore.size() > 0) {
            SyncSystemModulesManagerScheduler syncScheduler = plugin.syncScheduler;
            ArrayList<IInterpreterInfo> lst = new ArrayList<>(interpreterNamesToRestore.size());
            for (IInterpreterInfo info : interpreterInfos) {
                if (interpreterNamesToRestore.contains(info.getExecutableOrJar())) {
                    lst.add(info);
                }
            }
            syncScheduler.addToCheck(this, lst.toArray(new IInterpreterInfo[lst.size()]));
        }
    }

    /**
     * @param interpreterNamesToRestore if null, all interpreters are restored, otherwise, only the interpreters
     *      whose name is in this set are restored.
     *
     * Must be called with the synchronized(lock) in place!!
     */
    @SuppressWarnings("unchecked")
    private void restorePythopathForInterpreters(IProgressMonitor monitor, Set<String> interpretersNamesToRestore) {
        Set<Entry<String, InterpreterInfo>> entrySet = exeToInfo.entrySet();
        for (Entry<String, InterpreterInfo> entry : entrySet) {
            String interpreterExecutableOrJar = entry.getKey();
            if (interpretersNamesToRestore != null) {
                if (!interpretersNamesToRestore.contains(interpreterExecutableOrJar)) {
                    continue; //only restore the ones specified
                }
            }
            InterpreterInfo info = entry.getValue();
            info.restorePythonpath(monitor); //that's it, info.modulesManager contains the SystemModulesManager

            List<IInterpreterObserver> participants = ExtensionHelper
                    .getParticipants(ExtensionHelper.PYDEV_INTERPRETER_OBSERVER);
            for (IInterpreterObserver observer : participants) {
                try {
                    observer.notifyDefaultPythonpathRestored(this, interpreterExecutableOrJar, monitor);
                } catch (Exception e) {
                    Log.log(e);
                }
            }
        }
    }

    private void restorePythopathForNatures(IProgressMonitor monitor) {
        IInterpreterInfo defaultInterpreterInfo;
        try {
            defaultInterpreterInfo = getDefaultInterpreterInfo(false);
        } catch (NotConfiguredInterpreterException e1) {
            defaultInterpreterInfo = null; //go on as usual... (the natures must know that they're not bound to an interpreter anymore).
        }

        FastStringBuffer buf = new FastStringBuffer();
        //Also notify that all the natures had the pythonpath changed (it's the system pythonpath, but still,
        //clients need to know about it)
        List<IPythonNature> pythonNatures;
        try {
            pythonNatures = PythonNature.getAllPythonNatures();
        } catch (IllegalStateException e1) {
            //java.lang.IllegalStateException: Workspace is closed.
            //      at org.eclipse.core.resources.ResourcesPlugin.getWorkspace(ResourcesPlugin.java:367)
            return;
        }
        for (IPythonNature nature : pythonNatures) {
            try {
                //If they have the same type of the interpreter manager, notify.
                if (this.getInterpreterType() == nature.getInterpreterType()) {
                    IPythonPathNature pythonPathNature = nature.getPythonPathNature();

                    //There's a catch here: if the nature uses some variable defined in the string substitution
                    //from the interpreter info, we need to do a full build instead of only making a notification.
                    String complete = pythonPathNature.getProjectExternalSourcePath(false)
                            + pythonPathNature.getProjectSourcePath(false);

                    PythonNature n = (PythonNature) nature;
                    String projectInterpreterName = n.getProjectInterpreterName();
                    IInterpreterInfo info;
                    if (IPythonNature.DEFAULT_INTERPRETER.equals(projectInterpreterName)) {
                        //if it's the default, let's translate it to the outside world
                        info = defaultInterpreterInfo;
                    } else {
                        synchronized (lock) {
                            info = exeToInfo.get(projectInterpreterName);
                        }
                    }

                    boolean makeCompleteRebuild = false;
                    if (info != null) {
                        Properties stringSubstitutionVariables = info.getStringSubstitutionVariables();
                        if (stringSubstitutionVariables != null) {
                            Enumeration<Object> keys = stringSubstitutionVariables.keys();
                            while (keys.hasMoreElements()) {
                                Object key = keys.nextElement();
                                buf.clear();
                                buf.append("${");
                                buf.append(key.toString());
                                buf.append("}");

                                if (complete.indexOf(buf.toString()) != -1) {
                                    makeCompleteRebuild = true;
                                    break;
                                }
                            }
                        }
                    }

                    if (!makeCompleteRebuild) {
                        //just notify that it changed
                        if (nature instanceof PythonNature) {
                            ((PythonNature) nature).clearCaches(true);
                        }
                        PythonNatureListenersManager.notifyPythonPathRebuilt(nature.getProject(), nature);
                    } else {
                        //Rebuild the whole info.
                        nature.rebuildPath();
                    }
                }
            } catch (Throwable e) {
                Log.log(e);
            }
        }
    }

}
