/**
 * Copyright 2015 Netflix, Inc.
 *
 * 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 com.netflix.spectator.api;

import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Base class to make it easier to implement a simple registry that only needs to customise the
 * types returned for Counter, DistributionSummary, and Timer calls.
 */
public abstract class AbstractRegistry implements Registry {

  private final Clock clock;

  private final ConcurrentHashMap<Id, Meter> meters;

  /**
   * Create a new instance.
   *
   * @param clock
   *     Clock used for performing all timing measurements.
   */
  public AbstractRegistry(Clock clock) {
    this.clock = clock;
    meters = new ConcurrentHashMap<>();
  }

  /**
   * Create a new counter instance for a given id.
   *
   * @param id
   *     Identifier used to lookup this meter in the registry.
   * @return
   *     New counter instance.
   */
  protected abstract Counter newCounter(Id id);

  /**
   * Create a new distribution summary instance for a given id.
   *
   * @param id
   *     Identifier used to lookup this meter in the registry.
   * @return
   *     New distribution summary instance.
   */
  protected abstract DistributionSummary newDistributionSummary(Id id);

  /**
   * Create a new timer instance for a given id.
   *
   * @param id
   *     Identifier used to lookup this meter in the registry.
   * @return
   *     New timer instance.
   */
  protected abstract Timer newTimer(Id id);

  @Override public final Clock clock() {
    return clock;
  }

  @Override public final Id createId(String name) {
    return new DefaultId(name, TagList.EMPTY);
  }

  @Override public final Id createId(String name, Iterable<Tag> tags) {
    return new DefaultId(name, TagList.create(tags));
  }

  private void logTypeError(Id id, Class<?> desired, Class<?> found) {
    final String dtype = desired.getName();
    final String ftype = found.getName();
    final String msg = String.format("cannot access '%s' as a %s, it already exists as a %s",
      id, dtype, ftype);
    Throwables.propagate(new IllegalStateException(msg));
  }

  private void addToAggr(Meter aggr, Meter meter) {
    if (aggr instanceof AggrMeter) {
      ((AggrMeter) aggr).add(meter);
    } else {
      logTypeError(meter.id(), meter.getClass(), aggr.getClass());
    }
  }

  private Meter putIfAbsent(Id id, Meter m, Meter fallback) {
    return (meters.size() >= Config.maxNumberOfMeters()) ? fallback : meters.putIfAbsent(id, m);
  }

  @Override public final void register(Meter meter) {
    Meter m = meters.get(meter.id());
    if (m == null) {
      if (meters.size() >= Config.maxNumberOfMeters()) {
        return;
      }
      AggrMeter aggr = new AggrMeter(meter.id());
      m = meters.putIfAbsent(meter.id(), aggr);
      if (m == null) {
        aggr.add(meter);
      } else {
        addToAggr(m, meter);
      }
    } else {
      addToAggr(m, meter);
    }
  }

  @Override public final Counter counter(Id id) {
    Meter m = meters.get(id);
    if (m == null) {
      Counter c = newCounter(id);
      m = putIfAbsent(id, c, NoopCounter.INSTANCE);
      if (m == null) {
        m = c;
      }
    }
    if (!(m instanceof Counter)) {
      logTypeError(id, Counter.class, m.getClass());
      m = NoopCounter.INSTANCE;
    }
    return (Counter) m;
  }

  @Override public final DistributionSummary distributionSummary(Id id) {
    Meter m = meters.get(id);
    if (m == null) {
      DistributionSummary s = newDistributionSummary(id);
      m = putIfAbsent(id, s, NoopDistributionSummary.INSTANCE);
      if (m == null) {
        m = s;
      }
    }
    if (!(m instanceof DistributionSummary)) {
      logTypeError(id, DistributionSummary.class, m.getClass());
      m = NoopDistributionSummary.INSTANCE;
    }
    return (DistributionSummary) m;
  }

  @Override public final Timer timer(Id id) {
    Meter m = meters.get(id);
    if (m == null) {
      Timer t = newTimer(id);
      m = putIfAbsent(id, t, NoopTimer.INSTANCE);
      if (m == null) {
        m = t;
      }
    }
    if (!(m instanceof Timer)) {
      logTypeError(id, Timer.class, m.getClass());
      m = NoopTimer.INSTANCE;
    }
    return (Timer) m;
  }

  @Override public final Meter get(Id id) {
    return meters.get(id);
  }

  @Override public final Iterator<Meter> iterator() {
    return meters.values().iterator();
  }
}

@@ -2888,27 +2888,16 @@
 ter 
-putIfAbsent(Id id, 
+compute(
 Mete
@@ -2994,32 +2994,8 @@
  : m
-eters.putIfAbsent(id, m)
 ;%0A  
@@ -3065,65 +3065,14 @@
 ter 
-m = meters.get(meter.id());%0A    if (m == null) %7B%0A      if
+aggr =
  (me
@@ -3117,71 +3117,27 @@
 s())
- %7B
 %0A      
-  return;%0A      %7D%0A      AggrMeter aggr = new AggrMeter
+? meters.get
 (met
@@ -3148,27 +3148,24 @@
 d())
-;
 %0A      
-m =
+:
  meters.
 putI
@@ -3160,19 +3160,23 @@
  meters.
+com
 put
+e
 IfAbsent
@@ -3192,125 +3192,55 @@
 (), 
-aggr);%0A      if (m == null) %7B%0A        aggr.add(meter);%0A      %7D else %7B%0A        addToAggr(m, meter);%0A      %7D%0A    %7D else
+id -%3E new AggrMeter(id));%0A    if (aggr != null)
  %7B%0A 
@@ -3254,17 +3254,20 @@
 dToAggr(
-m
+aggr
 , meter)
@@ -3354,175 +3354,79 @@
 ers.
-get(id);%0A    if (m == null) %7B%0A      Counter c = newCounter(id);%0A      m = putIfAbsent(id, c, NoopCounter.INSTANCE);%0A      if (m == null) %7B%0A        m = c;%0A      %7D%0A    %7D
+computeIfAbsent(id, i -%3E compute(newCounter(i), NoopCounter.INSTANCE));
 %0A   
@@ -3676,211 +3676,111 @@
 ers.
-get(id);%0A    if (m == null) %7B%0A      DistributionSummary s = newDistributionSummary(id);%0A      m = putIfAbsent(id, s, NoopDistributionSummary.INSTANCE);%0A      if (m == null) %7B%0A        m = s;%0A      %7D%0A    %7D
+computeIfAbsent(id, i -%3E%0A        compute(newDistributionSummary(i), NoopDistributionSummary.INSTANCE));
 %0A   
@@ -4050,169 +4050,75 @@
 ers.
-get(id);%0A    if (m == null) %7B%0A      Timer t = newTimer(id);%0A      m = putIfAbsent(id, t, NoopTimer.INSTANCE);%0A      if (m == null) %7B%0A        m = t;%0A      %7D%0A    %7D
+computeIfAbsent(id, i -%3E compute(newTimer(i), NoopTimer.INSTANCE));
 %0A   

simplify computeIfAbsent calls in AbstractRegistry