/**
 * 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.
 */
package org.python.pydev.debug.newconsole;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.XmlRpcHandler;
import org.apache.xmlrpc.XmlRpcRequest;
import org.apache.xmlrpc.server.XmlRpcHandlerMapping;
import org.apache.xmlrpc.server.XmlRpcNoSuchHandlerException;
import org.apache.xmlrpc.server.XmlRpcServer;
import org.apache.xmlrpc.webserver.WebServer;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.texteditor.ITextEditor;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.ICompletionState;
import org.python.pydev.core.IToken;
import org.python.pydev.core.concurrency.ConditionEvent;
import org.python.pydev.core.concurrency.ConditionEventWithValue;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.core.PydevDebugPlugin;
import org.python.pydev.debug.newconsole.env.UserCanceledException;
import org.python.pydev.debug.newconsole.prefs.InteractiveConsolePrefs;
import org.python.pydev.editor.codecompletion.AbstractPyCodeCompletion;
import org.python.pydev.editor.codecompletion.PyCalltipsContextInformation;
import org.python.pydev.editor.codecompletion.PyCodeCompletionImages;
import org.python.pydev.editor.codecompletion.PyLinkedModeCompletionProposal;
import org.python.pydev.editorinput.PyOpenEditor;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.callbacks.ICallback0;
import org.python.pydev.shared_core.io.ThreadStreamReader;
import org.python.pydev.shared_core.process.ProcessUtils;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_interactive_console.console.IScriptConsoleCommunication;
import org.python.pydev.shared_interactive_console.console.IXmlRpcClient;
import org.python.pydev.shared_interactive_console.console.InterpreterResponse;
import org.python.pydev.shared_interactive_console.console.ScriptXmlRpcClient;
import org.python.pydev.shared_ui.EditorUtils;
import org.python.pydev.shared_ui.proposals.IPyCompletionProposal;
import org.python.pydev.shared_ui.proposals.PyCompletionProposal;
import org.python.pydev.shared_ui.utils.RunInUiThread;

/**
 * Communication with Xml-rpc with the client.
 *
 * After creating the comms, a successful {@link #hello(IProgressMonitor)} message must be sent before using other methods.
 *
 * @author Fabio
 */
public class PydevConsoleCommunication implements IScriptConsoleCommunication, XmlRpcHandler {

    /**
     * XML-RPC client for sending messages to the server.
     */
    private volatile IXmlRpcClient client;

    /**
     * This is the server responsible for giving input to a raw_input() requested
     * and for opening editors (as a result of %edit in IPython)
     */
    private WebServer webServer;

    private final String[] commandArray;

    private final String[] envp;

    private StdStreamsThread stdStreamsThread;

    private class StdStreamsThread extends Thread {

        /**
         * Responsible for getting the stdout of the process.
         */
        private final ThreadStreamReader stdOutReader;

        /**
         * Responsible for getting the stderr of the process.
         */
        private final ThreadStreamReader stdErrReader;

        private volatile boolean stopped = false;

        private final Object lock = new Object();

        public StdStreamsThread(Process process, String encoding) {
            this.setName("StdStreamsThread: " + process);
            this.setDaemon(true);
            stdOutReader = new ThreadStreamReader(process.getInputStream(), true, encoding);
            stdErrReader = new ThreadStreamReader(process.getErrorStream(), true, encoding);
            stdOutReader.start();
            stdErrReader.start();
        }

        @Override
        public void run() {
            while (!stopped) {
                synchronized (lock) {
                    if (onContentsReceived != null) {
                        String stderrContents = stdErrReader.getAndClearContents();
                        String stdOutContents = stdOutReader.getAndClearContents();
                        if (stdOutContents.length() > 0 || stderrContents.length() > 0) {
                            onContentsReceived.call(new Tuple<String, String>(stdOutContents, stderrContents));
                        }
                    }
                    try {
                        lock.wait(50);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }

        public void stopLoop() {
            stopped = true;
            synchronized (lock) {
                lock.notifyAll();
            }
            stdOutReader.stopGettingOutput();
            stdErrReader.stopGettingOutput();
        }
    }

    /**
     * Initializes the xml-rpc communication.
     *
     * @param port the port where the communication should happen.
     * @param process this is the process that was spawned (server for the XML-RPC)
     *
     * @throws MalformedURLException
     */
    public PydevConsoleCommunication(int port, final Process process, int clientPort, String[] commandArray,
            String[] envp, String encoding)
            throws Exception {
        this.commandArray = commandArray;
        this.envp = envp;

        finishedExecution = new ConditionEvent(new ICallback0<Boolean>() {

            @Override
            public Boolean call() {
                try {
                    process.exitValue();
                    return true; // already exited
                } catch (Exception e) {
                    return false;
                }
            }
        }, 200);

        //start the server that'll handle input requests
        this.webServer = new WebServer(clientPort);
        XmlRpcServer serverToHandleRawInput = this.webServer.getXmlRpcServer();
        serverToHandleRawInput.setHandlerMapping(new XmlRpcHandlerMapping() {

            @Override
            public XmlRpcHandler getHandler(String handlerName) throws XmlRpcNoSuchHandlerException, XmlRpcException {
                return PydevConsoleCommunication.this;
            }
        });

        this.webServer.start();
        this.stdStreamsThread = new StdStreamsThread(process, encoding);
        this.stdStreamsThread.start();

        IXmlRpcClient client = new ScriptXmlRpcClient(process);
        client.setPort(port);

        this.client = client;

    }

    /**
     * Stops the communication with the client (passes message for it to quit).
     */
    @Override
    public void close() throws Exception {
        if (this.client != null) {
            Job job = new Job("Close console communication") {

                @Override
                protected IStatus run(IProgressMonitor monitor) {
                    try {
                        PydevConsoleCommunication.this.client.execute("close", new Object[0]);
                    } catch (Exception e) {
                        //Ok, we can ignore this one on close.
                    }
                    PydevConsoleCommunication.this.client = null;
                    return Status.OK_STATUS;
                }
            };
            job.schedule(); //finish it
        }
        if (this.stdStreamsThread != null) {
            this.stdStreamsThread.stopLoop();
            this.stdStreamsThread = null;
        }

        if (this.webServer != null) {
            this.webServer.shutdown();
            this.webServer = null;
        }
    }

    @Override
    public boolean isConnected() {
        return this.client != null;
    }

    /**
     * Variables that control when we're expecting to give some input to the server or when we're
     * adding some line to be executed
     */

    /**
     * Signals that the next command added should be sent as an input to the server.
     */
    private volatile boolean waitingForInput;

    /**
     * Input that should be sent to the server (waiting for raw_input)
     */
    private volatile String inputReceived;

    /**
     * Response that should be sent back to the shell.
     */
    private volatile ConditionEventWithValue<InterpreterResponse> nextResponse = new ConditionEventWithValue<>(null,
            20);

    /**
     * Helper to keep on busy loop.
     */
    private volatile Object lock = new Object();

    /**
     * Keeps a flag indicating that we were able to communicate successfully with the shell at least once
     * (if we haven't we may retry more than once the first time, as jython can take a while to initialize
     * the communication)
     * This is set to true on successful {@link #hello(IProgressMonitor)}
     */
    private volatile boolean firstCommWorked = false;

    private final ConditionEvent finishedExecution;

    /**
     * When non-null, the Debug Target to notify when the underlying process is suspended or running.
     */
    private IPydevConsoleDebugTarget debugTarget = null;

    private ICallback<Object, Tuple<String, String>> onContentsReceived;

    /**
     * Called when the server is requesting some input from this class.
     */
    @Override
    public Object execute(XmlRpcRequest request) throws XmlRpcException {
        String methodName = request.getMethodName();
        if ("RequestInput".equals(methodName)) {
            return requestInput();
        } else if ("IPythonEditor".equals(methodName)) {
            return openEditor(request);
        } else if ("NotifyAboutMagic".equals(methodName)) {
            return "";
        } else if ("NotifyFinished".equals(methodName)) {
            finishedExecution.set();
            return "";
        }
        Log.log("Unexpected call to execute for method name: " + methodName);
        return "";
    }

    private Object openEditor(XmlRpcRequest request) {
        try {

            String filename = request.getParameter(0).toString();
            final int lineNumber = Integer.parseInt(request.getParameter(1).toString());
            final File fileToOpen = new File(filename);

            if (!fileToOpen.exists()) {
                final OutputStream out = new FileOutputStream(fileToOpen);
                try {
                    out.close();
                } catch (final IOException ioe) {
                    // ignore
                }
            }

            RunInUiThread.async(new Runnable() {

                @Override
                public void run() {
                    IEditorPart editor = PyOpenEditor.doOpenEditorOnFileStore(fileToOpen);
                    if (editor instanceof ITextEditor && lineNumber >= 0) {
                        EditorUtils.showInEditor((ITextEditor) editor, lineNumber);
                    }
                }
            });
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private Object requestInput() {
        waitingForInput = true;
        inputReceived = null;
        boolean needInput = true;

        //let the busy loop from execInterpreter free and enter a busy loop
        //in this function until execInterpreter gives us an input
        setNextResponse(new InterpreterResponse(false, needInput));

        //busy loop until we have an input
        while (inputReceived == null) {
            synchronized (lock) {
                try {
                    lock.wait(10);
                } catch (InterruptedException e) {
                    Log.log(e);
                }
            }
        }
        return inputReceived;
    }

    @Override
    public void setOnContentsReceivedCallback(ICallback<Object, Tuple<String, String>> onContentsReceived) {
        this.onContentsReceived = onContentsReceived;
    }

    /**
     * Holding the last response (if the last response needed more input, we'll buffer contents internally until
     * we do have a suitable line and will pass it in a batch to the interpreter).
     */
    private volatile InterpreterResponse lastResponse = null;

    /**
     * List with the strings to be passed to the interpreter once we have a line that's suitable for evaluation.
     */
    private final List<String> moreBuffer = new ArrayList<>();

    /**
     * Instructs the client to raise KeyboardInterrupt and return to a clean command prompt.  This can be
     * called to terminate:
     * - infinite or excessively long processing loops (CPU bound)
     * - I/O wait (e.g. urlopen, time.sleep)
     * - asking for input from the console i.e. input(); this is a special case of the above because PyDev
     *   is involved
     * - command prompt continuation processing, so that the user doesn't have to work out the exact
     *   sequence of close brackets required to get the prompt back
     * This requires the cooperation of the client (the call to interrupt must be processed by the XMLRPC
     * server) but in most cases is better than just terminating the process.
     */
    @Override
    public void interrupt() {
        Job job = new Job("Interrupt console process") {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                try {
                    lastResponse = null;
                    setNextResponse(new InterpreterResponse(false, false));
                    moreBuffer.clear();

                    PydevConsoleCommunication.this.client.execute("interrupt", new Object[0]);
                    if (PydevConsoleCommunication.this.waitingForInput) {
                        PydevConsoleCommunication.this.inputReceived = "";
                        PydevConsoleCommunication.this.waitingForInput = false;
                    }
                } catch (Exception e) {
                    Log.log(IStatus.ERROR, "Problem interrupting python process", e);
                }
                return Status.OK_STATUS;
            }
        };
        job.schedule();
    }

    /**
     * Executes a given line in the interpreter.
     *
     * @param command the command to be executed in the client
     */
    @Override
    public void execInterpreter(String command, final ICallback<Object, InterpreterResponse> onResponseReceived) {
        setNextResponse(null);
        if (waitingForInput) {
            inputReceived = command;
            waitingForInput = false;
            //the thread that we started in the last exec is still alive if we were waiting for an input.
        } else {

            if (lastResponse != null && lastResponse.need_input == false && lastResponse.more) {
                if (command.trim().length() > 0 && Character.isWhitespace(command.charAt(0))) {
                    moreBuffer.add(command);
                    //Pass same response back again (we still need more input to try to do some evaluation).
                    onResponseReceived.call(lastResponse);
                    return;
                }
            }
            final String executeCommand;
            if (moreBuffer.size() > 0) {
                executeCommand = StringUtils.join("\n", moreBuffer) + "\n" + command;
                moreBuffer.clear();
            } else {
                executeCommand = command;
            }

            //create a thread that'll keep locked until an answer is received from the server.
            Job job = new Job("PyDev Console Communication") {

                /**
                 * Executes the needed command
                 *
                 * @return a tuple with (null, more) or (error, false)
                 *
                 * @throws XmlRpcException
                 */
                private boolean exec() throws XmlRpcException {

                    if (client == null) {
                        return false;
                    }

                    Object ret = client.execute(executeCommand.contains("\n") ? "execMultipleLines" : "execLine",
                            new Object[] { executeCommand });
                    if (!(ret instanceof Boolean)) {
                        if (ret instanceof Object[]) {
                            Object[] objects = (Object[]) ret;
                            ret = StringUtils.join(" ", objects);
                        } else {
                            ret = "" + ret;
                        }
                        if (onContentsReceived != null) {
                            onContentsReceived.call(new Tuple<String, String>("", ret.toString()));
                        }
                        return false;
                    }
                    boolean more = (Boolean) ret;
                    return more;
                }

                @Override
                protected IStatus run(IProgressMonitor monitor) {
                    final boolean needInput = false;
                    try {
                        if (!firstCommWorked) {
                            throw new Exception(
                                    "hello must be called successfully before execInterpreter can be used.");
                        }

                        finishedExecution.unset();
                        boolean more = exec();
                        if (!more) {
                            finishedExecution.waitForSet();
                        }

                        setNextResponse(new InterpreterResponse(more, needInput));

                    } catch (Exception e) {
                        Log.log(e);
                        setNextResponse(new InterpreterResponse(false, needInput));
                    }
                    return Status.OK_STATUS;
                }
            };

            job.schedule();

        }

        //busy loop until we have a response
        InterpreterResponse waitForSet = nextResponse.waitForSet();
        lastResponse = waitForSet;
        onResponseReceived.call(waitForSet);
    }

    /**
     * @return completions from the client
     */
    @Override
    public ICompletionProposal[] getCompletions(String text, String actTok, int offset, boolean showForTabCompletion)
            throws Exception {
        if (waitingForInput) {
            return new ICompletionProposal[0];
        }
        Object fromServer = client.execute("getCompletions", new Object[] { text, actTok });
        List<ICompletionProposal> ret = new ArrayList<ICompletionProposal>();

        convertConsoleCompletionsToICompletions(text, actTok, offset, fromServer, ret, showForTabCompletion);
        ICompletionProposal[] proposals = ret.toArray(new ICompletionProposal[ret.size()]);
        return proposals;
    }

    public static void convertConsoleCompletionsToICompletions(final String text, String actTok, int offset,
            Object fromServer,
            List<ICompletionProposal> ret, boolean showForTabCompletion) {
        IFilterCompletion filter = null;
        if (actTok != null && actTok.indexOf("].") != -1) {
            // Fix issue: when we request a code-completion on a list position i.e.: "lst[0]." IPython is giving us completions from the
            // filesystem, so, this is a workaround for that where we remove such completions.
            filter = new IFilterCompletion() {

                @Override
                public boolean acceptCompletion(int type, PyLinkedModeCompletionProposal completion) {
                    if (type == IToken.TYPE_IPYTHON) {
                        if (completion.getDisplayString().startsWith(".")) {
                            return false;
                        }
                    }
                    return true;
                }

            };
        }

        convertToICompletions(text, actTok, offset, fromServer, ret, showForTabCompletion, filter);
    }

    public static interface IFilterCompletion {
        boolean acceptCompletion(int type, PyLinkedModeCompletionProposal completion);
    }

    private static void convertToICompletions(final String text, String actTok, int offset, Object fromServer,
            List<ICompletionProposal> ret, boolean showForTabCompletion, IFilterCompletion filter) {
        if (fromServer instanceof Object[]) {
            Object[] objects = (Object[]) fromServer;
            fromServer = Arrays.asList(objects);
        }
        if (fromServer instanceof List) {
            int length = actTok.lastIndexOf('.');
            if (length == -1) {
                length = actTok.length();
            } else {
                length = actTok.length() - length - 1;
            }
            final String trimmedText = text.trim();

            List comps = (List) fromServer;
            for (Object o : comps) {
                if (o instanceof Object[]) {
                    //name, doc, args, type
                    Object[] comp = (Object[]) o;

                    String name = (String) comp[0];
                    String docStr = (String) comp[1];
                    int type = extractInt(comp[3]);
                    String args = AbstractPyCodeCompletion.getArgs((String) comp[2], type,
                            ICompletionState.LOOKING_FOR_INSTANCED_VARIABLE);
                    String nameAndArgs = name + args;

                    int priority = IPyCompletionProposal.PRIORITY_DEFAULT;

                    if (type == IToken.TYPE_LOCAL) {
                        priority = IPyCompletionProposal.PRIORITY_LOCALS;

                    } else if (type == IToken.TYPE_PARAM) {
                        priority = IPyCompletionProposal.PRIORITY_LOCALS_1;
                    } else if (type == IToken.TYPE_IPYTHON_MAGIC) {
                        priority = IPyCompletionProposal.PRIORTTY_IPYTHON_MAGIC;
                    }

                    //                    ret.add(new PyCompletionProposal(name,
                    //                            offset-length, length, name.length(),
                    //                            PyCodeCompletionImages.getImageForType(type), name, null, docStr, priority));

                    int cursorPos = name.length();
                    if (args.length() > 1) {
                        cursorPos += 1;
                    }

                    int replacementOffset = offset - length;
                    PyCalltipsContextInformation pyContextInformation = null;
                    if (args.length() > 2) {
                        pyContextInformation = new PyCalltipsContextInformation(args, replacementOffset + name.length()
                                + 1); //just after the parenthesis
                    } else {

                        //Support for IPython completions (non standard names)

                        //i.e.: %completions, cd ...
                        if (name.length() > 0) {

                            //magic ipython stuff (starting with %)
                            // Decrement the replacement offset _only_ if the token begins with %
                            // as ipthon completes a<tab> to %alias etc.
                            if (name.charAt(0) == '%' && text.length() > 0 && text.charAt(0) == '%') {
                                replacementOffset -= 1;

                                // handle cd -- we handle this by returning the full path from ipython
                                // TODO: perhaps we could do this for all completions
                            } else if (trimmedText.equals("cd") || trimmedText.startsWith("cd ")
                                    || trimmedText.equals("%cd") || trimmedText.startsWith("%cd ")) {

                                // text == the full search e.g. "cd works"   ; "cd workspaces/foo"
                                // actTok == the last segment of the path e.g. "foo"  ;
                                // nameAndArgs == full completion e.g. "workspaces/foo/"

                                if (showForTabCompletion) {
                                    replacementOffset = 0;
                                    length = text.length();

                                } else {
                                    if (name.charAt(0) == '/') {
                                        //Should be something as cd c:/temp/foo (and name is /temp/foo)
                                        char[] chars = text.toCharArray();
                                        for (int i = 0; i < chars.length; i++) {
                                            char c = chars[i];
                                            if (c == name.charAt(0)) {
                                                String sub = text.substring(i, text.length());
                                                if (name.startsWith(sub)) {
                                                    replacementOffset -= (sub.length() - FullRepIterable
                                                            .getLastPart(actTok)
                                                            .length());
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    PyLinkedModeCompletionProposal completion = new PyLinkedModeCompletionProposal(nameAndArgs,
                            replacementOffset, length, cursorPos,
                            PyCodeCompletionImages.getImageForType(type), nameAndArgs, pyContextInformation, docStr,
                            priority, PyCompletionProposal.ON_APPLY_DEFAULT, args, false, null);
                    if (filter == null || filter.acceptCompletion(type, completion)) {
                        ret.add(completion);
                    }
                }
            }
        }
    }

    /**
     * Extracts an int from an object
     *
     * @param objToGetInt the object that should be gotten as an int
     * @return int with the int the object represents
     */
    private static int extractInt(Object objToGetInt) {
        if (objToGetInt instanceof Integer) {
            return (Integer) objToGetInt;
        }
        return Integer.parseInt(objToGetInt.toString());
    }

    /**
     * @return the description of the given attribute in the shell
     */
    @Override
    public String getDescription(String text) throws Exception {
        if (waitingForInput) {
            return "Unable to get description: waiting for input.";
        }
        return client.execute("getDescription", new Object[] { text }).toString();
    }

    /**
     * The Debug Target to notify when the underlying process is suspended or
     * running.
     *
     * @param debugTarget
     */
    public void setDebugTarget(IPydevConsoleDebugTarget debugTarget) {
        this.debugTarget = debugTarget;
    }

    /**
     * The Debug Target to notify when the underlying process is suspended or
     * running.
     */
    public IPydevConsoleDebugTarget getDebugTarget() {
        return debugTarget;
    }

    /**
     * Common code to handle all cases of setting nextResponse so that the
     * attached debug target can be notified of effective state.
     *
     * @param nextResponse new next response
     */
    private void setNextResponse(InterpreterResponse nextResponse) {
        this.nextResponse.set(nextResponse);
        updateDebugTarget(nextResponse);
    }

    /**
     * Update the debug target (if non-null) of suspended state of console.
     * @param nextResponse2
     */
    private void updateDebugTarget(InterpreterResponse nextResponse) {
        if (debugTarget != null) {
            if (nextResponse == null || nextResponse.need_input == true) {
                debugTarget.setSuspended(false);
            } else {
                debugTarget.setSuspended(true);
            }
        }
    }

    /**
     * Request that pydevconsole connect (with pydevd) to the specified port
     *
     * @param localPort
     *            port for pydevd to connect to.
     * @throws Exception if connection fails
     */
    public void connectToDebugger(int localPort) throws Exception {
        if (waitingForInput) {
            throw new Exception("Can't connect debugger now, waiting for input");
        }
        Object result = client.execute("connectToDebugger", new Object[] { localPort });
        Exception exception = null;
        if (result instanceof Object[]) {
            Object[] resultarray = (Object[]) result;
            if (resultarray.length == 1) {
                if ("connect complete".equals(resultarray[0])) {
                    return;
                }
                if (resultarray[0] instanceof String) {
                    exception = new Exception((String) resultarray[0]);
                }
                if (resultarray[0] instanceof Exception) {
                    exception = (Exception) resultarray[0];
                }
            }
        }
        throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR,
                "pydevconsole failed to execute connectToDebugger", exception));
    }

    /**
     * Wait for an established connection.
     * @param monitor
     * @throws Exception if no suitable response is received before the timeout
     * @throws UserCanceledException if user cancelled with monitor
     */
    public void hello(IProgressMonitor monitor) throws Exception, UserCanceledException {
        int maximumAttempts = InteractiveConsolePrefs.getMaximumAttempts();
        monitor.beginTask("Establishing Connection To Console Process", maximumAttempts);
        try {
            if (firstCommWorked) {
                return;
            }

            // We'll do a connection attempt, we can try to
            // connect n times (until the 1st time the connection
            // is accepted) -- that's mostly because the server may take
            // a while to get started.

            String result = null;
            for (int commAttempts = 0; commAttempts < maximumAttempts; commAttempts++) {
                if (monitor.isCanceled()) {
                    throw new UserCanceledException("Canceled before hello was successful");
                }
                try {
                    Object resulta = client.execute("hello", new Object[] { "Hello pydevconsole" });
                    if (resulta instanceof String) {
                        result = (String) resulta;
                    } else {
                        result = StringUtils.join("", (Object[]) resulta);
                    }
                } catch (XmlRpcException e) {
                    // We'll retry in a moment
                }

                if ("Hello eclipse".equals(result)) {
                    firstCommWorked = true;
                    break;
                }

                if (result.startsWith("Console already exited with value")) {
                    // Failed, probably some error starting the process
                    break;
                }

                try {
                    Thread.sleep(250);
                } catch (InterruptedException e) {
                    // Retry now
                }
                monitor.worked(1);
            }

            if (!firstCommWorked) {
                String commandLine = this.commandArray != null ? ProcessUtils.getArgumentsAsStr(this.commandArray)
                        : "(unable to determine command line)";
                String environment = this.envp != null ? ProcessUtils.getEnvironmentAsStr(this.envp) : "null";
                throw new Exception("Failed to recive suitable Hello response from pydevconsole. Last msg received: "
                        + result + "\nCommand Line used: " + commandLine + "\n\nEnvironment:\n" + environment);
            }
        } finally {
            monitor.done();
        }
    }

    /**
     * Not required for normal pydev console
     */
    @Override
    public void linkWithDebugSelection(boolean isLinkedWithDebug) {
        throw new RuntimeException("Not implemented");
    }

    /**
     * Enable GUI Loop integration in PyDev Console
     * @param enableGuiName The name of the GUI to enable, see inputhook.py:enable_gui for list of legal names
     * @throws Exception on connection issues
     */
    public void enableGui(String enableGuiName) throws Exception {
        if (waitingForInput) {
            throw new Exception("Can't connect debugger now, waiting for input");
        }
        client.execute("enableGui", new Object[] { enableGuiName });
    }
}
