package function.builtin;

import static org.junit.Assert.assertTrue;
import static testutil.TestUtilities.evaluateString;
import static testutil.TypeAssertions.assertNil;
import static testutil.TypeAssertions.assertT;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import org.junit.Test;

import environment.RuntimeEnvironment;
import error.ErrorManager;
import function.ArgumentValidator.BadArgumentTypeException;
import function.ArgumentValidator.TooFewArgumentsException;
import function.ArgumentValidator.TooManyArgumentsException;
import testutil.SymbolAndFunctionCleaner;

public class LOADTest extends SymbolAndFunctionCleaner {

    private ByteArrayOutputStream outputStream;
    private ByteArrayOutputStream errorOutputStream;
    private RuntimeEnvironment environment;

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

    private void assertWarningMessagePrinted() {
        assertTrue(outputStream.toByteArray().length > 0);
        assertTrue(errorOutputStream.toByteArray().length == 0);
    }

    private void assertErrorMessagePrinted() {
        assertTrue(errorOutputStream.toByteArray().length > 0);
        assertTrue(outputStream.toByteArray().length == 0);
    }

    private void assertNothingPrinted() {
        assertTrue(errorOutputStream.toByteArray().length == 0);
        assertTrue(outputStream.toByteArray().length == 0);
    }

    @Override
    public void additionalSetUp() {
        outputStream = new ByteArrayOutputStream();
        errorOutputStream = new ByteArrayOutputStream();

        environment.reset();
        environment.setOutput(new PrintStream(outputStream));
        environment.setErrorOutput(new PrintStream(errorOutputStream));
        environment.setErrorManager(new ErrorManager());
        environment.setPath("");
        environment.setWarningOutputDecorator(s -> s);
        environment.setErrorOutputDecorator(s -> s);
    }

    @Override
    public void additionalTearDown() {
        environment.reset();
    }

    @Test
    public void loadEmptyFileName_ReturnsNilAndPrintsWarning() {
        String input = "(load \"\")";

        assertNil(evaluateString(input));
        assertWarningMessagePrinted();
    }

    @Test
    public void loadGoodFile_ReturnsTAndPrintsNothing() {
        String input = "(load \"test/function/builtin/test-files/load-good.lisp\")";

        assertT(evaluateString(input));
        assertNothingPrinted();
    }

    @Test
    public void loadBadFile_ReturnsNilAndPrintsError() {
        String input = "(load \"test/function/builtin/test-files/load-bad.lisp\")";

        assertNil(evaluateString(input));
        assertErrorMessagePrinted();
    }

    @Test
    public void loadNonExistentFile_ReturnsNilAndPrintsWarning() {
        String input = "(load \"doesNotExist.lisp\")";

        assertNil(evaluateString(input));
        assertWarningMessagePrinted();
    }

    @Test
    public void nestedLoadsInTheSameDirectory() {
        String input = "(load \"test/function/builtin/test-files/nested/nested.lisp\")";

        assertT(evaluateString(input));
        assertNothingPrinted();
    }

    @Test
    public void nestedLoadsInDifferentDirectories() {
        String input = "(load \"test/function/builtin/test-files/nested/one/load-one.lisp\")";

        assertT(evaluateString(input));
        assertNothingPrinted();
    }

    @Test(expected = BadArgumentTypeException.class)
    public void loadWithBadArgumentType() {
        evaluateString("(load '1)");
    }

    @Test(expected = TooManyArgumentsException.class)
    public void loadWithTooManyArguments() {
        evaluateString("(load \"1\" \"2\")");
    }

    @Test(expected = TooFewArgumentsException.class)
    public void loadWithTooFewArguments() {
        evaluateString("(load)");
    }

    @Test
    public void loadUsesRuntimePath() {
        environment.setPath("test/function/builtin/test-files/nested/one/");
        String input = "(load \"load-one.lisp\")";

        assertT(evaluateString(input));
        assertNothingPrinted();
    }

}
