package error;

import static error.ErrorManager.Severity.CRITICAL;
import static error.ErrorManager.Severity.ERROR;
import static error.ErrorManager.Severity.WARNING;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.Set;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import environment.RuntimeEnvironment;
import error.ErrorManager.Severity;

public class ErrorManagerTest {

    private static final String TERMINATED = "terminated";
    private static final String MESSAGE = "message";

    private Set<String> indicatorSet;
    private ByteArrayOutputStream errorOutputStream;
    private ByteArrayOutputStream outputStream;
    private RuntimeEnvironment environment;

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

    private ErrorManager createErrorManagerWithIndicators() {
        environment.setErrorTerminationFunction(() -> indicatorSet.add(TERMINATED));
        environment.setErrorOutput(new PrintStream(errorOutputStream));
        environment.setOutput(new PrintStream(outputStream));
        environment.setWarningOutputDecorator(s -> s);
        environment.setErrorOutputDecorator(s -> s);
        environment.setCriticalOutputDecorator(s -> s);

        return new ErrorManager();
    }

    private LispException createLispException(Severity severity) {
        return new LispException() {

            private static final long serialVersionUID = 1L;

            @Override
            public Severity getSeverity() {
                return severity;
            }

            @Override
            public String getMessage() {
                return MESSAGE;
            }
        };
    }

    private void assertTerminated() {
        assertTrue(indicatorSet.contains(TERMINATED));
    }

    private void assertNotTerminated() {
        assertFalse(indicatorSet.contains(TERMINATED));
    }

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

    private void assertWarningMessageWritten() {
        assertTrue(outputStream.toByteArray().length > 0);
    }

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

    private void assertErrorMessageWritten() {
        assertTrue(errorOutputStream.toByteArray().length > 0);
    }

    @Before
    public void setUp() {
        indicatorSet = new HashSet<>();
        errorOutputStream = new ByteArrayOutputStream();
        outputStream = new ByteArrayOutputStream();
        environment.reset();
    }

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

    @Test
    public void givenCriticalExceptionSeverity_RunsProvidedTerminationFunction() {
        ErrorManager errorManager = createErrorManagerWithIndicators();

        errorManager.handle(createLispException(CRITICAL));
        assertTerminated();
    }

    @Test
    public void givenWarningExceptionSeverity_DoesNotRunProvidedTerminationFunction() {
        ErrorManager errorManager = createErrorManagerWithIndicators();

        errorManager.handle(createLispException(WARNING));
        assertNotTerminated();
    }

    @Test
    public void givenErrorExceptionSeverity_DoesNotRunProvidedTerminationFunction() {
        ErrorManager errorManager = createErrorManagerWithIndicators();

        errorManager.handle(createLispException(ERROR));
        assertNotTerminated();
    }

    @Test
    public void usesOutputToDisplayWarningMessage() {
        ErrorManager errorManager = createErrorManagerWithIndicators();

        errorManager.handle(createLispException(WARNING));
        assertNotTerminated();
        assertErrorMessageNotWritten();
        assertWarningMessageWritten();
    }

    @Test
    public void usesErrorOutputToDisplayErrorMessage() {
        ErrorManager errorManager = createErrorManagerWithIndicators();

        errorManager.handle(createLispException(ERROR));
        assertNotTerminated();
        assertWarningMessageNotWritten();
        assertErrorMessageWritten();
    }

    @Test
    public void noMessageDisplayedBeforeError() {
        createErrorManagerWithIndicators();

        assertNotTerminated();
        assertErrorMessageNotWritten();
    }

    @Test
    public void usesOutputFunctionToDisplayMessages_WithTermination() {
        ErrorManager errorManager = createErrorManagerWithIndicators();

        errorManager.handle(createLispException(CRITICAL));
        assertTerminated();
        assertErrorMessageWritten();
    }

    @Test
    public void severityEnumCoverage() {
        for (Severity severity : Severity.values())
            Severity.valueOf(severity.toString());
    }

}
