/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 opennlp.tools.cmdline;

import java.io.PrintStream;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import opennlp.tools.commons.Internal;
import opennlp.tools.log.LogPrintStream;

/**
 * The {@link PerformanceMonitor} measures increments to a counter.
 * During the computation it prints out current and average throughput
 * per second. After the computation is done it prints a final performance
 * report.
 * <p>
 * <b>Note:</b> This class is not thread safe.
 * <p>
 * <b>Note:</b> Do not use this class, internal use only!
 */
@Internal
public class PerformanceMonitor {

  private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitor.class);

  private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, runnable -> {
    Thread thread = new Thread(runnable);
    thread.setName("opennlp.tools.cmdline.PerformanceMonitor");
    thread.setDaemon(true);
    return thread;
  });

  private final String unit;

  private ScheduledFuture<?> beeperHandle;

  private volatile long startTime = -1;

  private volatile int counter;

  private final PrintStream out;

  public PerformanceMonitor(PrintStream out, String unit) {
    this.out = out;
    this.unit = unit;
  }

  public PerformanceMonitor(String unit) {
    this(new LogPrintStream(logger), unit);
  }

  public boolean isStarted() {
    return startTime != -1;
  }

  public void incrementCounter(int increment) {

    if (!isStarted())
      throw new IllegalStateException("Must be started first!");

    if (increment < 0)
      throw new IllegalArgumentException("increment must be zero or positive but was " + increment + "!");

    counter += increment;
  }

  public void incrementCounter() {
    incrementCounter(1);
  }

  public void start() {

    if (isStarted())
      throw new IllegalStateException("Already started!");

    startTime = System.currentTimeMillis();
  }


  public void startAndPrintThroughput() {

    start();

    final Runnable beeper = new Runnable() {

      private long lastTimeStamp = startTime;
      private int lastCount = counter;

      @Override
      public void run() {

        int deltaCount = counter - lastCount;

        long timePassedSinceLastCount = System.currentTimeMillis()
            - lastTimeStamp;

        double currentThroughput;

        if (timePassedSinceLastCount > 0) {
          currentThroughput = deltaCount / ((double) timePassedSinceLastCount / 1000);
        } else {
          currentThroughput = 0;
        }

        long totalTimePassed = System.currentTimeMillis() - startTime;

        double averageThroughput;
        if (totalTimePassed > 0) {
          averageThroughput = counter / (((double) totalTimePassed) / 1000);
        }
        else {
          averageThroughput = 0;
        }

        out.printf("current: %.1f " + unit + "/s avg: %.1f " + unit + "/s total: %d "
            + unit + "%n", currentThroughput, averageThroughput, counter);

        lastTimeStamp = System.currentTimeMillis();
        lastCount = counter;
      }
    };

    beeperHandle = scheduler.scheduleAtFixedRate(beeper, 1, 1, TimeUnit.SECONDS);
  }

  public void stopAndPrintFinalResult() {

    if (!isStarted())
      throw new IllegalStateException("Must be started first!");

    if (beeperHandle != null) {
      // yeah we have time to finish current
      // printing if there is one
      beeperHandle.cancel(false);
    }

    scheduler.shutdown();

    long timePassed = System.currentTimeMillis() - startTime;

    double average;
    if (timePassed > 0) {
      average = counter / (timePassed / 1000d);
    }
    else {
      average = 0;
    }

    out.printf("Average: %.1f " + unit + "/s %n", average);
    out.println("Total: " + counter + " " + unit);
    out.println("Runtime: " + timePassed / 1000d + "s");
  }
}
