/*
 * Copyright (c) 2007 Mockito contributors
 * This program is made available under the terms of the MIT License.
 */

package org.mockitousage.matchers;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.verification.WantedButNotInvoked;
import org.mockitousage.IMethods;
import org.mockitoutil.TestBase;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

public class CapturingArgumentsTest extends TestBase {

    class Person {

        private final Integer age;

        public Person(Integer age) {
            this.age = age;
        }

        public int getAge() {
            return age;
        }
    }

    class BulkEmailService {

        private EmailService service;

        public BulkEmailService(EmailService service) {
            this.service = service;
        }

        public void email(Integer ... personId) {
            for (Integer i : personId) {
                Person person = new Person(i);
                service.sendEmailTo(person);
            }
        }
    }

    interface EmailService {
        boolean sendEmailTo(Person person);
    }

    EmailService emailService = mock(EmailService.class);
    BulkEmailService bulkEmailService = new BulkEmailService(emailService);
    IMethods mock = mock(IMethods.class);

    @SuppressWarnings("deprecation")
    @Test
    public void should_allow_assertions_on_captured_argument() {
        //given
        ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);

        //when
        bulkEmailService.email(12);

        //then
        verify(emailService).sendEmailTo(argument.capture());
        assertEquals(12, argument.getValue().getAge());
    }

    @Test
    public void should_allow_assertions_on_all_captured_arguments() {
        //given
        ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);

        //when
        bulkEmailService.email(11, 12);

        //then
        verify(emailService, times(2)).sendEmailTo(argument.capture());
        assertEquals(11, argument.getAllValues().get(0).getAge());
        assertEquals(12, argument.getAllValues().get(1).getAge());
    }

    @Test
    public void should_allow_assertions_on_last_argument() {
        //given
        ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);

        //when
        bulkEmailService.email(11, 12, 13);

        //then
        verify(emailService, times(3)).sendEmailTo(argument.capture());
        assertEquals(13, argument.getValue().getAge());
    }

    @Test
    public void should_print_captor_matcher() {
        //given
        ArgumentCaptor<Person> person = ArgumentCaptor.forClass(Person.class);

        try {
            //when
            verify(emailService).sendEmailTo(person.capture());
            fail();
        } catch(WantedButNotInvoked e) {
            //then
            assertThat(e).hasMessageContaining("<Capturing argument>");
        }
    }

    @Test
    public void should_allow_assertions_on_captured_null() {
        //given
        ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);

        //when
        emailService.sendEmailTo(null);

        //then
        verify(emailService).sendEmailTo(argument.capture());
        assertEquals(null, argument.getValue());
    }

    @Test
    public void should_allow_construction_of_captor_for_parameterized_type_in_a_convenient_way()  {
        //the test passes if this expression compiles
        @SuppressWarnings("unchecked")
        ArgumentCaptor<List<Person>> argument = ArgumentCaptor.forClass(List.class);
        assertNotNull(argument);
    }

    @Test
    public void should_allow_construction_of_captor_for_a_more_specific_type()  {
        //the test passes if this expression compiles
        ArgumentCaptor<List<?>> argument = ArgumentCaptor.forClass(ArrayList.class);
        assertNotNull(argument);
    }

    @Test
    public void should_allow_capturing_for_stubbing() {
        //given
        ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
        when(emailService.sendEmailTo(argument.capture())).thenReturn(false);

        //when
        emailService.sendEmailTo(new Person(10));

        //then
        assertEquals(10, argument.getValue().getAge());
    }

    @Test
    public void should_capture_when_stubbing_only_when_entire_invocation_matches() {
        //given
        ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
        when(mock.simpleMethod(argument.capture(), eq(2))).thenReturn("blah");

        //when
        mock.simpleMethod("foo", 200);
        mock.simpleMethod("bar", 2);

        //then
        Assertions.assertThat(argument.getAllValues()).containsOnly("bar");
    }

    @Test
    public void should_say_something_smart_when_misused() {
        ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
        try {
            argument.getValue();
            fail();
        } catch (MockitoException expected) { }
    }

    @Test
    public void should_capture_when_full_arg_list_matches() throws Exception {
        //given
        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);

        //when
        mock.simpleMethod("foo", 1);
        mock.simpleMethod("bar", 2);

        //then
        verify(mock).simpleMethod(captor.capture(), eq(1));
        assertEquals(1, captor.getAllValues().size());
        assertEquals("foo", captor.getValue());
    }

    @Test
    public void should_capture_int_by_creating_captor_with_primitive_wrapper() {
        //given
        ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);

        //when
        mock.intArgumentMethod(10);

        //then
        verify(mock).intArgumentMethod(argument.capture());
        assertEquals(10, (int) argument.getValue());
    }

    @Test
    public void should_capture_int_by_creating_captor_with_primitive() throws Exception {
        //given
        ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(int.class);

        //when
        mock.intArgumentMethod(10);

        //then
        verify(mock).intArgumentMethod(argument.capture());
        assertEquals(10, (int) argument.getValue());
    }

    @Test
    public void should_capture_byte_vararg_by_creating_captor_with_primitive() throws Exception {
        // given
        ArgumentCaptor<Byte> argumentCaptor = ArgumentCaptor.forClass(byte.class);

        // when
        mock.varargsbyte((byte) 1, (byte) 2);

        // then
        verify(mock).varargsbyte(argumentCaptor.capture());
        assertEquals((byte) 2, (byte) argumentCaptor.getValue());
        Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly((byte) 1, (byte) 2);
    }

    @Test
    public void should_capture_byte_vararg_by_creating_captor_with_primitive_wrapper() throws Exception {
        // given
        ArgumentCaptor<Byte> argumentCaptor = ArgumentCaptor.forClass(Byte.class);

        // when
        mock.varargsbyte((byte) 1, (byte) 2);

        // then
        verify(mock).varargsbyte(argumentCaptor.capture());
        assertEquals((byte) 2, (byte) argumentCaptor.getValue());
        Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly((byte) 1, (byte) 2);
    }

    @Test
    public void should_capture_vararg() throws Exception {
        // given
        ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);

        // when
        mock.mixedVarargs(42, "a", "b", "c");

        // then
        verify(mock).mixedVarargs(any(), argumentCaptor.capture());
        Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly("a", "b", "c");
    }

    @Test
    public void should_capture_all_vararg() throws Exception {
        // given
        ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);

        // when
        mock.mixedVarargs(42, "a", "b", "c");
        mock.mixedVarargs(42, "again ?!");

        // then
        verify(mock, times(2)).mixedVarargs(any(), argumentCaptor.capture());

        Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly("a", "b", "c", "again ?!");
    }

    @Test
    public void should_capture_one_arg_even_when_using_vararg_captor_on_nonvararg_method() throws Exception {
        // given
        ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);

        // when
        mock.simpleMethod("a", 2);

        // then
        verify(mock).simpleMethod(argumentCaptor.capture(), eq(2));
        Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly("a");
    }

    @Test
    public void captures_correctly_when_captor_used_multiple_times() throws Exception {
        // given
        ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);

        // when
        mock.mixedVarargs(42, "a", "b", "c");

        // then
        // this is only for backwards compatibility. It does not make sense in real to do so.
        verify(mock).mixedVarargs(any(), argumentCaptor.capture(), argumentCaptor.capture(), argumentCaptor.capture());
        Assertions.assertThat(argumentCaptor.getAllValues()).containsExactly("a", "b", "c");
    }

    @Test
    public void captures_correctly_when_captor_used_on_pure_vararg_method() throws Exception {
        // given
        ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);

        // when
        mock.varargs(42, "capturedValue");

        // then
        verify(mock).varargs(eq(42), argumentCaptor.capture());
        Assertions.assertThat(argumentCaptor.getValue()).contains("capturedValue");
    }
}
