package org.metaborg.spoofax.shell.client.console;

import static org.junit.Assert.fail;

import static org.metaborg.spoofax.shell.client.console.TerminalUserInterfaceTest.C_D;
import static org.metaborg.spoofax.shell.client.console.TerminalUserInterfaceTest.ENTER;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.RETURNS_MOCKS;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.junit.Test;
import org.metaborg.spoofax.shell.client.IEditor;
import org.metaborg.spoofax.shell.client.console.Repl;
import org.metaborg.spoofax.shell.client.console.Repl.ExitCommand;
import org.metaborg.spoofax.shell.commands.CommandNotFoundException;
import org.metaborg.spoofax.shell.commands.ICommandInvoker;
import org.mockito.Mockito;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.util.Modules;

/**
 * Tests the Repl by simulating user input.
 */
public class ReplTest {
    private ByteArrayInputStream in;
    private ByteArrayOutputStream out;
    private Repl repl;
    private Injector injector;

    private void createInjector(Module... overrides) {
        Module overridden = Modules.override(new ReplModule()).with(overrides);
        injector = Guice.createInjector(overridden);
    }

    /**
     * Create initial injector, later to be overwritten by {@link #createRepl(String, Module...)}.
     *
     * @param inputString
     *            The simulated user input.
     * @throws UnsupportedEncodingException
     *             When UTF-8 is not supported.
     */
    public void setUp(String inputString) throws UnsupportedEncodingException {
        in = new ByteArrayInputStream(inputString.getBytes("UTF-8"));
        out = new ByteArrayOutputStream();

        injector = Guice.createInjector(new ReplModule());
    }

    /**
     * Setup and inject the {@link InputStream}s and {@link OutputStream}s.
     *
     * @param overrides
     *            Module overrides w.r.t. ReplModule.
     * @throws IOException
     *             When an IO error occurs upon construction of the {@link ConsoleReader}.
     */
    public void createRepl(Module... overrides) throws IOException {
        createInjector(overrides);
        repl = injector.getInstance(Repl.class);
    }

    /**
     * Test whether the Repl exits when Ctrl + D is pressed.
     */
    @Test
    public void testCtrlDDoesExit() {
        try {
            setUp(String.valueOf(C_D));
            ICommandInvoker mock = mock(ICommandInvoker.class, RETURNS_MOCKS);
            createRepl(new UserInputSimulationModule(in, out), new MockModule(mock));
            repl.run();
            // Ensure that the command invoker is never called with any command.
            verify(mock, never()).execute(anyString());
        } catch (IOException | CommandNotFoundException e) {
            fail("Should not happen");
        }
    }

    /**
     * Tests the {@link ExitCommand}.
     */
    @Test
    public void testExitCommand() {
        try {
            setUp(":exit" + ENTER + ENTER + ":exit" + ENTER + ENTER);
            createInjector(new UserInputSimulationModule(in, out));
            ICommandInvoker invokerMock = spy(injector.getInstance(ICommandInvoker.class));
            IEditor editorMock = spy(injector.getInstance(IEditor.class));

            // Create a Repl with the mock invoker and editor.
            createRepl(new UserInputSimulationModule(in, out),
                       new MockModule(invokerMock, editorMock));

            // Hack: Create an ExitCommand manually, by giving a provider to the repl instance.
            // Then stub the invoker so that it returns this exitCommand, not its own.
            Repl.ExitCommand exitCommandMock = spy(new Repl.ExitCommand(() -> repl));
            Mockito.when(invokerMock.commandFromName("exit")).thenReturn(exitCommandMock);

            repl.run();

            // Ensure that the command was given to the invoker just once.
            verify(invokerMock, times(1)).execute(":exit");

            // Ensure that exitCommand was executed once.
            verify(exitCommandMock, times(1)).execute();

            // Verify that the Editor was not asked for input after the exit command was executed.
            verify(editorMock, times(1)).getInput();
        } catch (IOException | CommandNotFoundException e) {
            fail("Should not happen");
        }
    }
}

@@ -558,16 +558,73 @@
 MOCKS;%0A%0A
+import java.io.InputStream;%0Aimport java.io.OutputStream;%0A
 import j
@@ -1703,24 +1703,19 @@
 or, 
-later to
+can
  be over
 writ
@@ -1714,14 +1714,22 @@
 over
-w
 ri
-tte
+dden later o
 n by
@@ -1747,21 +1747,17 @@
 eate
-Repl(String, 
+Injector(
 Modu
@@ -2375,126 +2375,8 @@
 le.%0A
-     * @throws IOException%0A     *             When an IO error occurs upon construction of the %7B@link ConsoleReader%7D.%0A
     
@@ -2430,27 +2430,8 @@
 des)
- throws IOException
  %7B%0A 

Fix JavaDocs in ReplTest