/***
 * Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
 * All rights reserved.
 *
 * 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 br.com.caelum.vraptor.validator;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Arrays;
import java.util.Locale;
import java.util.ResourceBundle;

import javax.validation.MessageInterpolator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotNull;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import br.com.caelum.vraptor.Controller;
import br.com.caelum.vraptor.Result;
import br.com.caelum.vraptor.core.DefaultReflectionProvider;
import br.com.caelum.vraptor.proxy.JavassistProxifier;
import br.com.caelum.vraptor.proxy.Proxifier;
import br.com.caelum.vraptor.util.SafeResourceBundle;
import br.com.caelum.vraptor.util.test.MockResult;
import br.com.caelum.vraptor.view.DefaultValidationViewsFactory;
import br.com.caelum.vraptor.view.LogicResult;
import br.com.caelum.vraptor.view.PageResult;

@RunWith(MockitoJUnitRunner.class)
public class DefaultValidatorTest {

	private static final Message A_MESSAGE = new SimpleMessage("", "");
	private @Mock Result result = new MockResult();
	private @Mock LogicResult logicResult;
	private @Mock PageResult pageResult;
	private @Mock Outjector outjector;

	private @Mock MyComponent instance;
	private DefaultValidator validator;

	@Before
	public void setup() {
		ResourceBundle bundle = new SafeResourceBundle(ResourceBundle.getBundle("messages"));

		ValidatorFactory validatorFactory = javax.validation.Validation.buildDefaultValidatorFactory();
		javax.validation.Validator bvalidator = validatorFactory.getValidator();
		MessageInterpolator interpolator = validatorFactory.getMessageInterpolator();

		Proxifier proxifier = new JavassistProxifier();
		Messages messages = new Messages();

		validator = new DefaultValidator(result, new DefaultValidationViewsFactory(result, proxifier, new DefaultReflectionProvider()), 
				outjector, proxifier, bundle, bvalidator, interpolator, Locale.ENGLISH, messages);
		when(result.use(LogicResult.class)).thenReturn(logicResult);
		when(result.use(PageResult.class)).thenReturn(pageResult);
		when(logicResult.forwardTo(MyComponent.class)).thenReturn(instance);
		when(pageResult.of(MyComponent.class)).thenReturn(instance);
	}

	@Test
	public void outjectsTheRequestParameters() {
		try {
			validator.add(A_MESSAGE);
			validator.onErrorForwardTo(MyComponent.class).logic();
		} catch (ValidationException e) {
		}
		verify(outjector).outjectRequestMap();
	}

	@Test
	public void addsTheErrorsOnTheResult() {
		try {
			validator.add(A_MESSAGE);
			validator.onErrorForwardTo(MyComponent.class).logic();
		} catch (ValidationException e) {
		}
		verify(result).include(eq("errors"), argThat(is(not(empty()))));
	}

	@Test
	public void forwardToCustomOnErrorPage() {
		try {
			when(logicResult.forwardTo(MyComponent.class)).thenReturn(instance);
			validator.add(A_MESSAGE);
			validator.onErrorForwardTo(MyComponent.class).logic();
			fail("should stop flow");
		} catch (ValidationException e) {
			verify(instance).logic();
		}
	}

	@Test
	public void shouldNotRedirectIfHasNotErrors() {
		try {
			validator.onErrorRedirectTo(MyComponent.class).logic();
			assertThat(validator.getErrors(), hasSize(0));
			verify(outjector, never()).outjectRequestMap();
		} catch (ValidationException e) {
			fail("no error occurs");
		}
	}

	@Test
	public void testThatValidatorGoToRedirectsToTheErrorPageImmediatellyAndNotBeforeThis() {
		try {
			// call all other validation methods and don't expect them to redirect
			validator.addAll(Arrays.asList(new SimpleMessage("test", "test")));

			when(pageResult.of(MyComponent.class)).thenReturn(instance);

			validator.onErrorUsePageOf(MyComponent.class).logic();
			fail("should stop flow");
		} catch (ValidationException e) {
			verify(instance).logic();
		}
	}

	@Test
	public void shouldParametrizeMessage() {
		Message message0 = new SimpleMessage("category", "The simple message");
		Message message1 = new SimpleMessage("category", "The {0} message", "simple");

		assertThat(message0.getMessage(), is("The simple message"));
		assertThat(message1.getMessage(), is("The simple message"));
	}
	
	@Test
	public void shouldntThrowExceptionWithEmptyMessageParameter() {
		String weirdMessage = "{weird message with braces}";
		Object[] emptyParams = new Object[]{};
		SimpleMessage message = new SimpleMessage("category", weirdMessage, emptyParams);
		assertThat(message.getMessage(), is(weirdMessage));
	}

	@Test
	public void doNothingIfCheckingSuccess() {
		Client c = new Client();
		c.name = "The name";

		validator.check(c.name != null, new SimpleMessage("client.name", "not null"));
		assertThat(validator.getErrors(), hasSize(0));
	}
	
	@Test
	public void shouldAddMessageIfCheckingFails() {
		Client c = new Client();

		validator.check(c.name != null, new SimpleMessage("client.name", "not null"));
		assertThat(validator.getErrors(), hasSize(1));
		assertThat(validator.getErrors().get(0).getMessage(), containsString("not null"));
	}
	
	@Test
	public void doNothingIfEnsuringSuccess() {
		Client c = new Client();
		c.name = "The name";

		validator.ensure(c.name != null, new SimpleMessage("client.name", "not null"));
		assertThat(validator.getErrors(), hasSize(0));
	}
	
	@Test
	public void shouldAddMessageIfEnsuringFails() {
		Client c = new Client();

		validator.ensure(c.name != null, new SimpleMessage("client.name", "not null"));
		assertThat(validator.getErrors(), hasSize(1));
		assertThat(validator.getErrors().get(0).getMessage(), containsString("not null"));
	}
	
	@Test
	public void doNothingIfFalse() {
		Client c = new Client();
		c.name = "The name";

		validator.addIf(c.name == null, new SimpleMessage("client.name", "not null"));
		assertThat(validator.getErrors(), hasSize(0));
	}
	
	@Test
	public void shouldAddMessageIfTrue() {
		Client c = new Client();

		validator.addIf(c.name == null, new SimpleMessage("client.name", "not null"));
		assertThat(validator.getErrors(), hasSize(1));
		assertThat(validator.getErrors().get(0).getMessage(), containsString("not null"));
	}

	@Test
	public void shouldReturnEmptyCollectionIfBeanIsNull() {
		validator.validate(null);
		assertThat(validator.getErrors(), hasSize(0));
	}

	@Test
	public void shouldReturnEmptyCollectionForValidBean() {
		validator.validate(new Customer(1, "Otto, the dog"));
		assertThat(validator.getErrors(), hasSize(0));
	}

	@Test
	public void shouldReturnElementsForInvalidBean() {
		validator.validate(new Customer(null, null));
		assertThat(validator.getErrors(), hasSize(2));
	}

	@Test
	public void shouldPrependAliasIfNotNull() {
		validator.validate("customer", new Customer(null, null));

		for (Message msg : validator.getErrors()) {
			assertThat(msg.getCategory(), startsWith("customer."));
		}
	}

	@Controller
	public static interface MyComponent {
		public void logic();
	}

	static class Client {
		public String name;
		public int age;
	}

	public static class Customer {

		@NotNull public Integer id;
		@NotNull public String name;

		public Customer(Integer id, String name) {
			this.id = id;
			this.name = name;
		}
	}
}