package main;

import static java.text.MessageFormat.format;
import static main.LispMain.ANSI_GREEN;
import static main.LispMain.ANSI_PURPLE;
import static main.LispMain.ANSI_RESET;
import static main.LispMain.GREETING;
import static org.junit.Assert.assertEquals;

import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.CountDownLatch;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;
import org.junit.contrib.java.lang.system.SystemErrRule;
import org.junit.contrib.java.lang.system.SystemOutRule;

import com.googlecode.lanterna.input.KeyType;
import com.googlecode.lanterna.terminal.virtual.DefaultVirtualTerminal;

import environment.RuntimeEnvironment;
import interpreter.LispInterpreterBuilderImpl;
import terminal.TerminalConfiguration;
import terminal.VirtualTerminalInteractor;

public class MainTest {

    private RuntimeEnvironment environment;
    private CountDownLatch latch;

    public MainTest() {
        this.environment = RuntimeEnvironment.getInstance();
    }

    private void runInterpreterWithFile(String fileName) {
        TerminalConfiguration configuration = new TerminalConfiguration();
        configuration.setInputPair(new PipedOutputStream(), new PipedInputStream());
        configuration.setOutputPair(new PipedOutputStream(), new PipedInputStream());
        configuration.setTerminal(new DefaultVirtualTerminal());
        LispMain main = new LispMain(new LispInterpreterBuilderImpl() {}, configuration);

        main.runWithFile(fileName);
    }

    private VirtualTerminalInteractor runInterpreterAndGetInteractor() {
        VirtualTerminalInteractor terminal = new VirtualTerminalInteractor();
        latch = new CountDownLatch(1);

        new Thread(() -> {
            try {
                LispMain main = new LispMain(new LispInterpreterBuilderImpl() {}, terminal.getConfiguration());
                main.runInteractive();
            } finally {
                latch.countDown();
            }
        }).start();

        return terminal;
    }

    private void waitForInterpreterToShutdown() {
        try {
            latch.await();
        } catch (InterruptedException ignored) {}
    }

    private String getSystemOutLog() {
        return systemOutRule.getLogWithNormalizedLineSeparator();
    }

    private String getSystemErrLog() {
        return systemErrRule.getLogWithNormalizedLineSeparator();
    }

    @Rule
    public ExpectedSystemExit exit = ExpectedSystemExit.none();

    @Rule
    public SystemErrRule systemErrRule = new SystemErrRule().enableLog().mute();

    @Rule
    public SystemOutRule systemOutRule = new SystemOutRule().enableLog().mute();

    @Before
    public void setUp() {
        environment.reset();
    }

    @After
    public void tearDown() {
        environment.reset();
    }

    @Test
    public void runWithBadFile() {
        String expectedMessage = "[critical] test/main/test-files/bad.lisp (No such file or directory)";

        exit.expectSystemExitWithStatus(1);
        exit.checkAssertionAfterwards(() -> {
            assertEquals(format("{0}{1}{2}\n", ANSI_PURPLE, expectedMessage, ANSI_RESET), getSystemErrLog());
            assertEquals("", getSystemOutLog());

        });

        runInterpreterWithFile("test/main/test-files/bad.lisp");
    }

    @Test
    public void runWithFile_PrintsDecoratedLastValueOnly() {
        runInterpreterWithFile("test/main/test-files/file.lisp");

        assertEquals("", getSystemErrLog());
        assertEquals(format("{0}{1}{2}\n\n", ANSI_GREEN, "RADISH", ANSI_RESET), getSystemOutLog());
    }

    @Test
    public void runInteractive() {
        VirtualTerminalInteractor terminal = runInterpreterAndGetInteractor();

        terminal.waitForPrompt();
        terminal.enterCharacters("'hi");
        terminal.pressKey(KeyType.Enter);
        terminal.waitForPrompt();
        terminal.assertCursorPosition(2, 5);
        terminal.assertScreenText(GREETING, " ", "~ 'hi ", " ", "HI ", "~  ");
        terminal.enterControlCharacter('d');
        terminal.assertInputStreamClosed();

        waitForInterpreterToShutdown();
    }

    @Test
    public void runMain() {
        LispMain.main(new String[] { "test/main/test-files/file.lisp" });

        assertEquals("", getSystemErrLog());
        assertEquals(format("{0}{1}{2}\n\n", ANSI_GREEN, "RADISH", ANSI_RESET), getSystemOutLog());
    }

}
