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

package io.opentelemetry.api.baggage;

import static java.util.Objects.requireNonNull;

import com.google.auto.value.AutoValue;
import io.opentelemetry.api.internal.ImmutableKeyValuePairs;
import io.opentelemetry.api.internal.StringUtils;
import io.opentelemetry.context.Context;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

@Immutable
abstract class ImmutableBaggage extends ImmutableKeyValuePairs<String, BaggageEntry>
    implements Baggage {

  private static final Baggage EMPTY = new ImmutableBaggage.Builder().build();

  static Baggage empty() {
    return EMPTY;
  }

  static BaggageBuilder builder() {
    return new Builder();
  }

  @AutoValue
  @Immutable
  abstract static class ArrayBackedBaggage extends ImmutableBaggage {
    ArrayBackedBaggage() {}

    @Override
    protected abstract List<Object> data();

    @Override
    public BaggageBuilder toBuilder() {
      return new ImmutableBaggage.Builder(new ArrayList<>(data()));
    }
  }

  @Override
  public void forEach(BaggageConsumer consumer) {
    for (int i = 0; i < data().size(); i += 2) {
      Entry entry = (Entry) data().get(i + 1);
      consumer.accept((String) data().get(i), entry.getValue(), entry.getEntryMetadata());
    }
  }

  @Nullable
  @Override
  public String getEntryValue(String entryKey) {
    BaggageEntry entry = get(entryKey);
    return entry != null ? entry.getValue() : null;
  }

  @Override
  public BaggageBuilder toBuilder() {
    Builder builder = new Builder(data());
    builder.noImplicitParent = true;
    return builder;
  }

  private static Baggage sortAndFilterToBaggage(Object[] data) {
    return new AutoValue_ImmutableBaggage_ArrayBackedBaggage(
        sortAndFilter(data, /* filterNullValues= */ true));
  }

  // TODO: Migrate to AutoValue.Builder
  // @AutoValue.Builder
  static class Builder implements BaggageBuilder {

    @Nullable private Baggage parent;
    private boolean noImplicitParent;
    private final List<Object> data;

    Builder() {
      this.data = new ArrayList<>();
    }

    Builder(List<Object> data) {
      this.data = new ArrayList<>(data);
    }

    @Override
    public BaggageBuilder setParent(Context context) {
      requireNonNull(context, "context");
      parent = Baggage.fromContext(context);
      return this;
    }

    @Override
    public BaggageBuilder setNoParent() {
      this.parent = null;
      noImplicitParent = true;
      return this;
    }

    @Override
    public BaggageBuilder put(String key, String value, BaggageEntryMetadata entryMetadata) {
      if (!isKeyValid(key) || !isValueValid(value) || entryMetadata == null) {
        return this;
      }
      data.add(key);
      data.add(Entry.create(value, entryMetadata));

      return this;
    }

    @Override
    public BaggageBuilder remove(String key) {
      if (key == null) {
        return this;
      }
      data.add(key);
      data.add(null);
      return this;
    }

    @Override
    public Baggage build() {
      if (parent == null && !noImplicitParent) {
        parent = Baggage.current();
      }

      List<Object> data = this.data;
      if (parent != null && !parent.isEmpty()) {
        List<Object> merged = new ArrayList<>(parent.size() * 2 + data.size());
        parent.forEach(
            (key, value, metadata) -> {
              merged.add(key);
              merged.add(Entry.create(value, metadata));
            });
        merged.addAll(data);
        data = merged;
      }
      return sortAndFilterToBaggage(data.toArray());
    }
  }

  /**
   * Determines whether the given {@code String} is a valid entry key.
   *
   * @param name the entry key name to be validated.
   * @return whether the name is valid.
   */
  private static boolean isKeyValid(String name) {
    return name != null && !name.isEmpty() && StringUtils.isPrintableString(name);
  }

  /**
   * Determines whether the given {@code String} is a valid entry value.
   *
   * @param value the entry value to be validated.
   * @return whether the value is valid.
   */
  private static boolean isValueValid(String value) {
    return value != null && StringUtils.isPrintableString(value);
  }
}

@@ -491,16 +491,27 @@
 table;%0A%0A
+@AutoValue%0A
 @Immutab
@@ -513,16 +513,16 @@
 mutable%0A
-
 abstract
@@ -825,135 +825,8 @@
  %7D%0A%0A
-  @AutoValue%0A  @Immutable%0A  abstract static class ArrayBackedBaggage extends ImmutableBaggage %7B%0A    ArrayBackedBaggage() %7B%7D%0A%0A  
   @O
@@ -833,18 +833,16 @@
 verride%0A
-  
   protec
@@ -880,141 +880,8 @@
 );%0A%0A
-    @Override%0A    public BaggageBuilder toBuilder() %7B%0A      return new ImmutableBaggage.Builder(new ArrayList%3C%3E(data()));%0A    %7D%0A  %7D%0A%0A
   @O
@@ -1390,23 +1390,40 @@
 Builder(
+new ArrayList%3C%3E(
 data())
+)
 ;%0A    bu
@@ -1588,37 +1588,9 @@
 gage
-_ArrayBackedBaggage(%0A        
+(
 sort

Remove unnecessary class (#2648)