/**
   * {@inheritDoc}
   *
   * @throws OperaRunnerException if launcher is shut down or not running
   */
  @Override
  public void startOpera() throws OperaRunnerException {
    assertLauncherAlive();

    try {
      byte[] request = LauncherStartRequest.newBuilder().build().toByteArray();

      ResponseEncapsulation res = protocol.sendRequest(MessageType.MSG_START, request);

      if (handleStatusMessage(res.getResponse()) != StatusType.RUNNING) {
        throw new IOException("launcher unable to start binary");
      }

      // Check Opera hasn't immediately exited (e.g. due to unknown arguments)
      try {
        Thread.sleep(OperaIntervals.PROCESS_START_SLEEP.getMs());
      } catch (InterruptedException e) {
        // nothing
      }

      res = protocol.sendRequest(MessageType.MSG_STATUS, request);

      if (handleStatusMessage(res.getResponse()) != StatusType.RUNNING) {
        throw new IOException(
            "Opera exited immediately; possibly incorrect arguments?  Command: "
                + binary.getCommands());
      }
    } catch (IOException e) {
      throw new OperaRunnerException("Could not start Opera: " + e.getMessage());
    }
  }
  private void init() {
    try {
      binary =
          new OperaLauncherBinary(
              settings.getLauncher().getPath(), arguments.toArray(new String[arguments.size()]));
    } catch (IOException e) {
      throw new OperaRunnerException("Unable to start launcher: " + e.getMessage());
    }

    logger.fine("Waiting for launcher connection on port " + launcherPort);

    ServerSocket listenerServer = null;
    try {
      // Setup listener server
      listenerServer = new ServerSocket(launcherPort);
      // TODO(andreastt): Unsafe int cast
      listenerServer.setSoTimeout((int) OperaIntervals.LAUNCHER_CONNECT_TIMEOUT.getMs());

      // Try to connect
      protocol = new OperaLauncherProtocol(listenerServer.accept());

      // We did it!
      logger.fine("Connected with launcher on port " + launcherPort);

      // Do the handshake!
      LauncherHandshakeRequest.Builder request = LauncherHandshakeRequest.newBuilder();
      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_HELLO, request.build().toByteArray());

      // Are we happy?
      if (res.isSuccess()) {
        logger.finer("Got launcher handshake: " + res.getResponse().toString());
      } else {
        throw new OperaRunnerException(
            "Did not get launcher handshake: " + res.getResponse().toString());
      }
    } catch (SocketTimeoutException e) {
      throw new OperaRunnerException(
          "Timeout waiting for launcher to connect on port " + launcherPort, e);
    } catch (IOException e) {
      throw new OperaRunnerException("Unable to listen to launcher port " + launcherPort, e);
    } finally {
      Closeables.closeQuietly(listenerServer);
    }
  }
  public boolean isOperaRunning(int processId) {
    if (!isLauncherRunning()) {
      return false;
    }

    try {
      LauncherStatusRequest.Builder request = LauncherStatusRequest.newBuilder();
      if (processId > 0) {
        request.setProcessid(processId);
      }

      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_STATUS, request.build().toByteArray());
      logger.finer("Getting Opera's status from launcher: " + res.getResponse().toString());

      return handleStatusMessage(res.getResponse()) == StatusType.RUNNING;
    } catch (IOException e) {
      logger.fine("Could not get state of Opera, assuming launcher has shut down");
      return false;
    }
  }
  /**
   * {@inheritDoc}
   *
   * @throws OperaRunnerException if launcher is shut down or not running
   */
  @Override
  public void stopOpera() throws OperaRunnerException {
    assertLauncherAlive();

    if (!isOperaRunning()) {
      return;
    }

    try {
      LauncherStopRequest.Builder request = LauncherStopRequest.newBuilder();

      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_STOP, request.build().toByteArray());

      if (handleStatusMessage(res.getResponse()) == StatusType.RUNNING) {
        throw new IOException("launcher unable to stop binary");
      }
    } catch (IOException e) {
      throw new OperaRunnerException("Could not stop Opera: " + e.getMessage());
    }
  }
  /**
   * Take screenshot using external program. Will not trigger a screen repaint.
   *
   * @throws OperaRunnerException if launcher is shutdown or not running
   */
  @Override
  public ScreenShotReply saveScreenshot(long timeout, String... hashes)
      throws OperaRunnerException {
    assertLauncherAlive();

    String resultMd5;
    byte[] resultBytes;
    boolean blank = false;

    try {
      LauncherScreenshotRequest.Builder request = LauncherScreenshotRequest.newBuilder();
      for (String hash : hashes) {
        request.addKnownMD5S(hash);
      }
      request.setKnownMD5STimeoutMs((int) timeout);

      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_SCREENSHOT, request.build().toByteArray());
      LauncherScreenshotResponse response = (LauncherScreenshotResponse) res.getResponse();

      resultMd5 = response.getMd5();
      resultBytes = response.getImagedata().toByteArray();

      if (response.hasBlank()) {
        blank = response.getBlank();
      }

    } catch (SocketTimeoutException e) {
      throw new OperaRunnerException("Could not get screenshot from launcher", e);
    } catch (IOException e) {
      throw new OperaRunnerException("Could not get screenshot from launcher", e);
    }

    ScreenShotReply screenshotreply = new ScreenShotReply(resultMd5, resultBytes);
    screenshotreply.setBlank(blank);
    screenshotreply.setCrashed(this.hasOperaCrashed());

    return screenshotreply;
  }