/*
 * Copyright DataStax, Inc.
 *
 * 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.datastax.oss.driver.api.core.type.reflect;

import static org.assertj.core.api.Assertions.assertThat;

import com.datastax.oss.driver.shaded.guava.common.reflect.TypeToken;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.Test;

public class GenericTypeTest {

  @Test
  public void should_wrap_class() {
    GenericType<String> stringType = GenericType.of(String.class);
    assertThat(stringType.__getToken()).isEqualTo(TypeToken.of(String.class));
  }

  @Test
  public void should_capture_generic_type() {
    GenericType<List<String>> stringListType = new GenericType<List<String>>() {};
    TypeToken<List<String>> stringListToken = new TypeToken<List<String>>() {};
    assertThat(stringListType.__getToken()).isEqualTo(stringListToken);
  }

  @Test
  public void should_wrap_classes_in_collection() {
    GenericType<Map<String, Integer>> mapType = GenericType.mapOf(String.class, Integer.class);
    assertThat(mapType.__getToken()).isEqualTo(new TypeToken<Map<String, Integer>>() {});
  }

  @Test
  public void should_wrap_types_in_collection() {
    GenericType<Map<String, List<Integer>>> mapType =
        GenericType.mapOf(GenericType.of(String.class), GenericType.listOf(Integer.class));
    assertThat(mapType.__getToken()).isEqualTo(new TypeToken<Map<String, List<Integer>>>() {});
  }

  @Test
  public void should_substitute_type_parameters() {
    assertThat(optionalOf(GenericType.listOf(String.class)).__getToken())
        .isEqualTo(new TypeToken<Optional<List<String>>>() {});
    assertThat(mapOf(String.class, Integer.class).__getToken())
        .isEqualTo(new TypeToken<Map<String, Integer>>() {});
  }

  @Test
  public void should_report_supertype() {
    assertThat(GenericType.of(Number.class).isSupertypeOf(GenericType.of(Integer.class))).isTrue();
    assertThat(GenericType.of(Integer.class).isSupertypeOf(GenericType.of(Number.class))).isFalse();
  }

  @Test
  public void should_report_subtype() {
    assertThat(GenericType.of(Number.class).isSubtypeOf(GenericType.of(Integer.class))).isFalse();
    assertThat(GenericType.of(Integer.class).isSubtypeOf(GenericType.of(Number.class))).isTrue();
  }

  @Test
  public void should_wrap_primitive_type() {
    assertThat(GenericType.of(Integer.TYPE).wrap()).isEqualTo(GenericType.of(Integer.class));
    GenericType<String> stringType = GenericType.of(String.class);
    assertThat(stringType.wrap()).isSameAs(stringType);
  }

  @Test
  public void should_unwrap_wrapper_type() {
    assertThat(GenericType.of(Integer.class).unwrap()).isEqualTo(GenericType.of(Integer.TYPE));
    GenericType<String> stringType = GenericType.of(String.class);
    assertThat(stringType.unwrap()).isSameAs(stringType);
  }

  @Test
  public void should_return_raw_type() {
    assertThat(GenericType.INTEGER.getRawType()).isEqualTo(Integer.class);
    assertThat(GenericType.listOf(Integer.class).getRawType()).isEqualTo(List.class);
  }

  @Test
  public void should_return_super_type() {
    GenericType<Iterable<Integer>> expectedType = iterableOf(GenericType.INTEGER);
    assertThat(GenericType.listOf(Integer.class).getSupertype(Iterable.class))
        .isEqualTo(expectedType);
  }

  @Test
  public void should_return_sub_type() {
    GenericType<Iterable<Integer>> superType = iterableOf(GenericType.INTEGER);
    assertThat(superType.getSubtype(List.class)).isEqualTo(GenericType.listOf(GenericType.INTEGER));
  }

  @Test
  public void should_return_type() {
    assertThat(GenericType.INTEGER.getType()).isEqualTo(Integer.class);
  }

  @Test
  public void should_return_component_type() {
    assertThat(GenericType.of(Integer[].class).getComponentType()).isEqualTo(GenericType.INTEGER);
  }

  @Test
  public void should_report_is_array() {
    assertThat(GenericType.INTEGER.isArray()).isFalse();
    assertThat(GenericType.of(Integer[].class).isArray()).isTrue();
  }

  private <T> GenericType<Optional<T>> optionalOf(GenericType<T> elementType) {
    return new GenericType<Optional<T>>() {}.where(new GenericTypeParameter<T>() {}, elementType);
  }

  private <T> GenericType<Iterable<T>> iterableOf(GenericType<T> elementType) {
    return new GenericType<Iterable<T>>() {}.where(new GenericTypeParameter<T>() {}, elementType);
  }

  private <K, V> GenericType<Map<K, V>> mapOf(Class<K> keyClass, Class<V> valueClass) {
    return new GenericType<Map<K, V>>() {}.where(new GenericTypeParameter<K>() {}, keyClass)
        .where(new GenericTypeParameter<V>() {}, valueClass);
  }
}
