// Copyright (c) 1996-2002 Brian D. Carlstrom

package bdc.scheme;

import bdc.util.MultiPrintWriter;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.PushbackReader;
import java.util.Arrays;

/**
    Read Eval Print Loop

    Reads an s-expression from stdin,
    (compiles it),
    Evaluates it,
    Prints the result on stdout.
    Loops until EOF on stdin.
*/
public class REPL implements Runnable
{
    /**
        The Scheme system we are a REPL for
    */
    private Scheme scheme;

    /**
        arguments to the REPL.

        If present the first is a file to load.
    */
    private String[] args;
    public static final String CurrentInputPortString  = "current-input-port";
    /*
        these are only used to pass state from the constructor to the thread
    */
    private PushbackReader      currentInputPort;
    private MultiPrintWriter    currentOutputPort;
    private MultiPrintWriter    currentErrorPort;

    /*
        Create a REPL with args and a specified I/O context.
    */
    public REPL (Scheme         scheme,
                 String[]       args,
                 PushbackReader currentInputPort,
                 OutputStream   currentOutputPort,
                 OutputStream   currentErrorPort)
    {
        this.scheme            = scheme;
        this.args              = args;
        this.currentInputPort  = currentInputPort;
        this.currentOutputPort = new MultiPrintWriter(currentOutputPort,
                                                      false);
        this.currentErrorPort = new MultiPrintWriter(currentErrorPort,
                                                     false);
    }

    /**
        Start the REPL on the I/O context specified in the constructor
    */
    public void run ()
    {
        /*
            Process arguments.

            We stash the arguments away in a GlobalVariable for the
            program to use.
        */
        scheme.globalEnvironment.define(Symbol.get("argv"), Arrays.asList(args));

        if (args.length > 0) {
            scheme.load(args[0]);
        }

        /*
            Read Eval Print Loop aka REPL
        */

        /* Create a reader for this REPL */
        Reader reader = new Reader(CurrentInputPortString,
                                   currentInputPort);
        Stack stack = new Stack(scheme,
                                currentInputPort,
                                currentOutputPort,
                                currentErrorPort);
        /* Loop until end of file on the currentInputPort is reached */
        Object read;
        while ((read = read(reader, stack)) != Scheme.EOFObject) {
            try {
                /* If we are keeping a transcript print what we read in */
                PrintWriter transcript = stack.currentTranscriptPort;
                if (transcript != null) {
                    transcript.println(Writer.write(read));
                }

                /* Compile what we read in and evaluate it */
                Object object = scheme.compiler.eval(read, stack);

                /*
                    If there was some interesting value print it.

                    It will automatically get printed to a transcript
                    if there is one thanks to MultiPrintWriter
                */
                if (object != Scheme.Unspecified) {
                    stack.currentOutputPort.println(Writer.write(object));
                }
            }
            catch (SchemeException se) {
                error(se, stack);
                read = null;
            }
        }
    }

    /**

        Print another prompt, read another value, and continue.

        If there is a problem reading return the value to
        Scheme.Unspecified which is self evaluating and causes the
        reader to print another prompt after the error message
    */
    private Object read (Reader reader, Stack stack)
    {
        stack.currentOutputPort.print("> ");
        stack.currentOutputPort.flush();
        try {
            return reader.read();
        }
        catch (SchemeException se) {
            error(se, stack);
            return Scheme.Unspecified;
        }
    }

    /**
        Report an exceptional error
    */
    public static void error (SchemeException se, Stack stack)
    {
        PrintWriter error = stack.currentErrorPort;
        se.stackTrace(error);
        error.flush();
    }
}
