/**
   * Creates the complete description of the bug including user description, exception stack trace,
   * system properties and RM and plugin versions.
   *
   * @param userDescription the description the user entered
   * @param exception the {@link Throwable} on which the bug report is based upon
   * @param attachProcess if true, will attach the process xml
   * @param attachSystemProps if true, will attach the system properties
   * @return the human readable complete bug report
   */
  public static String createCompleteBugDescription(
      String userDescription,
      Throwable exception,
      boolean attachProcess,
      boolean attachSystemProps,
      boolean attachLog) {
    StringBuffer buffer = new StringBuffer();

    // append the user description
    buffer.append(userDescription);

    // append RapidMiner and plugin versions
    buffer.append(Tools.getLineSeparator());
    buffer.append(Tools.getLineSeparator());
    buffer.append("RapidMiner: ");
    buffer.append(RapidMiner.getVersion());
    buffer.append(Tools.getLineSeparator());
    for (Plugin plugin : Plugin.getAllPlugins()) {
      buffer.append(plugin.getName());
      buffer.append(": ");
      buffer.append(plugin.getVersion());
      buffer.append(Tools.getLineSeparator());
    }

    // append stack trace
    buffer.append(Tools.getLineSeparator());
    buffer.append(Tools.getLineSeparator());
    buffer.append(Tools.getLineSeparator());
    buffer.append(getStackTrace(exception));

    // if user selected it, attach process xml
    if (attachProcess) {
      buffer.append(Tools.getLineSeparator());
      buffer.append(Tools.getLineSeparator());
      buffer.append("Process:");
      buffer.append(Tools.getLineSeparator());
      buffer.append("------------");
      buffer.append(Tools.getLineSeparator());
      buffer.append(Tools.getLineSeparator());
      String xmlProcess;
      if (RapidMinerGUI.getMainFrame().getProcess() != null) {
        try {
          xmlProcess = RapidMinerGUI.getMainFrame().getProcess().getRootOperator().getXML(false);
        } catch (Throwable t) {
          xmlProcess = "could not read: " + t;
        }
      } else {
        xmlProcess = "no process available";
      }
      buffer.append(xmlProcess);
    }

    // if user agreed to it, attach system properties
    if (attachSystemProps) {
      buffer.append(Tools.getLineSeparator());
      buffer.append(Tools.getLineSeparator());
      buffer.append(Tools.getLineSeparator());
      buffer.append(getProperties());
    }

    if (attachLog) {
      buffer.append(Tools.getLineSeparator());
      buffer.append(Tools.getLineSeparator());
      buffer.append(Tools.getLineSeparator());
      buffer.append(getRelevantLogContent());
    }

    return buffer.toString();
  }
  /**
   * Creates the BugZilla bugreport.
   *
   * @param client the logged in BugZilla client
   * @param exception the exception which was thrown by the bug
   * @param userSummary summary of the bug
   * @param completeDescription description of the bug
   * @param component the component which malfunctionied
   * @param version the RM version
   * @param severity the severity of the bug
   * @param platform the platform (e.g. PC or Mac)
   * @param os the OS
   * @param attachments a list of optional attachements
   * @param attachProcess if <code>true</code>, will attach the current process xml
   * @param attachSystemProps if <code>true</code>, will attach system properties
   * @throws Exception
   */
  public static void createBugZillaReport(
      XmlRpcClient client,
      Throwable exception,
      String userSummary,
      String completeDescription,
      String component,
      String version,
      String severity,
      String platform,
      String os,
      File[] attachments,
      boolean attachProcess,
      boolean attachSystemProps,
      boolean attachLogFile)
      throws Exception {

    // create temp files with all the data we need
    // create process xml file for later attachement if user agreed to it
    File processFile = File.createTempFile("_process", ".xml");
    processFile.deleteOnExit();
    String xmlProcess;
    if (RapidMinerGUI.getMainFrame().getProcess().getProcessLocation() != null) {
      try {
        xmlProcess = RapidMinerGUI.getMainFrame().getProcess().getProcessLocation().getRawXML();
      } catch (Throwable t) {
        xmlProcess = "could not read: " + t;
      }
    } else {
      xmlProcess = "no process available";
    }
    writeFile(processFile, xmlProcess);

    // create system properties tempfile for later attachement if user agreed to it
    File propertiesFile = File.createTempFile("_properties", ".txt");
    propertiesFile.deleteOnExit();
    writeFile(propertiesFile, getProperties());

    // create log tempfile (last MAX_LOGFILE_LINES rows) for later attachement
    File logTempFile = File.createTempFile("_log", ".txt");
    logTempFile.delete();
    writeFile(logTempFile, getRelevantLogContent());

    // append the RM version to the description
    StringBuffer buffer = new StringBuffer(completeDescription);
    buffer.append(Tools.getLineSeparator());
    buffer.append(Tools.getLineSeparator());
    buffer.append(getStackTrace(exception));
    buffer.append(Tools.getLineSeparator());
    buffer.append(Tools.getLineSeparator());
    buffer.append("RapidMiner: ");
    buffer.append(RapidMiner.getVersion());
    buffer.append(Tools.getLineSeparator());
    for (Plugin plugin : Plugin.getAllPlugins()) {
      buffer.append(plugin.getName());
      buffer.append(": ");
      buffer.append(plugin.getVersion());
      buffer.append(Tools.getLineSeparator());
    }
    completeDescription = buffer.toString();

    // call BugZilla via xml-rpc
    XmlRpcClient rpcClient = client;

    Map<String, String> bugMap = new HashMap<>();
    bugMap.put("product", "RapidMiner");
    bugMap.put("component", component);
    bugMap.put("summary", userSummary);
    bugMap.put("description", completeDescription);
    bugMap.put("version", version);
    bugMap.put("op_sys", os);
    bugMap.put("platform", platform);
    bugMap.put("severity", severity);
    bugMap.put("status", "NEW");

    Map createResult = (Map) rpcClient.execute("Bug.create", new Object[] {bugMap});
    // LogService.getRoot().fine("Bug submitted successfully. Bug ID: " +
    // createResult.get("id"));
    LogService.getRoot()
        .log(Level.FINE, "com.rapidminer.tools.BugReport.bug_submitted", createResult.get("id"));

    String id = String.valueOf(createResult.get("id"));
    Map<String, Object> attachmentMap = new HashMap<>();
    FileInputStream fileInputStream = null;
    // add process xml file attachment if selected
    if (attachProcess) {
      attachmentMap.put("ids", new String[] {id});
      // BugZilla API states Base64 encoded string is needed, but it does not work
      // attachmentMap.put("data", Base64.encodeFromFile(processFile.getPath()));
      try {
        fileInputStream = new FileInputStream(processFile);
        byte[] data = new byte[(int) processFile.length()];
        fileInputStream.read(data);
        attachmentMap.put("data", data);
        attachmentMap.put("file_name", "process.xml");
        attachmentMap.put("summary", "process.xml");
        attachmentMap.put("content_type", "application/xml");

        createResult = (Map) rpcClient.execute("Bug.add_attachment", new Object[] {attachmentMap});
        attachmentMap.clear();
      } finally {
        if (fileInputStream != null) {
          fileInputStream.close();
        }
      }
    }

    // add system properties file attachment if selected
    if (attachSystemProps) {
      attachmentMap.put("ids", new String[] {id});
      // BugZilla API states Base64 encoded string is needed, but it does not work
      // attachmentMap.put("data", Base64.encodeFromFile(propertiesFile.getPath()));
      try {
        fileInputStream = new FileInputStream(propertiesFile);
        byte[] data = new byte[(int) propertiesFile.length()];
        fileInputStream.read(data);
        attachmentMap.put("data", data);
        attachmentMap.put("file_name", "system-properties.txt");
        attachmentMap.put("summary", "system-properties.txt");
        attachmentMap.put("content_type", "text/plain");

        createResult = (Map) rpcClient.execute("Bug.add_attachment", new Object[] {attachmentMap});
        attachmentMap.clear();
      } finally {
        if (fileInputStream != null) {
          fileInputStream.close();
        }
      }
    }

    // add rm.log file attachment
    if (attachLogFile) {
      attachmentMap.put("ids", new String[] {id});
      // BugZilla API states Base64 encoded string is needed, but it does not work
      // attachmentMap.put("data", Base64.encodeFromFile(propertiesFile.getPath()));
      try {
        fileInputStream = new FileInputStream(logTempFile);
        byte[] data = new byte[(int) logTempFile.length()];
        fileInputStream.read(data);
        attachmentMap.put("data", data);
        attachmentMap.put("file_name", "rm.log");
        attachmentMap.put("summary", "rm.log");
        attachmentMap.put("content_type", "text/plain");

        createResult = (Map) rpcClient.execute("Bug.add_attachment", new Object[] {attachmentMap});
        attachmentMap.clear();
      } finally {
        if (fileInputStream != null) {
          fileInputStream.close();
        }
      }
    }

    // add attachments by user
    for (File file : attachments) {
      attachmentMap.put("ids", new String[] {id});
      // BugZilla API states Base64 encoded string is needed, but it does not work
      // attachmentMap.put("data", Base64.encodeFromFile(file.getPath()));
      try {
        fileInputStream = new FileInputStream(file);
        byte[] data = new byte[(int) file.length()];
        fileInputStream.read(data);
        attachmentMap.put("data", data);
        attachmentMap.put("file_name", file.getName());
        attachmentMap.put("summary", file.getName());
        attachmentMap.put("content_type", "application/data");

        createResult = (Map) rpcClient.execute("Bug.add_attachment", new Object[] {attachmentMap});
        attachmentMap.clear();
      } finally {
        if (fileInputStream != null) {
          fileInputStream.close();
        }
      }
    }
  }