/*
 * 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 org.apache.geode.internal.statistics;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.logging.log4j.Logger;

import org.apache.geode.CancelCriterion;
import org.apache.geode.Statistics;
import org.apache.geode.SystemFailure;
import org.apache.geode.logging.internal.executors.LoggingExecutors;
import org.apache.geode.logging.internal.log4j.api.LogService;

public class CallbackSampler {
  private static final Logger logger = LogService.getLogger();
  private StatisticsManager statisticsManager;
  private final CancelCriterion cancelCriterion;
  private long sampleIntervalNanos;
  private ScheduledExecutorService executor;
  private final StatSamplerStats statSamplerStats;

  public CallbackSampler(final CancelCriterion cancelCriterion,
      final StatSamplerStats statSamplerStats) {
    this.cancelCriterion = cancelCriterion;
    this.statSamplerStats = statSamplerStats;
  }

  public void start(StatisticsManager statisticsManager, int sampleInterval, TimeUnit timeUnit) {
    ScheduledExecutorService executor =
        LoggingExecutors.newSingleThreadScheduledExecutor("CallbackSampler");
    start(executor, statisticsManager, sampleInterval, timeUnit);
  }

  void start(ScheduledExecutorService executor, StatisticsManager statisticsManager,
      int sampleInterval, TimeUnit timeUnit) {
    stop();
    this.statisticsManager = statisticsManager;
    this.executor = executor;

    executor.scheduleAtFixedRate(this::sampleCallbacks, sampleInterval, sampleInterval, timeUnit);
  }

  private void sampleCallbacks() {
    if (cancelCriterion.isCancelInProgress()) {
      executor.shutdown();
    }
    int errors = 0;
    int suppliers = 0;
    long start = System.nanoTime();
    try {
      for (Statistics stats : statisticsManager.getStatsList()) {
        StatisticsImpl statistics = (StatisticsImpl) stats;
        errors += statistics.updateSuppliedValues();
        suppliers += statistics.getSupplierCount();
      }
    } catch (VirtualMachineError e) {
      SystemFailure.initiateFailure(e);
    } catch (Throwable throwable) {
      logger.error("Error invoking statistic suppliers", throwable);
    } finally {
      long end = System.nanoTime();
      statSamplerStats.incSampleCallbackDuration(TimeUnit.NANOSECONDS.toMillis(end - start));
      statSamplerStats.incSampleCallbackErrors(errors);
      statSamplerStats.setSampleCallbacks(suppliers);
    }

  }

  public void stop() {
    if (executor != null) {
      executor.shutdown();
    }
  }
}
