/***
 * 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.http.route;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Collections;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import br.com.caelum.vraptor.TwoWayConverter;
import br.com.caelum.vraptor.core.Converters;
import br.com.caelum.vraptor.http.MutableRequest;

import com.google.common.collect.ImmutableMap;

public class DefaultParametersControlTest {

	private @Mock MutableRequest request;
	private @Mock Converters converters;
	private @Mock TwoWayConverter converter;
	private final Evaluator evaluator = new JavaEvaluator();

	@Before
	public void setup() {
		MockitoAnnotations.initMocks(this);
	}

	@Test
	public void registerExtraParametersFromAcessedUrl() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{dog.id}", converters, evaluator);
		control.fillIntoRequest("/clients/45", request);
		verify(request).setParameter("dog.id", new String[] {"45"});
	}

	@Test
	public void registerParametersWithAsterisks() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{my.path*}", converters, evaluator);
		control.fillIntoRequest("/clients/one/path", request);
		verify(request).setParameter("my.path", new String[] {"one/path"});
	}

	@Test
	public void registerParametersWithRegexes() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{hexa:[0-9A-Z]+}", converters, evaluator);

		control.fillIntoRequest("/clients/FAF323", request);

		verify(request).setParameter("hexa", new String[] {"FAF323"});
	}

	@Test
	public void registerParametersWithMultipleRegexes() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/test/{hash1:[a-z0-9]{16}}{id}{hash2:[a-z0-9]{16}}/", Collections.singletonMap("id", "\\d+"), converters, evaluator);

		control.fillIntoRequest("/test/0123456789abcdef1234fedcba9876543210/", request);

		verify(request).setParameter("hash1", new String[] {"0123456789abcdef"});
		verify(request).setParameter("id", new String[] {"1234"});
		verify(request).setParameter("hash2", new String[] {"fedcba9876543210"});
	}

	@Test
	public void worksAsRegexWhenUsingParameters() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{dog.id}", converters, evaluator);
		assertThat(control.matches("/clients/15"), is(equalTo(true)));
	}

	@Test
	public void worksWithBasicRegexEvaluation() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/clients.*", converters, evaluator);
		assertThat(control.matches("/clientsWhatever"), is(equalTo(true)));
	}

	class Client {
		private final Long id;
		private Client child;

		public Client(Long id) {
			this.id = id;
		}

		public Client getChild() {
			return child;
		}

		public Long getId() {
			return id;
		}
	}

	@Test
	public void shouldTranslateAsteriskAsEmpty() {
		String uri = new DefaultParametersControl("/clients/.*", converters, evaluator).fillUri(new String[] {"client"}, client(3L));
		assertThat(uri, is(equalTo("/clients/")));
	}

	@Test
	public void shouldTranslatePatternArgs() {
	String uri = new DefaultParametersControl("/clients/{client.id}", converters, evaluator).fillUri(new String[] {"client"}, client(3L));
	assertThat(uri, is(equalTo("/clients/3")));
	}

	@Test
	public void shouldTranslatePatternArgsWithRegex() {
	String uri = new DefaultParametersControl("/clients/{id:[0-9]{1,}}", converters, evaluator).fillUri(new String[] {"id"}, 30L);
	assertThat(uri, is(equalTo("/clients/30")));
	}

	@Test
	public void shouldTranslatePatternArgsWithMultipleRegexes() {
	String uri = new DefaultParametersControl("/test/{hash1:[a-z0-9]{16}}{id}{hash2:[a-z0-9]{16}}/", converters, evaluator).fillUri(new String[] {"hash1", "id", "hash2"}, "0123456789abcdef", "1234", "fedcba9876543210");
	assertThat(uri, is(equalTo("/test/0123456789abcdef1234fedcba9876543210/")));
	}
	
	@Test
	public void shouldTranslatePatternArgNullAsEmpty() {
		String uri = new DefaultParametersControl("/clients/{client.id}", converters, evaluator).fillUri(new String[] {"client"}, client(null));
		assertThat(uri, is(equalTo("/clients/")));
	}

	@Test
	public void shouldUseConverterIfItExists() {
		when(converters.existsTwoWayFor(Client.class)).thenReturn(true);
		when(converters.twoWayConverterFor(Client.class)).thenReturn(converter);
		when(converter.convert(any(Client.class))).thenReturn("john");

		String uri = new DefaultParametersControl("/clients/{client}", converters, evaluator).fillUri(new String[] {"client"}, client(null));
		assertThat(uri, is(equalTo("/clients/john")));

	}

	@Test
	public void shouldTranslatePatternArgInternalNullAsEmpty() {
		String uri = new DefaultParametersControl("/clients/{client.child.id}", converters, evaluator) .fillUri(new String[] {"client"}, client(null));
		assertThat(uri, is(equalTo("/clients/")));
	}

	@Test
	public void shouldMatchPatternLazily() throws Exception {
		DefaultParametersControl wrong = new DefaultParametersControl("/clients/{client.id}/", converters, evaluator);
		DefaultParametersControl right = new DefaultParametersControl("/clients/{client.id}/subtask/", converters, evaluator);
		String uri = "/clients/3/subtask/";

		assertThat(wrong.matches(uri), is(false));
		assertThat(right.matches(uri), is(true));

	}

	@Test
	public void shouldMatchMoreThanOneVariable() throws Exception {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{client.id}/subtask/{task.id}/", converters, evaluator);

		assertThat(control.matches("/clients/3/subtask/5/"), is(true));
	}
	private Client client(Long id) {
		return new Client(id);
	}

	@Test
	public void shouldBeGreedyWhenIPutAnAsteriskOnExpression() throws Exception {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{pathToFile*}", converters, evaluator);

		assertThat(control.matches("/clients/my/path/to/file/"), is(true));
	}
	@Test
	public void shouldNotBeGreedyAtPatternCompiling() throws Exception {
		DefaultParametersControl control = new DefaultParametersControl("/project/{project.name}/build/{buildId}/view/{filename*}", converters, evaluator);

		String uri = "/project/Vraptor3/build/12345/view/artifacts/vraptor.jar";
		assertThat(control.matches(uri), is(true));

		control.fillIntoRequest(uri, request);

		verify(request).setParameter("project.name", "Vraptor3");
		verify(request).setParameter("filename", new String[] {"artifacts/vraptor.jar"});
		verify(request).setParameter("buildId", new String[] {"12345"});
		assertThat(control.apply(new String[] {"Vraptor3", "12345", "artifacts/vraptor.jar"}),
				is(uri));
	}

	@Test
	public void registerExtraParametersFromAcessedUrlWithGreedyParameters() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{pathToFile*}", converters, evaluator);

		control.fillIntoRequest("/clients/my/path/to/file", request);

		verify(request).setParameter("pathToFile", new String[] {"my/path/to/file"});
	}

	@Test
	public void registerExtraParametersFromAcessedUrlWithGreedyAndDottedParameters() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{path.to.file*}", converters, evaluator);

		control.fillIntoRequest("/clients/my/path/to/file", request);

		verify(request).setParameter("path.to.file", new String[] {"my/path/to/file"});
	}

	static class PathToFile {

		public void withPath(String pathToFile) {

		}
	}
	@Test
	public void fillURLWithGreedyParameters() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{pathToFile*}", converters, evaluator);

		String filled = control.fillUri(new String[] {"pathToFile"}, "my/path/to/file");

		assertThat(filled, is("/clients/my/path/to/file"));
	}
	@Test
	public void fillURLWithoutGreedyParameters() throws SecurityException, NoSuchMethodException {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{pathToFile}", converters, evaluator);

		String filled = control.fillUri(new String[] {"pathToFile"}, "my/path/to/file");

		assertThat(filled, is("/clients/my/path/to/file"));
	}

	@Test
	public void whenNoParameterPatternsAreGivenShouldMatchAnything() throws Exception {
		ParametersControl control = new DefaultParametersControl("/any/{aParameter}/what", Collections.<String,String>emptyMap(), converters, evaluator);
		assertTrue(control.matches("/any/ICanPutAnythingInHere/what"));
	}
	@Test
	public void whenParameterPatternsAreGivenShouldMatchAccordingToGivenPatterns() throws Exception {
		ParametersControl control = new DefaultParametersControl("/any/{aParameter}/what",
				new ImmutableMap.Builder<String, String>().put("aParameter", "aaa\\d{3}bbb").build(), converters, evaluator);
		assertFalse(control.matches("/any/ICantPutAnythingInHere/what"));
		assertFalse(control.matches("/any/aaa12bbb/what"));
		assertTrue(control.matches("/any/aaa123bbb/what"));
	}

	@Test
	public void shouldFillRequestWhenAPatternIsSpecified() throws Exception {
		DefaultParametersControl control = new DefaultParametersControl("/project/{project.id}/build/",
				new ImmutableMap.Builder<String, String>().put("project.id", "\\d+").build(), converters, evaluator);

		String uri = "/project/15/build/";
		assertThat(control.matches(uri), is(true));

		control.fillIntoRequest(uri, request);

		verify(request).setParameter("project.id", "15");

		assertThat(control.apply(new String[] {"15"}),is(uri));
	}

	@Test
	public void shouldDecodeUriParameters() throws Exception {
		DefaultParametersControl control = new DefaultParametersControl("/clients/{name}", converters, evaluator);

		control.fillIntoRequest("/clients/Joao+Leno", request);

		verify(request).setParameter("name", "Joao Leno");

		control.fillIntoRequest("/clients/Paulo%20Macartinei", request);

		verify(request).setParameter("name", "Paulo Macartinei");
	}

}
