package main;

import static com.googlecode.lanterna.terminal.IOSafeTerminalAdapter.createRuntimeExceptionConvertingAdapter;
import static terminal.LispTerminal.END_OF_SEGMENT;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.util.function.Function;

import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
import com.googlecode.lanterna.terminal.IOSafeTerminal;
import com.googlecode.lanterna.terminal.Terminal;

import interpreter.LispInterpreter;
import interpreter.LispInterpreterBuilder;
import interpreter.LispInterpreterBuilderImpl;
import stream.UncheckedIOException;
import terminal.LispTerminal;
import terminal.TerminalConfiguration;

public class LispMain {

    public static final String GREETING = "Transcendental Lisp - Version 1.0.1";

    public static final String ANSI_RESET = "\u001B[0m";
    public static final String ANSI_RED = "\u001B[31m";
    public static final String ANSI_GREEN = "\u001B[32m";
    public static final String ANSI_YELLOW = "\u001B[33m";
    public static final String ANSI_PURPLE = "\u001B[35m";

    public static void main(String[] arguments) {
        LispMain lispMain = new LispMain();

        if (arguments.length == 0)
            lispMain.runInteractive();
        else
            lispMain.runWithFile(arguments[0]);
    }

    private LispInterpreterBuilder builder;
    private PipedInputStream inputReader;
    private PrintStream output;
    private LispTerminal lispTerminal;
    private TerminalConfiguration configuration;

    public LispMain() {
        this.builder = LispInterpreterBuilderImpl.getInstance();
    }

    public LispMain(LispInterpreterBuilder builder, TerminalConfiguration configuration) {
        this.builder = builder;
        this.configuration = configuration;
    }

    public void runInteractive() {
        initializeTerminal();
        printGreeting();
        lispTerminal.start();
        buildInteractiveInterpreter().interpret();
        shutdownTerminal();
    }

    private void initializeTerminal() {
        createTerminalConfiguration();
        inputReader = configuration.getInputReader();
        output = new PrintStream(configuration.getOutputWriter());
        lispTerminal = new LispTerminal(configuration);
    }

    private void createTerminalConfiguration() {
        if (configuration == null) {
            configuration = new TerminalConfiguration();
            configuration.setInputPair(new PipedOutputStream(), new PipedInputStream());
            configuration.setOutputPair(new PipedOutputStream(), new PipedInputStream());
            configuration.setTerminal(createIOSafeTerminal());
        }
    }

    private IOSafeTerminal createIOSafeTerminal() {
        return createRuntimeExceptionConvertingAdapter(createDefaultTerminal());
    }

    private Terminal createDefaultTerminal() {
        try {
            return new DefaultTerminalFactory().createTerminal();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private void printGreeting() {
        output.println(GREETING);
        output.println();
    }

    private LispInterpreter buildInteractiveInterpreter() {
        builder.setInput(inputReader, "terminal");
        builder.setOutput(output);
        builder.setErrorOutput(output);
        builder.setTerminationFunction(this::shutdownTerminal);
        builder.setErrorTerminationFunction(this::shutdownTerminal);
        builder.setPromptDecorator(s -> s + END_OF_SEGMENT);
        builder.setValueOutputDecorator(makeColorDecorator(ANSI_GREEN));
        builder.setWarningOutputDecorator(makeColorDecorator(ANSI_YELLOW));
        builder.setErrorOutputDecorator(makeColorDecorator(ANSI_RED));
        builder.setCriticalOutputDecorator(makeColorDecorator(ANSI_PURPLE));

        return builder.build();
    }

    private void shutdownTerminal() {
        output.print(END_OF_SEGMENT);
        lispTerminal.stop();
        output.close();
    }

    private Function<String, String> makeColorDecorator(String color) {
        return new Function<String, String>() {

            @Override
            public String apply(String s) {
                return color + s + ANSI_RESET;
            }
        };
    }

    public void runWithFile(String fileName) {
        buildFileInterpreter(fileName).interpret();
    }

    private LispInterpreter buildFileInterpreter(String fileName) {
        builder.useFile(fileName);
        builder.setOutput(System.out);
        builder.setErrorOutput(System.err);
        builder.setTerminationFunction(() -> System.exit(0));
        builder.setErrorTerminationFunction(() -> System.exit(1));
        builder.setValueOutputDecorator(makeColorDecorator(ANSI_GREEN));
        builder.setWarningOutputDecorator(makeColorDecorator(ANSI_YELLOW));
        builder.setErrorOutputDecorator(makeColorDecorator(ANSI_RED));
        builder.setCriticalOutputDecorator(makeColorDecorator(ANSI_PURPLE));

        return builder.build();
    }

}
