/*
 * Copyright The OpenZipkin Authors
 * SPDX-License-Identifier: Apache-2.0
 */
package brave.baggage;

import brave.internal.Nullable;
import brave.internal.baggage.BaggageContext;
import brave.propagation.TraceContext;
import brave.propagation.TraceContextOrSamplingFlags;

/**
 * This contains pre-defined fields, such as {@link #TRACE_ID} and a way to create a {@linkplain
 * #constant(String, String) constant field}.
 *
 * <h3>Built-in fields</h3>
 * The following are fields that dispatch to methods on the {@link TraceContext}. They are available
 * regardless of {@link BaggagePropagation}. None will return in lookups such as {@link
 * BaggageField#getAllValues(TraceContext)} or {@link BaggageField#getByName(TraceContext, String)}.
 *
 * <p><ol>
 * <li>{@link #TRACE_ID}</li>
 * <li>{@link #PARENT_ID}</li>
 * <li>{@link #SPAN_ID}</li>
 * <li>{@link #SAMPLED}</li>
 * </ol>
 *
 * @since 5.11
 */
public final class BaggageFields {
  /**
   * This is the most common log correlation field.
   *
   * @see TraceContext#traceIdString()
   * @since 5.11
   */
  public static final BaggageField TRACE_ID = new BaggageField("traceId", new TraceId());

  static final class TraceId extends BaggageContext.ReadOnly {
    @Override public String getValue(BaggageField field, TraceContextOrSamplingFlags extracted) {
      if (extracted.context() != null) return getValue(field, extracted.context());
      if (extracted.traceIdContext() != null) return extracted.traceIdContext().traceIdString();
      return null;
    }

    @Override public String getValue(BaggageField field, TraceContext context) {
      return context.traceIdString();
    }
  }

  /**
   * Typically only useful when spans are parsed from log records.
   *
   * @see TraceContext#parentIdString()
   * @since 5.11
   */
  public static final BaggageField PARENT_ID = new BaggageField("parentId", new ParentId());

  static final class ParentId extends BaggageContext.ReadOnly {
    @Override public String getValue(BaggageField field, TraceContextOrSamplingFlags extracted) {
      if (extracted.context() != null) return getValue(field, extracted.context());
      return null;
    }

    @Override public String getValue(BaggageField field, TraceContext context) {
      return context.parentIdString();
    }
  }

  /**
   * Used with {@link #TRACE_ID} to correlate a log line with a span.
   *
   * @see TraceContext#spanIdString()
   * @since 5.11
   */
  public static final BaggageField SPAN_ID = new BaggageField("spanId", new SpanId());

  static final class SpanId extends BaggageContext.ReadOnly {
    @Override public String getValue(BaggageField field, TraceContextOrSamplingFlags extracted) {
      if (extracted.context() != null) return getValue(field, extracted.context());
      return null;
    }

    @Override public String getValue(BaggageField field, TraceContext context) {
      return context.spanIdString();
    }
  }

  /**
   * This is only useful when {@link #TRACE_ID} is also a baggage field. It is a hint that a trace
   * may exist in Zipkin, when a user is viewing logs. For example, unsampled traces are not
   * typically reported to Zipkin.
   *
   * @see TraceContext#sampled()
   * @since 5.11
   */
  public static final BaggageField SAMPLED = new BaggageField("sampled", new Sampled());

  static final class Sampled extends BaggageContext.ReadOnly {
    @Override public String getValue(BaggageField field, TraceContextOrSamplingFlags extracted) {
      return getValue(extracted.sampled());
    }

    @Override public String getValue(BaggageField field, TraceContext context) {
      return getValue(context.sampled());
    }

    @Nullable static String getValue(@Nullable Boolean sampled) {
      return sampled != null ? sampled.toString() : null;
    }
  }

  /**
   * Creates a local baggage field based on a possibly null constant, such as an ENV variable.
   *
   * <p>Ex.
   * <pre>{@code
   * CLOUD_REGION = BaggageFields.constant("region", System.getEnv("CLOUD_REGION"));
   * }</pre>
   *
   * @since 5.11
   */
  public static BaggageField constant(String name, @Nullable String value) {
    return new BaggageField(name, new Constant(value));
  }

  static final class Constant extends BaggageContext.ReadOnly {
    @Nullable final String value;

    Constant(String value) {
      this.value = value;
    }

    @Override public String getValue(BaggageField field, TraceContextOrSamplingFlags extracted) {
      return value;
    }

    @Override public String getValue(BaggageField field, TraceContext context) {
      return value;
    }
  }
}
