/*
 * Copyright (C) 2010 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;

import com.google.common.collect.Synchronized.SynchronizedNavigableMap;
import com.google.common.collect.Synchronized.SynchronizedNavigableSet;
import com.google.common.collect.Synchronized.SynchronizedSortedMap;
import com.google.common.collect.testing.NavigableMapTestSuiteBuilder;
import com.google.common.collect.testing.SafeTreeMap;
import com.google.common.collect.testing.TestStringSortedMapGenerator;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;
import com.google.common.collect.testing.features.MapFeature;
import com.google.common.testing.SerializableTester;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.SortedMap;
import junit.framework.TestSuite;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
 * Tests for {@link Maps#synchronizedNavigableMap(NavigableMap)}.
 *
 * @author Louis Wasserman
 */
@NullUnmarked
public class SynchronizedNavigableMapTest extends SynchronizedMapTest {
  @Override
  protected <K, V> NavigableMap<K, V> create() {
    @SuppressWarnings("unchecked")
    NavigableMap<K, V> innermost =
        new SafeTreeMap<>((Comparator<? super K>) Ordering.natural().nullsFirst());
    TestMap<K, V> inner = new TestMap<>(innermost, mutex);
    NavigableMap<K, V> outer = Synchronized.navigableMap(inner, mutex);
    return outer;
  }

  static class TestEntry<K, V> extends ForwardingMapEntry<K, V> implements Serializable {
    private final Entry<K, V> delegate;
    private final Object mutex;

    TestEntry(Entry<K, V> delegate, Object mutex) {
      this.delegate = delegate;
      this.mutex = mutex;
    }

    @Override
    protected Entry<K, V> delegate() {
      return delegate;
    }

    @Override
    public boolean equals(@Nullable Object object) {
      assertTrue(Thread.holdsLock(mutex));
      return super.equals(object);
    }

    @Override
    public K getKey() {
      assertTrue(Thread.holdsLock(mutex));
      return super.getKey();
    }

    @Override
    public V getValue() {
      assertTrue(Thread.holdsLock(mutex));
      return super.getValue();
    }

    @Override
    public int hashCode() {
      assertTrue(Thread.holdsLock(mutex));
      return super.hashCode();
    }

    @Override
    public V setValue(V value) {
      assertTrue(Thread.holdsLock(mutex));
      return super.setValue(value);
    }

    private static final long serialVersionUID = 0;
  }

  static class TestMap<K, V> extends SynchronizedMapTest.TestMap<K, V>
      implements NavigableMap<K, V> {

    public TestMap(NavigableMap<K, V> delegate, Object mutex) {
      super(delegate, mutex);
    }

    @Override
    protected NavigableMap<K, V> delegate() {
      return (NavigableMap<K, V>) super.delegate();
    }

    @Override
    public @Nullable Entry<K, V> ceilingEntry(K key) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().ceilingEntry(key);
    }

    @Override
    public @Nullable K ceilingKey(K key) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().ceilingKey(key);
    }

    @Override
    public NavigableSet<K> descendingKeySet() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().descendingKeySet();
    }

    @Override
    public NavigableMap<K, V> descendingMap() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().descendingMap();
    }

    @Override
    public @Nullable Entry<K, V> firstEntry() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().firstEntry();
    }

    @Override
    public @Nullable Entry<K, V> floorEntry(K key) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().floorEntry(key);
    }

    @Override
    public @Nullable K floorKey(K key) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().floorKey(key);
    }

    @Override
    public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().headMap(toKey, inclusive);
    }

    @Override
    public SortedMap<K, V> headMap(K toKey) {
      return headMap(toKey, false);
    }

    @Override
    public @Nullable Entry<K, V> higherEntry(K key) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().higherEntry(key);
    }

    @Override
    public @Nullable K higherKey(K key) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().higherKey(key);
    }

    @Override
    public @Nullable Entry<K, V> lastEntry() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().lastEntry();
    }

    @Override
    public @Nullable Entry<K, V> lowerEntry(K key) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().lowerEntry(key);
    }

    @Override
    public @Nullable K lowerKey(K key) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().lowerKey(key);
    }

    @Override
    public NavigableSet<K> navigableKeySet() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().navigableKeySet();
    }

    @Override
    public @Nullable Entry<K, V> pollFirstEntry() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().pollFirstEntry();
    }

    @Override
    public @Nullable Entry<K, V> pollLastEntry() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().pollLastEntry();
    }

    @Override
    public NavigableMap<K, V> subMap(
        K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().subMap(fromKey, fromInclusive, toKey, toInclusive);
    }

    @Override
    public SortedMap<K, V> subMap(K fromKey, K toKey) {
      return delegate().subMap(fromKey, true, toKey, false);
    }

    @Override
    public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().tailMap(fromKey, inclusive);
    }

    @Override
    public SortedMap<K, V> tailMap(K fromKey) {
      return tailMap(fromKey, true);
    }

    @Override
    public Comparator<? super K> comparator() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().comparator();
    }

    @Override
    public K firstKey() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().firstKey();
    }

    @Override
    public K lastKey() {
      assertTrue(Thread.holdsLock(mutex));
      return delegate().lastKey();
    }

    private static final long serialVersionUID = 0;
  }

  @AndroidIncompatible // test-suite builders
  public static TestSuite suite() {
    TestSuite suite = new TestSuite();
    suite.addTestSuite(SynchronizedNavigableMapTest.class);
    suite.addTest(
        NavigableMapTestSuiteBuilder.using(
                new TestStringSortedMapGenerator() {
                  private final Object mutex = new Object[0]; // something Serializable

                  @Override
                  protected SortedMap<String, String> create(Entry<String, String>[] entries) {
                    NavigableMap<String, String> innermost = new SafeTreeMap<>();
                    for (Entry<String, String> entry : entries) {
                      innermost.put(entry.getKey(), entry.getValue());
                    }
                    TestMap<String, String> inner = new TestMap<>(innermost, mutex);
                    NavigableMap<String, String> outer = Synchronized.navigableMap(inner, mutex);
                    return outer;
                  }
                })
            .named("Maps.synchronizedNavigableMap[SafeTreeMap]")
            .withFeatures(
                CollectionSize.ANY,
                CollectionFeature.KNOWN_ORDER,
                MapFeature.GENERAL_PURPOSE,
                MapFeature.ALLOWS_NULL_VALUES,
                CollectionFeature.SUPPORTS_ITERATOR_REMOVE)
            .createTestSuite());

    return suite;
  }

  public void testComparator() {
    assertNotNull(create().comparator());
  }

  public void testCeilingEntry() {
    assertNull(create().ceilingEntry("a"));
  }

  public void testCeilingKey() {
    assertNull(create().ceilingKey("a"));
  }

  public void testDescendingKeySet() {
    NavigableMap<String, Integer> map = create();
    NavigableSet<String> descendingKeySet = map.descendingKeySet();
    assertTrue(descendingKeySet instanceof SynchronizedNavigableSet);
    assertSame(mutex, ((SynchronizedNavigableSet<String>) descendingKeySet).mutex);
  }

  public void testDescendingMap() {
    NavigableMap<String, Integer> map = create();
    NavigableMap<String, Integer> descendingMap = map.descendingMap();
    assertTrue(descendingMap instanceof SynchronizedNavigableMap);
    assertSame(mutex, ((SynchronizedNavigableMap<String, Integer>) descendingMap).mutex);
  }

  public void testFirstEntry() {
    assertNull(create().firstEntry());
  }

  public void testFirstKey() {
    NavigableMap<String, Integer> map = create();
    map.put("a", 1);
    assertNotNull(map.firstKey());
  }

  public void testFloorEntry() {
    assertNull(create().floorEntry("a"));
  }

  public void testFloorKey() {
    assertNull(create().floorKey("a"));
  }

  public void testHeadMap_k() {
    NavigableMap<String, Integer> map = create();
    SortedMap<String, Integer> headMap = map.headMap("a");
    assertTrue(headMap instanceof SynchronizedSortedMap);
    assertSame(mutex, ((SynchronizedSortedMap<String, Integer>) headMap).mutex);
  }

  public void testHeadMap_k_b() {
    NavigableMap<String, Integer> map = create();
    NavigableMap<String, Integer> headMap = map.headMap("a", true);
    assertTrue(headMap instanceof SynchronizedNavigableMap);
    assertSame(mutex, ((SynchronizedNavigableMap<String, Integer>) headMap).mutex);
  }

  public void testHigherEntry() {
    assertNull(create().higherEntry("a"));
  }

  public void testHigherKey() {
    assertNull(create().higherKey("a"));
  }

  public void testLastEntry() {
    assertNull(create().lastEntry());
  }

  public void testLastKey() {
    NavigableMap<String, Integer> map = create();
    map.put("a", 1);
    assertNotNull(map.lastKey());
  }

  public void testLowerEntry() {
    assertNull(create().lowerEntry("a"));
  }

  public void testLowerKey() {
    assertNull(create().lowerKey("a"));
  }

  public void testNavigableKeySet() {
    NavigableMap<String, Integer> map = create();
    NavigableSet<String> navigableKeySet = map.navigableKeySet();
    assertTrue(navigableKeySet instanceof SynchronizedNavigableSet);
    assertSame(mutex, ((SynchronizedNavigableSet<String>) navigableKeySet).mutex);
  }

  public void testPollFirstEntry() {
    create().pollFirstEntry();
  }

  public void testPollLastEntry() {
    create().pollLastEntry();
  }

  public void testSubMap_k_k() {
    NavigableMap<String, Integer> map = create();
    SortedMap<String, Integer> subMap = map.subMap("a", "b");
    assertTrue(subMap instanceof SynchronizedSortedMap);
    assertSame(mutex, ((SynchronizedSortedMap<String, Integer>) subMap).mutex);
  }

  public void testSubMap_k_b_k_b() {
    NavigableMap<String, Integer> map = create();
    NavigableMap<String, Integer> subMap = map.subMap("a", true, "b", false);
    assertTrue(subMap instanceof SynchronizedNavigableMap);
    assertSame(mutex, ((SynchronizedNavigableMap<String, Integer>) subMap).mutex);
  }

  public void testTailMap_k() {
    NavigableMap<String, Integer> map = create();
    SortedMap<String, Integer> subMap = map.tailMap("a");
    assertTrue(subMap instanceof SynchronizedSortedMap);
    assertSame(mutex, ((SynchronizedSortedMap<String, Integer>) subMap).mutex);
  }

  public void testTailMap_k_b() {
    NavigableMap<String, Integer> map = create();
    NavigableMap<String, Integer> subMap = map.tailMap("a", true);
    assertTrue(subMap instanceof SynchronizedNavigableMap);
    assertSame(mutex, ((SynchronizedNavigableMap<String, Integer>) subMap).mutex);
  }

  @Override
  public void testSerialization() {
    SerializableTester.reserializeAndAssert(create());
  }
}
