package io.activej.types;

import org.junit.BeforeClass;
import org.junit.Test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;

import static io.activej.types.Types.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;

public class TypeTTest {

	private static boolean atLeastJava9;

	@BeforeClass
	public static void beforeClass() {
		atLeastJava9 = !System.getProperty("java.specification.version").contains(".");
	}

	@Test
	public void regularClass() {
		TypeT<Integer> typeT = new TypeT<@A("Integer") Integer>() {};
		assertTypeT(Integer.class, "Integer", Integer.class, typeT);
	}

	@Test
	public void parameterized() {
		TypeT<List<Integer>> typeT = new TypeT<@A("List") List<Integer>>() {};
		assertTypeT(parameterizedType(List.class, Integer.class), "List", List.class, typeT);

		TypeT<List<? extends Integer>> typeTWildcard = new TypeT<@A("List extends") List<? extends Integer>>() {};
		assertTypeT(parameterizedType(List.class, wildcardTypeExtends(Integer.class)), "List extends", List.class, typeTWildcard);
	}

	@Test
	public void genericArray() {
		TypeT<List<Integer>[]> typeT = new TypeT<List<Integer> @A("Array of lists") []>() {};
		assertTypeT(genericArrayType(parameterizedType(List.class, Integer.class)), "Array of lists", List[].class, typeT);

		TypeT<List<? extends Integer>[]> typeTWildcard = new TypeT<List<? extends Integer> @A("Array of lists extends") []>() {};
		assertTypeT(genericArrayType(parameterizedType(List.class, wildcardTypeExtends(Integer.class))), "Array of lists extends", List[].class, typeTWildcard);
	}

	@Test
	public void typeVariable() {
		TypeTHolder<?> holder = new TypeTHolder<>();
		TypeT<?> typeT = holder.typeT;

		assertThat(typeT.getType(), instanceOf(TypeVariable.class));
		assertAnnotation("Variable", typeT.getAnnotatedType());
		assertEquals(Integer.class, typeT.getRawType());
//		assertEquals(typeT.getAnnotatedType(), TypeT.ofAnnotatedType(typeT.getAnnotatedType()).getAnnotatedType());
	}

	private static <T> void assertTypeT(Type type, String annotationValue, Class<?> rawType, TypeT<T> typeT) {
		assertEquals(type, typeT.getType());
		assertAnnotation(annotationValue, typeT.getAnnotatedType());
		assertEquals(rawType, typeT.getRawType());
//		assertEquals(typeT.getAnnotatedType(), TypeT.ofAnnotatedType(typeT.getAnnotatedType()).getAnnotatedType());
	}

	private static void assertAnnotation(String annotationValue, AnnotatedType annotatedType) {
		if (atLeastJava9) {
			assertEquals(annotationValue, annotatedType.getAnnotation(A.class).value());
		} else {
			assertEquals(0, annotatedType.getAnnotations().length);
		}
	}

	private static class TypeTHolder<T extends Integer> {
		private final TypeT<T> typeT = new TypeT<@A("Variable") T>() {};
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE_USE)
	public @interface A {
		String value() default "";
	}
}
