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

package io.opentelemetry.instrumentation.api.instrumenter;

import static java.util.Collections.singleton;

import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.api.instrumenter.SpanSuppressors.BySpanKey;
import io.opentelemetry.instrumentation.api.instrumenter.SpanSuppressors.DelegateBySpanKind;
import io.opentelemetry.instrumentation.api.instrumenter.SpanSuppressors.Noop;
import io.opentelemetry.instrumentation.api.internal.SpanKey;
import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider;
import java.util.EnumMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

enum SpanSuppressionStrategy {
  /** Do not suppress spans at all. */
  NONE {
    @Override
    SpanSuppressor create(Set<SpanKey> spanKeys) {
      return Noop.INSTANCE;
    }
  },
  /**
   * Suppress spans by {@link SpanKind}. This is equivalent to the "legacy" suppression strategy
   * used in the agent.
   *
   * <p>Child spans of the same kind will be suppressed; e.g. if there already is a {@link
   * SpanKind#CLIENT CLIENT} span in the context, a second {@code CLIENT} span won't be started.
   */
  SPAN_KIND {
    @SuppressWarnings("ImmutableEnumChecker") // this field actually is immutable
    private final SpanSuppressor strategy;

    {
      Map<SpanKind, SpanSuppressor> delegates = new EnumMap<>(SpanKind.class);
      delegates.put(SpanKind.SERVER, new BySpanKey(singleton(SpanKey.KIND_SERVER)));
      delegates.put(SpanKind.CLIENT, new BySpanKey(singleton(SpanKey.KIND_CLIENT)));
      delegates.put(SpanKind.CONSUMER, new BySpanKey(singleton(SpanKey.KIND_CONSUMER)));
      delegates.put(SpanKind.PRODUCER, new BySpanKey(singleton(SpanKey.KIND_PRODUCER)));
      strategy = new DelegateBySpanKind(delegates);
    }

    @Override
    SpanSuppressor create(Set<SpanKey> spanKeys) {
      return strategy;
    }
  },
  /**
   * Suppress spans by the semantic convention they're supposed to represent. This strategy uses
   * {@linkplain SpanKey span keys} returned by the {@link SpanKeyProvider#internalGetSpanKey()}
   * method to determine if the span can be created or not. An {@link AttributesExtractor} can
   * implement that method to associate itself (and the {@link Instrumenter} it is a part of) with a
   * particular convention.
   *
   * <p>For example, nested HTTP client spans will be suppressed; but an RPC client span will not
   * suppress an HTTP client span, if the instrumented RPC client uses HTTP as transport.
   */
  SEMCONV {
    @Override
    SpanSuppressor create(Set<SpanKey> spanKeys) {
      if (spanKeys.isEmpty()) {
        return Noop.INSTANCE;
      }
      return new BySpanKey(spanKeys);
    }
  };

  abstract SpanSuppressor create(Set<SpanKey> spanKeys);

  static SpanSuppressionStrategy fromConfig(@Nullable String value) {
    if (value == null) {
      value = "semconv";
    }
    switch (value.toLowerCase(Locale.ROOT)) {
      case "none":
        return NONE;
      case "span-kind":
        return SPAN_KIND;
      default:
        return SEMCONV;
    }
  }
}
