/**
   * Return a host report based on the current state (similar to <code>gstat -a</code>).
   *
   * <p>Note: The report will not be accurate immediately as the {@link GangliaService} needs to
   * build up a model of the current state of the monitored hosts.
   *
   * @param hosts The hosts for which host reports will be returned.
   * @param reportOn The metrics to be reported for each host. The {@link IHostReport#getMetrics()}
   *     is an ordered map and will reflect the metrics in the order in which they are requested
   *     here.
   * @param comparator The comparator used to order the {@link IHostReport}s (optional).
   * @return The {@link IHostReport}s for each specified host ordered by the given {@link
   *     Comparator}.
   */
  public IHostReport[] getHostReport(
      final String[] hosts, final String[] reportOn, final Comparator<IHostReport> comparator) {

    if (reportOn == null || reportOn.length == 0) throw new IllegalArgumentException();

    //		if (comparator == null)
    //			throw new IllegalArgumentException();

    final IHostReport[] a = new IHostReport[hosts.length];

    for (int i = 0; i < a.length; i++) {

      final String hostName = hosts[i];

      // Note: This map preserves the insert order of the metrics.
      final Map<String, IGangliaMetricMessage> m =
          new LinkedHashMap<String, IGangliaMetricMessage>();

      for (String metricName : reportOn) {

        final TimestampMetricValue tmv = gangliaState.getMetric(hostName, metricName);

        if (tmv == null) {
          // No score for that metric for that host.
          continue;
        }

        final Object value = tmv.getValue();

        if (value == null) {
          // Should never happen.
          continue;
        }

        final IGangliaMetricMessage metricValue =
            metricFactory.newMetricMessage(hostName, tmv.getMetadata(), false /* spoof */, value);

        if (log.isDebugEnabled())
          log.debug(
              "host="
                  + hostName
                  + ", metric="
                  + metricName
                  + ", value="
                  + value
                  + ", record="
                  + metricValue);

        // Mock up a metric record.
        m.put(metricName /* metricName */, metricValue);
      }

      a[i] = new HostReport(hostName, m);
    }

    // Sort
    if (comparator != null) {

      Arrays.sort(a, comparator);
    }

    return a;
  }
    @Override
    public void accept(final IGangliaMessage msg) {

      if (msg == null) return;

      /*
       * true iff the message is from this host.
       *
       * Note: A message from this host could be from either an embedded
       * ganglia service (such as ourselves) or from gmond or even gmetad
       * (if we are running on the same host as gmetad).
       *
       * It is Ok to accept metric value updates from ourselves, and this
       * makes it possible to listen to a gmond instance on the same host.
       * However, the receipt of a metric value MUST NOT cause us to send
       * out a new metric value record.
       */

      if (msg.isMetricRequest()) {

        /*
         * Metadata request.
         */
        if (isQuietPeriod()) {
          /*
           * Ignore requests during the quiet period.
           *
           * TODO Review the quite period behavior.  Is this correct?
           */
          return;
        }

        /*
         * Lookup the metric value for *this* host.
         */
        final TimestampMetricValue tmv = gangliaState.getMetric(hostName, msg.getMetricName());

        if (tmv != null && tmv.getTimestamp() != 0L) {

          /*
           * If the requested metric is known on this host then we
           * reset it now. The next time we sample that metric it will
           * have a timestamp of ZERO (0) and will be sent out to all
           * listeners. Since the timestamp is ZERO (0) (rather than a
           * valid timestamp at which the metric was last sent), the
           * metadata record will also be sent out.
           */

          tmv.resetTimestamp();

          if (log.isInfoEnabled()) log.info("Reset metric timestamp: " + msg.toString());
        }

      } else if (msg.isMetricMetadata()) {

        /*
         * Handle metadata message.
         *
         * TODO Allow a pattern for metrics that we will accept. Do not
         * store the declaration unless it satisfies that pattern.
         */

        if (gangliaState.putIfAbsent((IGangliaMetadataMessage) msg) == null) {

          if (log.isInfoEnabled()) log.info("declared by host: " + msg.toString());
        }

      } else if (msg.isMetricValue()) {

        /*
         * TODO Allow a pattern for metrics that we will accept. Do not
         * store the declaration unless it satisfies that pattern.
         */

        if (gangliaState.getMetadata(msg.getMetricName()) == null) {

          /*
           * Request the metadata for the metric.
           *
           * Note: We will not accept the metric record into our
           * internal state until we have received a metadata record
           * for that metric.
           */

          if (log.isDebugEnabled()) log.debug("No metadata for metric: " + msg.getMetricName());

          sendMessage(new GangliaRequestMessage(hostName, msg.getMetricName(), false /* spoof */));

          return;
        }

        /*
         * Update the counters for the appropriate host.
         *
         * Note: At this point we have verified that we have a metadata
         * declaration on hand for this metric so we can go ahead and
         * store the metric value in our internal state.
         */

        final IGangliaMetricMessage m2 = (IGangliaMetricMessage) msg;

        final TimestampMetricValue tmv =
            gangliaState.getMetric(msg.getHostName(), msg.getMetricName());

        // Guaranteed non-null since metadata decl exists for metric.
        assert tmv != null;

        /*
         * Update the value.
         *
         * Note: The return value is ignored. Since the metric was
         * received over the wire we ignore the age of the metric rather
         * than using the metric age to trigger a send of the metric
         * value. (I.e., avoid re-sending received metrics.)
         */
        tmv.setValue(m2.getValue());

        if (log.isDebugEnabled()) log.debug("Updated value: " + msg);

      } else {

        log.error("Unknown message type: " + msg);
      }
    }
  /**
   * {@inheritDoc}
   *
   * <p>This routine is typically invoked by {@link IGangliaMetricsCollector}s. However, you can
   * also simply invoke it directly.
   *
   * <p>Note: In order to get a nice metadata declaration for the record, the application should
   * also register an {@link IGangliaMetadataFactory}.
   *
   * @param metricName The name of the metric.
   * @param value The metric value.
   * @see #getMetadataFactory()
   */
  @Override
  public void setMetric(final String metricName, final Object value) {

    final GangliaSender sender = this.gangliaSender;

    if (sender != null) {

      try {

        if (metricName == null) {
          log.warn("Metric was emitted with no name.");
          return;
        }

        if (value == null) {
          log.warn("Metric was emitted with a null value: metricName=" + metricName);
          return;
        }

        IGangliaMetadataMessage decl;
        {

          // Look for a pre-declared metric.
          decl = gangliaState.getMetadata(metricName);

          if (decl == null) {

            // Obtain declaration.
            decl = gangliaState.getMetadataFactory().newDecl(hostName, metricName, value);

            if (decl == null) {

              log.error("Could not declare: " + metricName);
              return;
            }

            // Atomically declare/resolve.
            decl = gangliaState.putIfAbsent(decl);
          }
        }

        /*
         * Lookup the current value for the metric.
         *
         * Note: At this point we are guaranteed that a metadata
         * declaration exists so the return value must be non-null.
         */
        final TimestampMetricValue tmv =
            gangliaState.getMetric(getHostName(), decl.getMetricName());

        // Return must be non-null since declaration exists.
        assert tmv != null;

        // Should be the same declaration reference.
        assert decl == tmv.getMetadata();

        if (tmv.getTimestamp() == 0L) {

          /*
           * Send metadata record.
           *
           * TODO Since the captured metadata record might have
           * originated on another host, we should build a new
           * metadata record either now (so we send out the record
           * with our host name) or when we capture the record (as
           * part of obtaining a richer metadata record object).
           */

          sendMessage(tmv.getMetadata());
        }

        // Update the current metric value.
        if (tmv.setValue(value)) {

          /*
           * Send the metric record.
           *
           * Note: Either the metric value has never been transmitted,
           * or it has changed significantly, or TMax might expire if
           * we do not retransmit it now.
           */

          // update the timestamp when sending out the metric value.
          tmv.update();

          final IGangliaMetricMessage msg =
              metricFactory.newMetricMessage(getHostName(), decl, false /* spoof */, value);

          sendMessage(msg);
        }

      } catch (Throwable e) {

        log.warn(e, e);
      }
    }
  }