package info.ricardokagawa.grapple.test.internal;

import info.ricardokagawa.grapple.FormattingException;
import info.ricardokagawa.grapple.GrappleFormat;
import info.ricardokagawa.grapple.GrappleValue;
import info.ricardokagawa.grapple.MalformedRequestException;
import info.ricardokagawa.grapple.internal.Utilities;
import info.ricardokagawa.grapple.plugin.api.ValueFormatter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import java.util.function.Function;
import org.hamcrest.CoreMatchers;
import static org.hamcrest.CoreMatchers.equalTo;
import org.hamcrest.Matcher;
import static org.junit.Assert.assertThat;
import org.junit.Test;

/**
 * Tests for {@link Utilities} methods.
 */
public class UtilitiesTest {

    private static final Function<Class<?>, ValueFormatter> WITHOUT_FORMATTERS
            = t -> null;

    private static final Function<Class<?>, ValueFormatter> WITH_FORMATTERS
            = t -> (v -> Collections.singleton("test-value"));

    private static final String NULL = "test-null";

    private static Matcher<Collection<String>> hasSingleValue(String value) {
        return CoreMatchers.<Collection<String>>either(
                equalTo(singleton(value)))
                .or(equalTo(singletonList(value)));
    }

    private static Matcher<Collection<String>> empty() {
        return CoreMatchers.<Collection<String>>either(
                equalTo(emptySet())).or(equalTo(emptyList()));
    }

    @Test
    public void format() {
        assertThat(Utilities.format(null, NULL, GrappleFormat::path,
                WITH_FORMATTERS), hasSingleValue("test-value"));
        assertThat(Utilities.format(new Object(), NULL, GrappleFormat::path,
                WITH_FORMATTERS), hasSingleValue("test-value"));

        assertThat(Utilities.format(emptySet(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS),
                equalTo(emptyList()));
        assertThat(Utilities.format(singleton("test"),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS),
                hasSingleValue("test"));
        assertThat(Utilities.format(Arrays.asList("egg", "ham", null, "spam"),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS),
                equalTo(Arrays.asList("egg", "ham", NULL, "spam")));

        assertThat(Utilities.format(new TestValue1(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS),
                hasSingleValue("test"));
        assertThat(Utilities.format(new TestValue2(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS),
                hasSingleValue("test"));
        assertThat(Utilities.format(new TestValue3(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS),
                hasSingleValue("test"));
        assertThat(Utilities.format(new TestValue4(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS),
                hasSingleValue("path"));

        assertThat(Utilities.format(null, NULL, GrappleFormat::path,
                WITHOUT_FORMATTERS), hasSingleValue(NULL));

        // branch coverage: is annotated without formatter
        assertThat(Utilities.format(new TestValue5(),
                NULL, GrappleFormat::path, WITH_FORMATTERS),
                hasSingleValue("test-value"));

        // branch coverage: grapple value returns null
        assertThat(Utilities.format(new TestValue6(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS),
                hasSingleValue(NULL));

        // branch coverage: formatter returns null
        assertThat(Utilities.format(new TestValue7(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS), empty());
    }

    @Test(expected = FormattingException.class)
    public void formatCheckedException() {
        Utilities.format(new TestValue8(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS);
    }

    @Test(expected = IllegalArgumentException.class)
    public void formatUncheckedException() {
        Utilities.format(new TestValue9(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS);
    }

    @Test(expected = MalformedRequestException.class)
    public void formatConstructorInvalid() {
        Utilities.format(new TestValue10(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS);
    }

    @Test(expected = FormattingException.class)
    public void formatConstructorCheckedException() {
        Utilities.format(new TestValue11(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS);
    }

    @Test(expected = IllegalArgumentException.class)
    public void formatConstructorUncheckedException() {
        Utilities.format(new TestValue12(),
                NULL, GrappleFormat::path, WITHOUT_FORMATTERS);
    }

    public static class TestFormatter1 implements ValueFormatter {

        @Override
        public Collection<String> format(Object value) {
            return singleton("test");
        }

    }

    public static class TestFormatter2 implements ValueFormatter {

        @Override
        public Collection<String> format(Object value) {
            return singleton("path");
        }

    }

    public static class TestFormatter3 implements ValueFormatter {

        @Override
        public Collection<String> format(Object value) throws Exception {
            return null;
        }

    }

    public static class TestFormatter4 implements ValueFormatter {

        @Override
        public Collection<String> format(Object value) throws Exception {
            throw new Exception("test checked exception");
        }

    }

    public static class TestFormatter5 implements ValueFormatter {

        @Override
        public Collection<String> format(Object value) throws Exception {
            throw new IllegalArgumentException("test unchecked exception");
        }

    }

    public static class TestFormatter6 implements ValueFormatter {

        private TestFormatter6() {
        }

        @Override
        public Collection<String> format(Object value) throws Exception {
            return null;
        }

    }

    public static class TestFormatter7 implements ValueFormatter {

        public TestFormatter7() throws Exception {
            throw new Exception("test checked exception");
        }

        @Override
        public Collection<String> format(Object value) throws Exception {
            return null;
        }

    }

    public static class TestFormatter8 implements ValueFormatter {

        public TestFormatter8() {
            throw new IllegalArgumentException("test unchecked exception");
        }

        @Override
        public Collection<String> format(Object value) throws Exception {
            return null;
        }

    }

    public class TestValue1 implements GrappleValue {

        @Override
        public Collection<String> toStringValues() {
            return singleton("test");
        }

    }

    @GrappleFormat(path = TestFormatter1.class)
    public class TestValue2 {
    }

    @GrappleFormat(TestFormatter1.class)
    public class TestValue3 {
    }

    @GrappleFormat(value = TestFormatter1.class, path = TestFormatter2.class)
    public class TestValue4 {
    }

    @GrappleFormat
    public class TestValue5 {
    }

    public class TestValue6 implements GrappleValue {

        @Override
        public Collection<String> toStringValues() {
            return null;
        }

    }

    @GrappleFormat(TestFormatter3.class)
    public class TestValue7 {
    }

    @GrappleFormat(TestFormatter4.class)
    public class TestValue8 {
    }

    @GrappleFormat(TestFormatter5.class)
    public class TestValue9 {
    }

    @GrappleFormat(TestFormatter6.class)
    public class TestValue10 {
    }

    @GrappleFormat(TestFormatter7.class)
    public class TestValue11 {
    }

    @GrappleFormat(TestFormatter8.class)
    public class TestValue12 {
    }

}
