/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.sdk.extension.incubator.trace;

import static io.opentelemetry.api.internal.Utils.checkArgument;
import static java.util.Objects.requireNonNull;

import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.time.Duration;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Builder class for the {@link ExecutorServiceSpanProcessor}.
 *
 * @see ExecutorServiceSpanProcessor#builder(SpanExporter, ScheduledExecutorService, boolean)
 */
public class ExecutorServiceSpanProcessorBuilder {

  // Visible for testing
  static final long DEFAULT_SCHEDULE_DELAY_MILLIS = 5000;
  // Visible for testing
  static final int DEFAULT_MAX_QUEUE_SIZE = 2048;
  // Visible for testing
  static final int DEFAULT_MAX_EXPORT_BATCH_SIZE = 512;
  // Visible for testing
  static final int DEFAULT_EXPORT_TIMEOUT_MILLIS = 30_000;
  // Visible for testing
  static final int WORKER_SCHEDULE_INTERVAL_NANOS = 100_000;

  private final SpanExporter spanExporter;
  private final boolean ownsExecutorService;
  private final ScheduledExecutorService executorService;
  private long scheduleDelayNanos = TimeUnit.MILLISECONDS.toNanos(DEFAULT_SCHEDULE_DELAY_MILLIS);
  private int maxQueueSize = DEFAULT_MAX_QUEUE_SIZE;
  private int maxExportBatchSize = DEFAULT_MAX_EXPORT_BATCH_SIZE;
  private long exporterTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(DEFAULT_EXPORT_TIMEOUT_MILLIS);
  private long workerScheduleIntervalNanos = WORKER_SCHEDULE_INTERVAL_NANOS;

  ExecutorServiceSpanProcessorBuilder(
      SpanExporter spanExporter,
      ScheduledExecutorService executorService,
      boolean ownsExecutorService) {
    this.spanExporter = spanExporter;
    this.ownsExecutorService = ownsExecutorService;
    this.executorService = executorService;
  }

  /**
   * Sets the delay interval between two consecutive exports. If unset, defaults to {@value
   * DEFAULT_SCHEDULE_DELAY_MILLIS}ms.
   */
  public ExecutorServiceSpanProcessorBuilder setScheduleDelay(long delay, TimeUnit unit) {
    requireNonNull(unit, "unit");
    checkArgument(delay >= 0, "delay must be non-negative");
    scheduleDelayNanos = unit.toNanos(delay);
    return this;
  }

  /**
   * Sets the delay interval between two consecutive exports. If unset, defaults to {@value
   * DEFAULT_SCHEDULE_DELAY_MILLIS}ms.
   */
  public ExecutorServiceSpanProcessorBuilder setScheduleDelay(Duration delay) {
    requireNonNull(delay, "delay");
    return setScheduleDelay(delay.toNanos(), TimeUnit.NANOSECONDS);
  }

  // Visible for testing
  long getScheduleDelayNanos() {
    return scheduleDelayNanos;
  }

  /**
   * Sets the maximum time an export will be allowed to run before being cancelled. If unset,
   * defaults to {@value DEFAULT_EXPORT_TIMEOUT_MILLIS}ms.
   */
  public ExecutorServiceSpanProcessorBuilder setExporterTimeout(long timeout, TimeUnit unit) {
    requireNonNull(unit, "unit");
    checkArgument(timeout >= 0, "timeout must be non-negative");
    exporterTimeoutNanos = unit.toNanos(timeout);
    return this;
  }

  /**
   * Sets the maximum time an export will be allowed to run before being cancelled. If unset,
   * defaults to {@value DEFAULT_EXPORT_TIMEOUT_MILLIS}ms.
   */
  public ExecutorServiceSpanProcessorBuilder setExporterTimeout(Duration timeout) {
    requireNonNull(timeout, "timeout");
    return setExporterTimeout(timeout.toNanos(), TimeUnit.NANOSECONDS);
  }

  // Visible for testing
  long getExporterTimeoutNanos() {
    return exporterTimeoutNanos;
  }

  /**
   * Sets the maximum number of Spans that are kept in the queue before start dropping.
   *
   * <p>See the BatchSampledSpansProcessor class description for a high-level design description of
   * this class.
   *
   * <p>Default value is {@code 2048}.
   *
   * @param maxQueueSize the maximum number of Spans that are kept in the queue before start
   *     dropping.
   * @return this.
   * @see ExecutorServiceSpanProcessorBuilder#DEFAULT_MAX_QUEUE_SIZE
   */
  public ExecutorServiceSpanProcessorBuilder setMaxQueueSize(int maxQueueSize) {
    this.maxQueueSize = maxQueueSize;
    return this;
  }

  // Visible for testing
  int getMaxQueueSize() {
    return maxQueueSize;
  }

  /**
   * Sets the maximum batch size for every export. This must be smaller or equal to {@code
   * maxQueuedSpans}.
   *
   * <p>Default value is {@code 512}.
   *
   * @param maxExportBatchSize the maximum batch size for every export.
   * @return this.
   * @see ExecutorServiceSpanProcessorBuilder#DEFAULT_MAX_EXPORT_BATCH_SIZE
   */
  public ExecutorServiceSpanProcessorBuilder setMaxExportBatchSize(int maxExportBatchSize) {
    checkArgument(maxExportBatchSize > 0, "maxExportBatchSize must be positive.");
    this.maxExportBatchSize = maxExportBatchSize;
    return this;
  }

  /**
   * Sets the delay interval between two consecutive runs of the worker job. If unset, defaults to
   * {@value WORKER_SCHEDULE_INTERVAL_NANOS}ns.
   */
  public ExecutorServiceSpanProcessorBuilder setWorkerScheduleInterval(Duration interval) {
    requireNonNull(interval, "interval");
    return setWorkerScheduleInterval(interval.toNanos(), TimeUnit.NANOSECONDS);
  }

  /**
   * Sets the delay interval between two consecutive runs of the worker job. If unset, defaults to
   * {@value WORKER_SCHEDULE_INTERVAL_NANOS}ms.
   */
  public ExecutorServiceSpanProcessorBuilder setWorkerScheduleInterval(
      long interval, TimeUnit unit) {
    requireNonNull(unit, "unit");
    checkArgument(interval >= 0, "interval must be non-negative");
    workerScheduleIntervalNanos = unit.toNanos(interval);
    return this;
  }

  // Visible for testing
  long getWorkerScheduleInterval() {
    return workerScheduleIntervalNanos;
  }

  // Visible for testing
  int getMaxExportBatchSize() {
    return maxExportBatchSize;
  }

  /**
   * Returns a new {@link ExecutorServiceSpanProcessor} that batches, then converts spans to proto
   * and forwards them to the given {@code spanExporter}.
   *
   * @return a new {@link ExecutorServiceSpanProcessor}.
   * @throws NullPointerException if the {@code spanExporter} is {@code null}.
   */
  public ExecutorServiceSpanProcessor build() {
    return new ExecutorServiceSpanProcessor(
        spanExporter,
        scheduleDelayNanos,
        maxQueueSize,
        maxExportBatchSize,
        exporterTimeoutNanos,
        executorService,
        ownsExecutorService,
        workerScheduleIntervalNanos);
  }
}
