/*
 * Copyright 2014 Aurélien Broszniowski
 *
 * 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 io.rainfall.statistics;

import jsr166e.LongAdder;

import java.util.concurrent.ConcurrentHashMap;

/**
 * A {@link Statistics} instance holds the statistics of all results at a given point in time
 *
 * @author Aurelien Broszniowski
 */

public class Statistics<E extends Enum<E>> {

  private final String name;
  private final Enum<E>[] keys;
  private final ConcurrentHashMap<Enum, LongAdder> periodicCounters = new ConcurrentHashMap<Enum, LongAdder>();
  private final ConcurrentHashMap<Enum, LongAdder> periodicTotalLatenciesInNs = new ConcurrentHashMap<Enum, LongAdder>();
  private final ConcurrentHashMap<Enum, LongAdder> cumulativeCounters = new ConcurrentHashMap<Enum, LongAdder>();
  private final ConcurrentHashMap<Enum, LongAdder> cumulativeTotalLatenciesInNs = new ConcurrentHashMap<Enum, LongAdder>();
  private Long periodicStartTime;
  private Long cumulativeStartTime;

  public Statistics(String name, Enum<E>[] keys) {
    this.name = name;
    this.keys = keys;
    for (Enum<E> key : keys) {
      this.periodicCounters.put(key, new LongAdder());
      this.periodicTotalLatenciesInNs.put(key, new LongAdder());
      this.cumulativeCounters.put(key, new LongAdder());
      this.cumulativeTotalLatenciesInNs.put(key, new LongAdder());
    }
    this.periodicStartTime = getTimeInNs();
    this.cumulativeStartTime = this.periodicStartTime;
  }

  Statistics(String name, Enum<E>[] keys, long startTime) {
    this.keys = keys;
    this.name = name;
    for (Enum<E> key : keys) {
      this.periodicCounters.put(key, new LongAdder());
      this.periodicTotalLatenciesInNs.put(key, new LongAdder());
      this.cumulativeCounters.put(key, new LongAdder());
      this.cumulativeTotalLatenciesInNs.put(key, new LongAdder());
    }
    this.periodicStartTime = startTime;
    this.cumulativeStartTime = this.periodicStartTime;
  }

  LongAdder getPeriodicCounters(Enum result) {
    return periodicCounters.get(result);
  }

  LongAdder getPeriodicTotalLatenciesInNs(Enum result) {
    return periodicTotalLatenciesInNs.get(result);
  }

  LongAdder getCumulativeCounters(Enum result) {
    return cumulativeCounters.get(result);
  }

  LongAdder getCumulativeTotalLatencies(Enum result) {
    return cumulativeTotalLatenciesInNs.get(result);
  }

  public void increaseCounterAndSetLatencyInNs(final Enum result, final long latency) {
    periodicCounters.get(result).add(1);
    periodicTotalLatenciesInNs.get(result).add(latency);
  }

  public Enum<E>[] getKeys() {
    return keys;
  }

  public String getName() {
    return name;
  }

  protected long getTimeInNs() {
    return System.nanoTime();
  }

  public synchronized StatisticsPeek<E> peek(final long timestamp) {
    StatisticsPeek<E> statisticsPeek = new StatisticsPeek<E>(this.name, this.keys, timestamp);
    for (Enum<E> key : keys) {
      this.cumulativeCounters.get(key).add(this.periodicCounters.get(key).longValue());
      this.cumulativeTotalLatenciesInNs.get(key).add(this.periodicTotalLatenciesInNs.get(key).longValue());
    }
    long now = getTimeInNs();
    statisticsPeek.setPeriodicValues(now - periodicStartTime, periodicCounters, periodicTotalLatenciesInNs);
    statisticsPeek.setCumulativeValues(now - cumulativeStartTime, cumulativeCounters, cumulativeTotalLatenciesInNs);
    for (Enum<E> key : keys) {
      periodicCounters.get(key).reset();
      periodicTotalLatenciesInNs.get(key).reset();
    }
    this.periodicStartTime = getTimeInNs();
    return statisticsPeek;
  }
}
@@ -3403,16 +3403,155 @@
 stamp);%0A
+    long now = getTimeInNs();%0A    statisticsPeek.setPeriodicValues(now - periodicStartTime, periodicCounters, periodicTotalLatenciesInNs);%0A
     for 
@@ -3643,33 +3643,36 @@
 rs.get(key).
-longValue
+sumThenReset
 ());%0A      t
@@ -3762,166 +3762,30 @@
 ey).
-longValue());%0A    %7D%0A    long now = getTimeInNs
+sumThenReset
 ()
+)
 ;%0A    
-statisticsPeek.setPeriodicValues(now - periodicStartTime, periodicCounters, periodicTotalLatenciesInNs);
+%7D
 %0A   
@@ -3902,137 +3902,8 @@
 s);%0A
-    for (Enum%3CE%3E key : keys) %7B%0A      periodicCounters.get(key).reset();%0A      periodicTotalLatenciesInNs.get(key).reset();%0A    %7D%0A
     

fix race in statistics peek. if adds occur while snapshot is being taken, add can be lost