package com.atlassian.stash.internal.web.soy.functions.nav;

import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.soy.renderer.JsExpression;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.junit.Assert.*;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class CompareNavFunctionTest {

    private static final String PROJECT = "project";
    private static final String REPO = "repo";

    private static final Function<Object, JsExpression> TO_JS = new Function<Object, JsExpression>() {
        public JsExpression apply(Object input) {
            return new JsExpression(input.toString());
        }
    };

    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private NavBuilder navBuilder;

    @InjectMocks
    private CompareNavFunction function;

    @Test
    public void testInitBuilderWith2Arguments() throws Exception {
        NavBuilder.Compare compare = mockNavBuilder(PROJECT, REPO);
        assertSame(compare, function.initBuilder(PROJECT, REPO));
    }

    @Test
    public void testInitBuilderWith4Arguments() throws Exception {
        NavBuilder.Compare compare = mockNavBuilder(PROJECT, REPO, "src", "target");
        assertSame(compare, function.initBuilder(PROJECT, REPO, "src", "target"));
    }

    @Test
    public void testInitBuilderWithInvalidNumberOfArguments() throws Exception {
        for (List<String> args : generateArguments()) {
            try {
                function.initBuilder(Iterables.toArray(args, String.class));
                fail("expected exception");
            } catch (IllegalArgumentException e) {
                // expected
            }
        }
    }

    @Test
    public void testInitJsBuilderWith2Arguments() throws Exception {
        JsNavBuilder jsBuilder = function.initJsBuilder(new JsExpression(PROJECT), new JsExpression(REPO));
        assertEquals("require('bitbucket/util/navbuilder').project(project).repo(repo).compare().build()", jsBuilder.build().getText());
    }

    @Test
    public void testInitJsBuilderWith4Arguments() throws Exception {
        JsNavBuilder jsBuilder = function.initJsBuilder(new JsExpression(PROJECT), new JsExpression(REPO), new JsExpression("src"), new JsExpression("target"));
        assertEquals("require('bitbucket/util/navbuilder').project(project).repo(repo).compare().sourceBranch(src).targetBranch(target).build()", jsBuilder.build().getText());
    }

    @Test
    public void testInitJsBuilderWithInvalidNumberOfArguments() throws Exception {
        for (List<JsExpression> args : generateArguments(TO_JS)) {
            try {
                function.initJsBuilder(Iterables.toArray(args, JsExpression.class));
                fail("expected exception");
            } catch (IllegalArgumentException e) {
                // expected
            }
        }
    }

    private List<List<String>> generateArguments() {
        return generateArguments(Functions.toStringFunction());
    }

    private <T> List<List<T>> generateArguments(Function<Object, T> converter) {
        Set<Integer> argSizes = new HashSet<>(ContiguousSet.create(Range.closed(0, 20), DiscreteDomain.integers()));
        argSizes.removeAll(function.validArgSizes());

        List<List<T>> argArrays = Lists.newArrayList();
        for (int size : argSizes) {
            Set<Integer> args = new HashSet<>(ContiguousSet.create(Range.closedOpen(0, size), DiscreteDomain.integers()));
            argArrays.add(Lists.newArrayList(Collections2.transform(args, converter)));
        }
        return argArrays;
    }

    private NavBuilder.Compare mockNavBuilder(String project, String repo) {
        NavBuilder.Compare compare = mock(NavBuilder.Compare.class);
        when(navBuilder.project(eq(project)).repo(eq(repo)).compare()).thenReturn(compare);
        return compare;
    }

    private NavBuilder.Compare mockNavBuilder(String project, String repo, String source, String target) {
        NavBuilder.Compare compare = mock(NavBuilder.Compare.class);
        when(navBuilder.project(eq(project)).repo(eq(repo)).compare().sourceBranch(eq(source)).targetBranch(eq(target))).thenReturn(compare);
        return compare;
    }
}
