/**
   * 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, Process process, int clientPort) throws Exception {
    stdOutReader = new ThreadStreamReader(process.getInputStream());
    stdErrReader = new ThreadStreamReader(process.getErrorStream());
    stdOutReader.start();
    stdErrReader.start();

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

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

    this.webServer.start();

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

    this.client = client;
  }
  /** Called when the server is requesting some input from this class. */
  public Object execute(XmlRpcRequest request) throws XmlRpcException {
    waitingForInput = true;
    inputReceived = null;
    boolean needInput = true;

    String stdOutContents = stdOutReader.getAndClearContents();
    String stderrContents = stdErrReader.getAndClearContents();
    // let the busy loop from execInterpreter free and enter a busy loop
    // in this function until execInterpreter gives us an input
    setNextResponse(new InterpreterResponse(stdOutContents, stderrContents, 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;
  }
  /**
   * Executes a given line in the interpreter.
   *
   * @param command the command to be executed in the client
   */
  public void execInterpreter(
      final String command,
      final ICallback<Object, InterpreterResponse> onResponseReceived,
      final ICallback<Object, Tuple<String, String>> onContentsReceived) {
    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 {
      // 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 Tuple<String, Boolean> exec() throws XmlRpcException {

              if (client == null) {
                return new Tuple<String, Boolean>(
                    "PydevConsoleCommunication.client is null (cannot communicate with server).",
                    false);
              }

              Object[] execute = (Object[]) client.execute("addExec", new Object[] {command});

              Object object = execute[0];
              boolean more;

              String errorContents = null;
              if (object instanceof Boolean) {
                more = (Boolean) object;

              } else {
                String str = object.toString();

                String lower = str.toLowerCase();
                if (lower.equals("true") || lower.equals("1")) {
                  more = true;
                } else if (lower.equals("false") || lower.equals("0")) {
                  more = false;
                } else {
                  more = false;
                  errorContents = str;
                }
              }
              return new Tuple<String, Boolean>(errorContents, more);
            }

            @Override
            protected IStatus run(IProgressMonitor monitor) {
              boolean needInput = false;
              try {

                Tuple<String, Boolean> executed = null;

                // the 1st time 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.
                int commAttempts = 0;
                int maximumAttempts = InteractiveConsolePrefs.getMaximumAttempts();
                // System.out.println(maximumAttempts);

                while (true) {
                  if (monitor.isCanceled()) {
                    return Status.CANCEL_STATUS;
                  }
                  executed = exec();

                  // executed.o1 is not null only if we had an error

                  String refusedConnPattern =
                      "Failed to read servers response"; // Was "refused", but it didn't
                  // work on non English system
                  // (in Spanish localized systems
                  // it is "rechazada")
                  // This string always works,
                  // because it is hard-coded in
                  // the XML-RPC library)
                  if (executed.o1 != null && executed.o1.indexOf(refusedConnPattern) != -1) {
                    if (firstCommWorked) {
                      break;
                    } else {
                      if (commAttempts < maximumAttempts) {
                        commAttempts += 1;
                        Thread.sleep(250);
                        executed.o1 = stdErrReader.getAndClearContents();
                        continue;
                      } else {
                        break;
                      }
                    }

                  } else {
                    break;
                  }

                  // unreachable code!! -- commented because eclipse will complain about it
                  // throw new RuntimeException("Can never get here!");
                }

                firstCommWorked = true;

                String errorContents = executed.o1;
                boolean more = executed.o2;

                String stdOutContents;
                if (errorContents == null) {
                  errorContents = stdErrReader.getAndClearContents();
                } else {
                  errorContents += "\n" + stdErrReader.getAndClearContents();
                }
                stdOutContents = stdOutReader.getAndClearContents();
                setNextResponse(
                    new InterpreterResponse(stdOutContents, errorContents, more, needInput));

              } catch (Exception e) {
                Log.log(e);
                setNextResponse(
                    new InterpreterResponse(
                        "",
                        "Exception while pushing line to console:" + e.getMessage(),
                        false,
                        needInput));
              }
              return Status.OK_STATUS;
            }
          };

      job.schedule();
    }

    int i = 500; // only get contents each 500 millis...

    // busy loop until we have a response
    while (nextResponse == null) {
      synchronized (lock2) {
        try {
          lock2.wait(20);
        } catch (InterruptedException e) {
          //                    Log.log(e);
        }
      }

      i -= 20;

      if (i <= 0 && nextResponse == null) {
        i = 250; // after the first, get it each 250 millis
        String stderrContents = stdErrReader.getAndClearContents();
        String stdOutContents = stdOutReader.getAndClearContents();
        if (stdOutContents.length() > 0 || stderrContents.length() > 0) {
          onContentsReceived.call(new Tuple<String, String>(stdOutContents, stderrContents));
        }
      }
    }
    onResponseReceived.call(nextResponse);
  }