/**
   * Create a list with histogram results that would be serialized to JSON like this
   *
   * <p>[{ x: -1893456000, y: 92228531 }, { x: -1577923200, y: 106021568 }]
   */
  protected List<Map<String, Long>> formatHistogramResults(
      DateHistogramResult histogram, int maxDataPoints, boolean allQuery) {
    final List<Map<String, Long>> points = Lists.newArrayList();
    final Map<String, Long> histogramResults = histogram.getResults();

    DateTime from;
    if (allQuery) {
      String firstTimestamp = histogramResults.entrySet().iterator().next().getKey();
      from = new DateTime(Long.parseLong(firstTimestamp) * 1000, DateTimeZone.UTC);
    } else {
      from = DateTime.parse(histogram.getHistogramBoundaries().getFrom());
    }
    final DateTime to = DateTime.parse(histogram.getHistogramBoundaries().getTo());
    final MutableDateTime currentTime = new MutableDateTime(from);

    final Duration step = estimateIntervalStep(histogram.getInterval());
    final int dataPoints = (int) ((to.getMillis() - from.getMillis()) / step.getMillis());

    // using the absolute value guarantees, that there will always be enough values for the given
    // resolution
    final int factor =
        (maxDataPoints != -1 && dataPoints > maxDataPoints) ? dataPoints / maxDataPoints : 1;

    int index = 0;
    floorToBeginningOfInterval(histogram.getInterval(), currentTime);
    while (currentTime.isBefore(to) || currentTime.isEqual(to)) {
      if (index % factor == 0) {
        String timestamp = Long.toString(currentTime.getMillis() / 1000);
        Long result = histogramResults.get(timestamp);
        Map<String, Long> point = Maps.newHashMap();
        point.put("x", Long.parseLong(timestamp));
        point.put("y", result != null ? result : 0);
        points.add(point);
      }
      index++;
      nextStep(histogram.getInterval(), currentTime);
    }

    return points;
  }
  public Result histogram(
      String q,
      String rangeType,
      int relative,
      String from,
      String to,
      String keyword,
      String interval,
      String streamId,
      int maxDataPoints) {
    if (q == null || q.isEmpty()) {
      q = "*";
    }

    // Interval.
    if (interval == null
        || interval.isEmpty()
        || !SearchTools.isAllowedDateHistogramInterval(interval)) {
      interval = "minute";
    }

    // Determine timerange type.
    TimeRange timerange;
    try {
      timerange = TimeRange.factory(rangeType, relative, from, to, keyword);
    } catch (InvalidRangeParametersException e2) {
      return status(
          400, views.html.errors.error.render("Invalid range parameters provided.", e2, request()));
    } catch (IllegalArgumentException e1) {
      return status(
          400, views.html.errors.error.render("Invalid range type provided.", e1, request()));
    }

    String filter = null;
    if (streamId != null && !streamId.isEmpty()) {
      filter = "streams:" + streamId;
    }

    try {
      UniversalSearch search = searchFactory.queryWithRangeAndFilter(q, timerange, filter);
      DateHistogramResult histogram = search.dateHistogram(interval);
      List<Map<String, Long>> results =
          formatHistogramResults(histogram, maxDataPoints, relative == 0);

      Map<String, Object> result = Maps.newHashMap();
      AbsoluteRange boundaries = histogram.getHistogramBoundaries();
      result.put("time", histogram.getTookMs());
      result.put("interval", histogram.getInterval());
      result.put("values", results);
      result.put("from", boundaries.getFrom());
      result.put("to", boundaries.getTo());

      return ok(Json.toJson(result));
    } catch (IOException e) {
      return internalServerError("io exception");
    } catch (APIException e) {
      if (e.getHttpCode() == 400) {
        // This usually means the field does not have a numeric type. Pass through!
        return badRequest();
      }

      return internalServerError("api exception " + e);
    }
  }