// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package google.registry.monitoring.metrics;

import static com.google.common.base.Preconditions.checkState;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;

/** A singleton registry of {@link Metric}s. */
@ThreadSafe
public final class MetricRegistryImpl implements MetricRegistry {

  private static final Logger logger = Logger.getLogger(MetricRegistryImpl.class.getName());
  private static final MetricRegistryImpl INSTANCE = new MetricRegistryImpl();

  /** The canonical registry for metrics. The map key is the metric name. */
  private final ConcurrentHashMap<String, Metric<?>> registeredMetrics = new ConcurrentHashMap<>();

  private MetricRegistryImpl() {}

  public static MetricRegistryImpl getDefault() {
    return INSTANCE;
  }

  /**
   * Creates a new event metric.
   *
   * <p>Note that the order of the labels is significant.
   */
  @Override
  public EventMetric newEventMetric(
      String name,
      String description,
      String valueDisplayName,
      ImmutableSet<LabelDescriptor> labels,
      DistributionFitter distributionFitter) {
    EventMetric metric =
        new EventMetric(name, description, valueDisplayName, distributionFitter, labels);
    registerMetric(name, metric);
    logger.info("Registered new event metric: " + name);

    return metric;
  }

  /**
   * Creates a new gauge metric.
   *
   * <p>Note that the order of the labels is significant.
   */
  @Override
  @CanIgnoreReturnValue
  public <V> Metric<V> newGauge(
      String name,
      String description,
      String valueDisplayName,
      ImmutableSet<LabelDescriptor> labels,
      Supplier<ImmutableMap<ImmutableList<String>, V>> metricCallback,
      Class<V> valueClass) {
    VirtualMetric<V> metric =
        new VirtualMetric<>(
            name, description, valueDisplayName, labels, metricCallback, valueClass);
    registerMetric(name, metric);
    logger.info("Registered new callback metric: " + name);

    return metric;
  }

  /**
   * Creates a new settable metric.
   *
   * <p>Note that the order of the labels is significant.
   */
  @Override
  public <V> SettableMetric<V> newSettableMetric(
      String name,
      String description,
      String valueDisplayName,
      ImmutableSet<LabelDescriptor> labels,
      Class<V> valueClass) {
    StoredMetric<V> metric =
        new StoredMetric<>(name, description, valueDisplayName, labels, valueClass);
    registerMetric(name, metric);
    logger.info("Registered new stored metric: " + name);

    return metric;
  }

  /**
   * Creates a new incrementable metric.
   *
   * <p>Note that the order of the labels is significant.
   */
  @Override
  public IncrementableMetric newIncrementableMetric(
      String name,
      String description,
      String valueDisplayName,
      ImmutableSet<LabelDescriptor> labels) {
    Counter metric = new Counter(name, description, valueDisplayName, labels);
    registerMetric(name, metric);
    logger.info("Registered new counter: " + name);

    return metric;
  }

  @Override
  public ImmutableList<Metric<?>> getRegisteredMetrics() {
    return ImmutableList.copyOf(registeredMetrics.values());
  }

  /**
   * Unregisters a metric.
   *
   * <p>This is a no-op if the metric is not already registered.
   *
   * <p>{@link MetricWriter} implementations should not send unregistered metrics on subsequent
   * write intervals.
   */
  @VisibleForTesting
  void unregisterMetric(String name) {
    registeredMetrics.remove(name);
    logger.info("Unregistered metric: " + name);
  }

  /** Registers a metric. */
  @VisibleForTesting
  void registerMetric(String name, Metric<?> metric) {
    Metric<?> previousMetric = registeredMetrics.putIfAbsent(name, metric);

    checkState(previousMetric == null, "Duplicate metric of same name: %s", name);
  }
}

@@ -1630,23 +1630,320 @@
 %3E();%0A%0A  
-private
+%0A  /**%0A   * Production code must use %7B@link getDefault%7D, since this returns the %7B@link MetricRegistry%7D%0A   * that %7B@link MetricReporter%7D uses.  Test code that does not use %7B@link MetricReporter%7D can%0A   * use this constructor to get an isolated instance of the registry.%0A   */%0A  @VisibleForTesting%0A  public
  MetricR

Change MetricsRegistryImpl constructor to "@VisibleForTesting public".