package cucumber.runtime.rhino;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doNothing;
import gherkin.formatter.model.Tag;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

import cucumber.runtime.HookDefinition;
import cucumber.runtime.RuntimeGlue;
import cucumber.runtime.io.ClasspathResourceLoader;
import cucumber.runtime.io.ResourceLoader;

@RunWith(MockitoJUnitRunner.class)
public class RhinoHooksTest {

    private static final String[] NO_TAG = {};
    private static final String[] TAG = { "@bellies" };
    private static final String[] TAGS = { "@tag1", "@tag2" };

    private static final int DEFAULT_ORDER = 1000;
    private static final int DEFAULT_TIMEOUT = 0;

    private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    private final ResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader);

    @Mock
    private RuntimeGlue glue;

    private ArgumentCaptor<HookDefinition> beforeHookCaptor;
    private ArgumentCaptor<HookDefinition> afterHookCaptor;

    @Before
    public void startCapturingHooksArguments() {
        // given
        beforeHookCaptor = ArgumentCaptor.forClass(HookDefinition.class);
        afterHookCaptor = ArgumentCaptor.forClass(HookDefinition.class);
        doNothing().when(glue).addBeforeHook(beforeHookCaptor.capture());
        doNothing().when(glue).addAfterHook(afterHookCaptor.capture());
    }

    @Test
    public void shouldCallAddBeforeAndAfterHook() throws IOException {
        // when
        RhinoBackend jsBackend = new RhinoBackend(resourceLoader);
        jsBackend.loadGlue(glue, Collections.singletonList("cucumber/runtime/rhino_hooks"));
        List<HookDefinition> beforeHooks = beforeHookCaptor.getAllValues();
        List<HookDefinition> afterHooks = afterHookCaptor.getAllValues();

        // then
        assertHooks(beforeHooks.get(0), afterHooks.get(0), NO_TAG, DEFAULT_ORDER, DEFAULT_TIMEOUT);
        assertHooks(beforeHooks.get(1), afterHooks.get(1), TAG, DEFAULT_ORDER, DEFAULT_TIMEOUT);
        assertHooks(beforeHooks.get(2), afterHooks.get(2), TAGS, DEFAULT_ORDER, DEFAULT_TIMEOUT);
        assertHooks(beforeHooks.get(3), afterHooks.get(3), TAGS, DEFAULT_ORDER, 300);
        assertHooks(beforeHooks.get(4), afterHooks.get(4), TAGS, 10, DEFAULT_TIMEOUT);
        assertHooks(beforeHooks.get(5), afterHooks.get(5), TAGS, 20, 600);
    }

    @Test
    public void shouldFailWithTimeout() throws Throwable {
        // when
        RhinoBackend jsBackend = new RhinoBackend(resourceLoader);
        jsBackend.loadGlue(glue, Collections.singletonList("cucumber/runtime/rhino_hooks_timeout"));
        List<HookDefinition> beforeHooks = beforeHookCaptor.getAllValues();

        try {
            beforeHooks.get(0).execute(null);
        } catch (Exception e) {
            // then
            assertThat(e.getCause(), instanceOf(InterruptedException.class));
            return;
        }

        fail(InterruptedException.class.getSimpleName() + " expected");
    }

    private void assertHooks(HookDefinition beforeHook, HookDefinition afterHook, String[] tags, int order, long timeoutMillis) {
        assertHook(beforeHook, tags, order, timeoutMillis);
        assertHook(afterHook, tags, order, timeoutMillis);
    }

    private void assertHook(HookDefinition hookDefinition, String[] tagExprs, int order, long timeoutMillis) {
        assertThat(hookDefinition, instanceOf(RhinoHookDefinition.class));

        RhinoHookDefinition rhinoHook = (RhinoHookDefinition) hookDefinition;

        List<Tag> tags = new ArrayList<Tag>();

        for (String tagExpr : tagExprs) {
            tags.add(new Tag(tagExpr, null));
        }

        assertTrue(rhinoHook.getTagExpression().evaluate(tags));
        assertThat(rhinoHook.getOrder(), equalTo(order));
        assertThat(rhinoHook.getTimeout(), equalTo(timeoutMillis));
    }

}
@@ -162,98 +162,9 @@
 ert.
-assertThat;%0Aimport static org.junit.Assert.assertTrue;%0Aimport static org.junit.Assert.fail
+*
 ;%0Aim
@@ -713,16 +713,64 @@
 eLoader;
+%0Aimport org.mozilla.javascript.WrappedException;
 %0A%0A@RunWi
@@ -2799,32 +2799,71 @@
     %7D%0A%0A    @Test
+(expected = InterruptedException.class)
 %0A    public void
@@ -3239,17 +3239,43 @@
     
-%7D catch (
+    fail();%0A        %7D catch(Wrapped
 Exce
@@ -3281,16 +3281,23 @@
 eption e
+xpected
 ) %7B%0A    
@@ -3308,73 +3308,31 @@
     
-// then%0A            assertThat(e.getCause(), instanceOf(Interrupt
+throw expected.getWrapp
 edEx
@@ -3338,23 +3338,17 @@
 xception
-.class)
+(
 );%0A     
@@ -3354,102 +3354,9 @@
     
-    return;%0A        %7D%0A%0A        fail(InterruptedException.class.getSimpleName() + %22 expected%22);
+%7D
 %0A   

Simplify test. Make sure it fails is nothing is thrown.