package br.com.caelum.vraptor.core;

import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;

public class ReflectionProviderTest {

	public static class Dog {
		private String name;
		private boolean running;

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public boolean isRunning() {
			return running;
		}

		public void setRunning(boolean running) {
			this.running = running;
		}
	}

	public static class DomesticDog extends Dog {
		private String breed;

		public void setBreed(String breed) {
			this.breed = breed;
		}

		public String getBreed() {
			return breed;
		}
	}

	protected ReflectionProvider reflectionProvider;

	@Before
	public void setup() {
		reflectionProvider = new DefaultReflectionProvider();
	}

	@Test
	public void shouldListAllMethodsMethodsFromClass() {
		List<Method> methods = reflectionProvider.getMethodsFor(Dog.class);
		List<String> names = FluentIterable.from(methods).filter(notFromObject()).transform(extractName()).toList();

		assertThat(names, containsInAnyOrder("getName", "isRunning", "setName", "setRunning"));
	}

	@Test
	public void shouldListAllMethodsMethodsFromInheritedClassAndSuperclass() {
		List<Method> methods = reflectionProvider.getMethodsFor(DomesticDog.class);
		List<String> names = FluentIterable.from(methods).filter(notFromObject()).transform(extractName()).toList();

		assertThat(names, containsInAnyOrder("getBreed", "getName", "isRunning", "setBreed", "setName", "setRunning"));
	}

	@Test
	public void shouldReturnMethodFromClass() {
		Method getName = reflectionProvider.getMethod(Dog.class, "getName");
		assertThat(getName.getName(), equalTo("getName"));
		assertThat(getName.getReturnType().getName(), equalTo(String.class.getName()));
		assertThat(getName.getParameterTypes().length, equalTo(0));

		Method setName = reflectionProvider.getMethod(Dog.class, "setName", String.class);
		assertThat(setName.getName(), equalTo("setName"));
		assertThat(setName.getReturnType().getName(), equalTo(void.class.getName()));
		assertThat(setName.getParameterTypes().length, equalTo(1));
	}

	@Test
	public void shouldGetMethodFromInheritedClass() {
		Method getName = reflectionProvider.getMethod(DomesticDog.class, "getName");
		assertThat(getName.getName(), equalTo("getName"));
		assertThat(getName.getReturnType().getName(), equalTo(String.class.getName()));
		assertThat(getName.getParameterTypes().length, equalTo(0));

		Method setBreed = reflectionProvider.getMethod(DomesticDog.class, "setBreed", String.class);
		assertThat(setBreed.getName(), equalTo("setBreed"));
		assertThat(setBreed.getReturnType().getName(), equalTo(void.class.getName()));
		assertThat(setBreed.getParameterTypes().length, equalTo(1));
	}

	@Test
	public void shouldReturnNullWhenMethodNotFound() {
		Method notFound = reflectionProvider.getMethod(Dog.class, "notFound");
		assertThat(notFound, nullValue());
	}

	@Test
	public void shouldInvokeMethodByInstanceFromClass() {
		Dog dog = new Dog();

		Method setName = reflectionProvider.getMethod(Dog.class, "setName", String.class);
		reflectionProvider.invoke(dog, setName, "Otto");
		assertThat(dog.getName(), equalTo("Otto"));
	}

	@Test
	public void shouldInvokeMethodByInstanceFromInheritedClass() {
		DomesticDog dog = new DomesticDog();

		Method setName = reflectionProvider.getMethod(Dog.class, "setName", String.class);
		reflectionProvider.invoke(dog, setName, "Otto");
		assertThat(dog.getName(), equalTo("Otto"));

		Method setBreed = reflectionProvider.getMethod(DomesticDog.class, "setBreed", String.class);
		reflectionProvider.invoke(dog, setBreed, "Schnauzer");
		assertThat(dog.getBreed(), equalTo("Schnauzer"));
	}

	@Test
	public void shouldInvokeMethodByNameFromClass() {
		Dog dog = new Dog();

		reflectionProvider.invoke(dog, "setName", "Otto");
		assertThat(dog.getName(), equalTo("Otto"));
	}

	@Test
	public void shouldInvokeMethodByNameFromInheritedClass() {
		DomesticDog dog = new DomesticDog();

		reflectionProvider.invoke(dog, "setName", "Otto");
		assertThat(dog.getName(), equalTo("Otto"));

		reflectionProvider.invoke(dog, "setBreed", "Schnauzer");
		assertThat(dog.getBreed(), equalTo("Schnauzer"));
	}

	@Test
	public void shouldInvokeGetterFromClass() {
		Dog dog = new Dog();
		dog.setName("Otto");
		dog.setRunning(true);

		String dogName = (String) reflectionProvider.invokeGetter(dog, "name");
		assertThat(dogName, equalTo("Otto"));

		boolean isRunning = (boolean) reflectionProvider.invokeGetter(dog, "running");
		assertThat(isRunning, equalTo(true));
	}

	@Test
	public void shouldInvokeGetterFromInheritedClass() {
		DomesticDog dog = new DomesticDog();
		dog.setName("Otto");
		dog.setBreed("Schnauzer");

		String dogName = (String) reflectionProvider.invokeGetter(dog, "name");
		assertThat(dogName, equalTo("Otto"));

		String dogBreed = (String) reflectionProvider.invokeGetter(dog, "breed");
		assertThat(dogBreed, equalTo("Schnauzer"));
	}

	@Test
	public void shouldGetFieldsFromClass() {
		List<Field> fields = reflectionProvider.getFieldsFor(Dog.class);
		List<String> names = FluentIterable.from(fields).filter(notFromObject()).transform(extractName()).toList();

		assertThat(names, contains("name", "running"));
	}

	@Test
	public void shouldGetFieldsFromInheritedClass() {
		List<Field> fields = reflectionProvider.getFieldsFor(DomesticDog.class);
		List<String> names = FluentIterable.from(fields).filter(notFromObject()).transform(extractName()).toList();

		assertThat(names, contains("breed", "name", "running"));
	}

	@Test
	public void shouldGetFieldByNameFromClass() {
		Field nameField = reflectionProvider.getField(Dog.class, "name");
		assertThat(nameField.getName(), equalTo("name"));
		assertThat(nameField.getType().getName(), equalTo(String.class.getName()));

		Field runningField = reflectionProvider.getField(Dog.class, "running");
		assertThat(runningField.getName(), equalTo("running"));
		assertThat(runningField.getType().getName(), equalTo(boolean.class.getName()));
	}

	@Test
	public void shouldGetFieldByNameFromInheritedClass() {
		Field nameField = reflectionProvider.getField(DomesticDog.class, "name");
		assertThat(nameField.getName(), equalTo("name"));
		assertThat(nameField.getType().getName(), equalTo(String.class.getName()));

		Field breedField = reflectionProvider.getField(DomesticDog.class, "breed");
		assertThat(breedField.getName(), equalTo("breed"));
		assertThat(breedField.getType().getName(), equalTo(String.class.getName()));
	}

	@Test
	public void shouldReturnNullWhenFieldNotFound() {
		Field notFound = reflectionProvider.getField(Dog.class, "notFound");
		assertThat(notFound, nullValue());
	}

	private Function<Member, String> extractName() {
		return new Function<Member, String>() {
			@Override
			public String apply(Member input) {
				return input.getName();
			}
		};
	}

	private Predicate<Member> notFromObject() {
		return new Predicate<Member>() {
			@Override
			public boolean apply(Member input) {
				return !input.getDeclaringClass().equals(Object.class);
			}
		};
	}
}