package code.loop;

import code.loader.LoadManager;
import code.loader.except.*;
import code.space.Client;
import code.space.Computation;
import code.space.ComputationFactory;
import code.space.Space;
import code.stuff.Tracer;
import code.subst.Subst;
import code.subst.visitor.SubstOps;
import code.table.*;
import code.table.UndefinedSymbol;
import code.symbols.*;
import code.type.*;
import code.term.Term;
import code.lang.SystemModule;

import java.util.Enumeration;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;

/**
 * This class loads a module and executes some of its methods.
 * There are two options controlled by invocation flags.
 * Either the symbol called "main" is evaluated, or
 * all the symbols starting with the characters "test" are evaluated
 * in some unspecified order.
 *
 * @author Sergio Antoy
 * @since September 2004
 */

public class Batcher extends Thread implements Client {

    private IOQueue outputQueue = new IOQueue();
    private BufferedWriter out
            = new BufferedWriter(new OutputStreamWriter(System.out));

    private String moduleName;
    // Next should be a parameter of the constructor
    private int solutionLimit = 4;


    public Batcher(String moduleName) {
        this.moduleName = moduleName;
    }

    public String getIdString() {
        return "BAT";
    }

    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;
           case Computation.ABANDONED:
                // OK
                break;
            default:
                throw new RuntimeException("Computation state "+
                                           Computation.stateName[state]+
                                           " in Batcher");
        }
    }

    public void run() {
        setPriority(getPriority() + 1);
        try {
            LoadManager.load(moduleName);
            Space.instance.setOutputQueue(outputQueue);
            Space.instance.start();
            if (Tracer.batch) execute("main");
            else if (Tracer.test) {
                Enumeration xenum = ModuleTable.getAllSymbols(moduleName);
                while (xenum.hasMoreElements()) {
                    Symbol symbol = (Symbol) xenum.nextElement();
                    String maybeTest = symbol.symbolName;
                    if (maybeTest.matches("test\\w*")) {
			// Name matches, now check type matches too
			// must be a nonfunction type.
                    	TypeExpression type = ((DataSymbol) symbol).typeExp;
                    	if (type instanceof FunctionType) continue;
                        else execute(maybeTest);
		    }
                }
		System.exit(0);
            } else {
		System.err.println("Something went terribly wrong in Batcher.");
		System.err.println("Neither \"-batch\" nor \"-test\" is set.");
	    }
	    System.exit(1);
        } catch (UndefinedSymbol ex) {
            System.err.println("The symbol \"" + ex.getMessage() + "\" is undefined");
        } catch (IOException ex) {
            throw new RuntimeException(ex.getMessage());
        } catch (Exception ex) {
            System.err.println("Unexpected exception " + ex.getMessage());
            System.err.println("Please inform the developers at antoy@cs.pdx.edu");
            ex.printStackTrace();
        }
        System.exit(1);
    }

    private void execute(String string) throws IOException {
        boolean performingIO = false;
	Tracer.startComputation();
        int solutionCount = 0;
        // If the module can't be loaded, skip the following
        int functId = ModuleTable.getId(moduleName, string);
        // if the symbol does not exist, skip the following
        Term term = new Term(functId, new Term[]{});
        // determine if this is an IO expression
        DataSymbol symbol = (DataSymbol)ModuleTable.getSymbol(moduleName,string);
        TypeExpression type = symbol.typeExp;
        if (type instanceof TypeConstructorApplication) {
            TypeConstructorApplication tapp = (TypeConstructorApplication)type;
            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;
            }
        }

        ComputationFactory.create(term, this);
        for (; ;) {
            Computation computation
                = (Computation) outputQueue.getOutput();
            if (computation == null) {
                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++;
                // we don't print IO output in batcher
                if (!performingIO) {
		    out.write(string + " -> " +
			      computation.getResult().toString());
		    Subst subst = computation.getSubst();
		    String answer =
			SubstOps.instance.toString
			(SubstOps.instance.filterAnon
			 (SubstOps.instance.selfApply(subst)));
		    if (!answer.equals("")) {
			out.write("  { " + answer + "}");
		    }
		    out.newLine();
		    out.flush();
		} 
                Tracer.conditionallyPrintTime(out);
            } else if (computation.getState() == Computation.FLOUNDER) {
                solutionCount++;
                out.write(string + " *** Goal flounders! ***");
                out.newLine();
                Tracer.conditionallyPrintTime(out);
                out.flush();
            }
        }
    }    
}
