/*
 * Copyright (C) 2012 The Guava Authors
 *
 * Licensed 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 com.google.common.collect.testing.google;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.testing.Helpers.mapEntry;

import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.BiMap;
import com.google.common.collect.testing.DerivedGenerator;
import com.google.common.collect.testing.OneSizeTestContainerGenerator;
import com.google.common.collect.testing.SampleElements;
import com.google.common.collect.testing.TestMapGenerator;
import com.google.common.collect.testing.TestSetGenerator;
import com.google.common.collect.testing.TestSubjectGenerator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

/**
 * Derived suite generators for Guava collection interfaces, split out of the suite builders so that
 * they are available to GWT.
 *
 * @author Louis Wasserman
 */
@GwtCompatible
@NullMarked
public final class DerivedGoogleCollectionGenerators {
  public static class MapGenerator<K extends @Nullable Object, V extends @Nullable Object>
      implements TestMapGenerator<K, V>, DerivedGenerator {

    private final OneSizeTestContainerGenerator<BiMap<K, V>, Entry<K, V>> generator;

    public MapGenerator(
        OneSizeTestContainerGenerator<BiMap<K, V>, Entry<K, V>> oneSizeTestContainerGenerator) {
      this.generator = oneSizeTestContainerGenerator;
    }

    @Override
    public SampleElements<Entry<K, V>> samples() {
      return generator.samples();
    }

    @Override
    public Map<K, V> create(Object... elements) {
      return generator.create(elements);
    }

    @Override
    public Entry<K, V>[] createArray(int length) {
      return generator.createArray(length);
    }

    @Override
    public Iterable<Entry<K, V>> order(List<Entry<K, V>> insertionOrder) {
      return generator.order(insertionOrder);
    }

    @SuppressWarnings("unchecked")
    @Override
    public K[] createKeyArray(int length) {
      return (K[]) new Object[length];
    }

    @SuppressWarnings("unchecked")
    @Override
    public V[] createValueArray(int length) {
      return (V[]) new Object[length];
    }

    @Override
    public TestSubjectGenerator<?> getInnerGenerator() {
      return generator;
    }
  }

  public static class InverseBiMapGenerator<K extends @Nullable Object, V extends @Nullable Object>
      implements TestBiMapGenerator<V, K>, DerivedGenerator {

    private final OneSizeTestContainerGenerator<BiMap<K, V>, Entry<K, V>> generator;

    public InverseBiMapGenerator(
        OneSizeTestContainerGenerator<BiMap<K, V>, Entry<K, V>> oneSizeTestContainerGenerator) {
      this.generator = oneSizeTestContainerGenerator;
    }

    @Override
    public SampleElements<Entry<V, K>> samples() {
      SampleElements<Entry<K, V>> samples = generator.samples();
      return new SampleElements<>(
          reverse(samples.e0()),
          reverse(samples.e1()),
          reverse(samples.e2()),
          reverse(samples.e3()),
          reverse(samples.e4()));
    }

    private Entry<V, K> reverse(Entry<K, V> entry) {
      checkNotNull(entry);
      return mapEntry(entry.getValue(), entry.getKey());
    }

    @SuppressWarnings("unchecked")
    @Override
    public BiMap<V, K> create(Object... elements) {
      Entry<?, ?>[] entries = new Entry<?, ?>[elements.length];
      for (int i = 0; i < elements.length; i++) {
        entries[i] = reverse((Entry<K, V>) elements[i]);
      }
      return generator.create((Object[]) entries).inverse();
    }

    @SuppressWarnings("unchecked")
    @Override
    public Entry<V, K>[] createArray(int length) {
      return (Entry<V, K>[]) new Entry<?, ?>[length];
    }

    @Override
    public Iterable<Entry<V, K>> order(List<Entry<V, K>> insertionOrder) {
      return insertionOrder;
    }

    @SuppressWarnings("unchecked")
    @Override
    public V[] createKeyArray(int length) {
      return (V[]) new Object[length];
    }

    @SuppressWarnings("unchecked")
    @Override
    public K[] createValueArray(int length) {
      return (K[]) new Object[length];
    }

    @Override
    public TestSubjectGenerator<?> getInnerGenerator() {
      return generator;
    }
  }

  public static class BiMapValueSetGenerator<K extends @Nullable Object, V extends @Nullable Object>
      implements TestSetGenerator<V>, DerivedGenerator {
    private final OneSizeTestContainerGenerator<BiMap<K, V>, Entry<K, V>> mapGenerator;
    private final SampleElements<V> samples;

    public BiMapValueSetGenerator(
        OneSizeTestContainerGenerator<BiMap<K, V>, Entry<K, V>> mapGenerator) {
      this.mapGenerator = mapGenerator;
      SampleElements<Entry<K, V>> mapSamples = this.mapGenerator.samples();
      this.samples =
          new SampleElements<>(
              mapSamples.e0().getValue(),
              mapSamples.e1().getValue(),
              mapSamples.e2().getValue(),
              mapSamples.e3().getValue(),
              mapSamples.e4().getValue());
    }

    @Override
    public SampleElements<V> samples() {
      return samples;
    }

    @Override
    public Set<V> create(Object... elements) {
      @SuppressWarnings("unchecked")
      V[] valuesArray = (V[]) elements;

      // Start with a suitably shaped collection of entries
      Collection<Entry<K, V>> originalEntries = mapGenerator.getSampleElements(elements.length);

      // Create a copy of that, with the desired value for each value
      Collection<Entry<K, V>> entries = new ArrayList<>(elements.length);
      int i = 0;
      for (Entry<K, V> entry : originalEntries) {
        entries.add(mapEntry(entry.getKey(), valuesArray[i++]));
      }

      return mapGenerator.create(entries.toArray()).values();
    }

    @Override
    public V[] createArray(int length) {
      V[] vs =
          ((TestBiMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createValueArray(length);
      return vs;
    }

    @Override
    public Iterable<V> order(List<V> insertionOrder) {
      return insertionOrder;
    }

    @Override
    public TestSubjectGenerator<?> getInnerGenerator() {
      return mapGenerator;
    }
  }

  private DerivedGoogleCollectionGenerators() {}
}
