package code.loop;

import code.term.*;
import code.loop.parser.*;
import code.subst.*;
import code.space.*;
import code.table.*;
import code.lang.*;
import code.stuff.*;
import code.type.*;
import code.loader.LoadManager;
import code.type.typechecker.TypeError;
import code.term.visitor.VariableNormalizer;
import code.subst.visitor.SubstOps;

import java.io.*;
import java.util.*;

/**
 * Description of the Class
 *
 * @author Sergio Antoy
 * @since June 17, 2003
 */
public class ReadEvalPrint extends Thread implements Client {

    private final static String badChoice = "Type \";\" for more or <return>";
    private static boolean performingIO = false;

    private BufferedReader in;
    private BufferedWriter out;

    /**
     * Constructor for the ReadEvalPrint object
     */
    public ReadEvalPrint() {
        this(new BufferedReader(new InputStreamReader(System.in)),
                new BufferedWriter(new OutputStreamWriter(System.out)));
    }


    /**
     * Constructor for the ReadEvalPrint object
     *
     * @param in Description of the Parameter
     */
    public ReadEvalPrint(BufferedReader in) {
        this(in, new BufferedWriter(new OutputStreamWriter(System.out)));
    }


    /**
     * Constructor for the ReadEvalPrint object
     *
     * @param out Description of the Parameter
     */
    public ReadEvalPrint(BufferedWriter out) {
        this(new BufferedReader(new InputStreamReader(System.in)), out);
    }


    /**
     * Constructor for the ReadEvalPrint object
     *
     * @param in  Description of the Parameter
     * @param out Description of the Parameter
     */
    public ReadEvalPrint(BufferedReader in, BufferedWriter out) {
        this.in = in;
        this.out = out;
        command = new Command(in, out);
        parser = new ClpParser(in, out);
        Space.instance.setOutputQueue(outputQueue);
        Space.instance.start();
    }


    private ClpParser parser;
    private IOQueue outputQueue = new IOQueue();
    private Command command;


    // IMPLEMENT Client
    /**
     * Gets the idString attribute of the ReadEvalPrint object
     *
     * @return The idString value
     */
    public String getIdString() {
        return "REP";
    }

    public void doneChild(Computation computation) {
        int state = computation.getState();
        switch (state) {
            case Computation.RESIDUATING:
                computation.forcedSetState(Computation.FLOUNDER);
                // no break !!!
            case Computation.SUCCESS:
            case Computation.FAILED:
                outputQueue.setOutput(computation);
                break;
            default:
                // it is OK
        }
    }

    private Term parseAndCheckIO(String string) throws Exception {
        Term term = (Term) parser.parse(string);
        TypeExpression texp = parser.parseTypeOnly(string);

        // determine if it's an IO expression
        if (texp instanceof TypeConstructorApplication) {
            TypeConstructorApplication tapp = (TypeConstructorApplication)texp;
            if (tapp.typeConstructor.equals(PredefinedTypes.ioTypeName)) {
                Term world = new Term(0);
                int appid = ModuleTable.getId(SystemModule.moduleName,SystemModule.applyName);
                term = new Term(appid,new Term[]{term,world});
                performingIO = true;
            }
        }

        return term;
    }
 
    private void forOutput(String header,
                           Computation computation,
                           BufferedWriter out) throws IOException {
        Subst substTmp = 
            SubstOps.instance.filterAnon
            (SubstOps.instance.selfApply(computation.getSubst()));
//1	Term result = computation.getResult();
//1	// The following also updates "result" with normalized vars
//1	Subst subst = new VariableNormalizer().doit(result, substTmp);
//1        String substString = SubstOps.instance.toString(subst);
        Pair<Term,Subst> normalized =
            new VariableNormalizer().doit
            (computation.getResult(), substTmp);
	Subst subst = normalized.second;
	String substString = SubstOps.instance.toString(subst);

	Term result = normalized.first;
        if (performingIO)
            // term looks like (value,world)
            result = result.getArgument((byte)0);
        String resultString = result.toString();

        Tracer.conditionallyPrintTime(out);
        out.write(header + (performingIO ? "$IO " : ""));
        out.write(resultString);
        if (! substString.equals(""))
            out.write("  { " + substString + "}");
        out.flush();
    }
    
    public void run() {
        setPriority(getPriority() + 1);
        try {
            if (Tracer.script) {
                noReturnScript();
            }
            // never returns after the call
            readLoop :
            for (; ;) {
                performingIO = false;
                displayInputPrompt();
                String string = readUserInput();
                boolean run = true;
                if (string == null) {
                    out.newLine();
                    out.flush();
                    System.exit(0);
                } else if (string.equals("")) {
                    continue;
                } else if (string.startsWith(":")) {
                    // suspend the evaluation
                    run = command.command(string);
                    string = null;
                    // resume the evaluation
                }
                if (run) {
                    try {
                        Tracer.startComputation();
                        Term term = null;
                        TypeExpression texp = null;
                        if (string != null) {
                            try {
                                term = parseAndCheckIO(string);
                            } catch (Exception e) {
                                continue;
                            }
                            ComputationFactory.create(term, this);
                        }

                        solutionLoop :
                        for (; ;) {
                            Computation computation
                                    = (Computation) outputQueue.getOutput();

                            if (computation == null) {
                                out.write("No more solutions.");
                                out.newLine();
                                break solutionLoop;
                            } else if (computation.getState() == Computation.FAILED) {
                                continue;
                            } else if (computation.getState() == Computation.SUCCESS) {
                                forOutput("Result: ", computation, out);
                                out.write(" ? ");
                                out.flush();
                                for (; ;) {
                                    String more = readUserInput();
                                    if (more == null) {
                                        out.newLine();
                                        out.flush();
                                        System.exit(0);
                                    } else if (more.equals(";")) {
                                        continue solutionLoop;
                                    } else if (more.equals("")) {
                                        Space.instance.abort();
                                        break solutionLoop;
                                    } else {
                                        out.write(badChoice);
                                        out.newLine();
                                        forOutput("Result: ", computation, out);
                                        out.write(" ? ");
                                        out.flush();
                                    }
                                }
                            } else if (computation.getState() == Computation.FLOUNDER) {
                                out.write("*** Goal flounders! ***");
                                out.newLine();
                            } else {
                                out.write("SOMETHING WENT TERRIBLY WRONG");
                            }
                        }
                    } catch (ParseError ex) {
                        out.write("Parse error: " + ex.getMessage());
                        out.newLine();
                        out.flush();
                    } catch (TypeError ex) {
                        out.write("Type Error: " + ex.getMessage());
                        out.newLine();
                        out.flush();
                    }
                }
            } 
        } catch (IOException ex) {
            throw new RuntimeException
                    ("ReadEvalPrint IO excetion " + ex.getMessage());
        } catch (Exception ex) {
            ex.printStackTrace();
            System.exit(1);
        }
	System.exit(0);
    }


    /**
     * Description of the Method
     *
     * @throws IOException Description of the Exception
     */
    private void displayInputPrompt() throws IOException {
        if (!(Tracer.batch || Tracer.script || Tracer.test)) {
            out.write(LoadManager.getCurrentModuleName()+"> ");
            out.flush();
        }
    }


    /**
     * Description of the Method
     *
     * @return Description of the Return Value
     * @throws IOException Description of the Exception
     */
    private String readUserInput() throws IOException {
        String string = in.readLine();
        if (string == null) {
            return null;
        } else {
            return string.trim();
        }
    }



    /**
     * Description of the Method
     *
     * @throws IOException Description of the Exception
     */
    private void noReturnScript() throws IOException {
        final int solutionLimit = 4;
        String string;
        performingIO = false;
        while ((string = readUserInput()) != null) {
            if (string.startsWith(":")) {
                // suspend the evaluation
                command.command(string);
                continue;
                // resume the evaluation
            } else if (string.equals("")) {
                continue;
            }
            Tracer.startComputation();
            Term term = null;
            try {
                term = parseAndCheckIO(string);
            } catch (Exception e) {
                // The specific exception should (!?) have been reported
                out.write("Error evaluating \""+string+"\"");
                out.newLine();
                out.flush();
                continue;
            }
            int solutionCount = 0;
            ComputationFactory.create(term, this);
            for (; ;) {
                Computation computation
                    = (Computation) outputQueue.getOutput();
                if (computation == null) {
                    if (Tracer.time && solutionCount == 0) {
                        Tracer.conditionallyPrintTime(out);
                        out.write(string + " -> [NO VALUE]");
                        out.newLine();
                        out.flush();
                    }
                    break;
                } else if (solutionCount >= solutionLimit) {
                    out.write(string + " -> ...");
                    out.newLine();
                    out.flush();
                    Space.instance.abort();
                    break;
                } else if (computation.getState() == Computation.FAILED) {
                    continue;
                } else if (computation.getState() == Computation.SUCCESS) {
                    solutionCount++;
                    forOutput(string + " -> ", computation, out);
                    out.newLine();
                    out.flush();
                } else if (computation.getState() == Computation.FLOUNDER) {
                    solutionCount++;
                    Tracer.conditionallyPrintTime(out);
                    out.write(string + " *** Goal flounders! ***");
                    out.newLine();
                    out.flush();
                }
            }
        }
        System.exit(0);
    }
}

