/*
 * Copyright 2009 Google 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.google.gwt.dev.util;

import com.google.gwt.thirdparty.guava.common.base.Preconditions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A counter which records bunch of statistics for frequently occuring
 * operations. Calculates: count, total time, average time, maximum time
 * & slow operations.
 */
public class PerfCounter {
  private static class OperationStats {
    private long count = 0;
    private boolean isCounter = false;
    private long maxTimeNanos = 0;
    private long slowCount = 0;
    private long totalSlowTimeNanos = 0;
    private long totalTimeNanos = 0;

    @Override
    public String toString() {
      StringBuilder result = new StringBuilder();

      if (!isCounter) {
        result.append(count);
        result.append("/");
        result.append(totalTimeNanos / 1000000000.0);
        result.append("/");
        result.append(totalTimeNanos / 1000000000.0 / count);
        result.append("/");
        result.append(maxTimeNanos / 1000000000.0);
        result.append("/");
        result.append(slowCount);
        result.append("/");
        result.append(totalSlowTimeNanos * 1.0 / totalTimeNanos);
        result.append(" ");
        result.append(
            "(count/total_time/avg_time/max_time/slow_count/slow_ratio)");
      } else {
        result.append(count);
        result.append(" (count)");
      }

      return result.toString();
    }
  }

  /**
   * Flag for enabling performance logging.
   */
  private static boolean enabled =
    Boolean.parseBoolean(System.getProperty("gwt.perfcounters"));

  private static final ThreadLocal<Map<String, Long>> operationsStartTime =
    new ThreadLocal<Map<String,Long>>() {
    @Override
    protected Map<String, Long> initialValue() {
      return new HashMap<String, Long>();
    }
  };

  private static final Map<String, OperationStats> operationStats =
    new HashMap<String, OperationStats>();

  /**
   * Record the end of the operation.
   */
  public static void end(String operation, long slowThresholdNano) {
    if (!enabled) {
      return;
    }
    long finishTime = System.nanoTime();

    Map<String, Long> startTimes = operationsStartTime.get();

    Long startTime;

    synchronized (startTimes) {
      startTime = startTimes.remove(operation);
    }

    Preconditions.checkNotNull(startTime);

    synchronized (operationStats) {
      OperationStats stats = getStats(operation);

      stats.count++;
      long elapsedTime = finishTime - startTime.longValue();
      stats.totalTimeNanos += elapsedTime;
      stats.maxTimeNanos = Math.max(stats.maxTimeNanos, elapsedTime);
      if (elapsedTime > slowThresholdNano) {
        stats.slowCount++;
        stats.totalSlowTimeNanos += elapsedTime;
      }
    }
  }

  /**
   * Increment counter.
   */
  public static void inc(String operation) {
    synchronized (operationStats) {
      OperationStats stats = getStats(operation);
      stats.count++;
      stats.isCounter = true;
    }
  }

  public static void print() {
    if (!enabled) {
      return;
    }
    System.out.println("------------- Perf Counters -------------");
    synchronized (operationStats) {
      List<String> keys = new ArrayList<String>(operationStats.keySet());
      Collections.sort(keys);
      for (String operation : keys) {
        System.out.println(operation + ": " + operationStats.get(operation));
      }
    }
    System.out.println("-----------------------------------------");
  }

  /**
   * Start operation.
   */
  public static void start(String operation) {
    if (!enabled) {
      return;
    }
    Map<String, Long> startTimes = operationsStartTime.get();

    synchronized (startTimes) {
      Preconditions.checkState(!startTimes.containsKey(operation));
      long startTime = System.nanoTime();
      startTimes.put(operation, new Long(startTime));
    }
  }

  private static OperationStats getStats(String operation) {
    OperationStats stats = operationStats.get(operation);
    if (stats == null) {
      stats = new OperationStats();
      operationStats.put(operation, stats);
    }
    return stats;
  }
}
