package code.space;

import code.loop.IOQueue;
import code.stuff.Logger;
import code.stuff.Tracer;
import code.symbols.DataSymbol;
import code.table.MapTable;
import code.table.ModuleTable;
import code.table.UndefinedSymbol;
import code.term.Term;

import java.util.EmptyStackException;
import java.util.Enumeration;
import java.util.Stack;

/**
 * This class abstracts the narrowing space of a term.
 * The class is structured as a thread with an input queue
 * and an output queue.
 * This thread takes a term (its computation, actually)
 * from the input queue and places the result (its computation, actually)
 * in an output queue.
 * <P>
 * This thread waits when the input queue is empty and/or
 * when the output queue is full.
 * This thread should be notified when a term is placed in the
 * input queue and when a term is removed from the output queue.
 */

public class Space extends Thread {

    /**
     * Maximum number of free variables in a rewrite rule.
     */
    public static final int freeVar = 1024;

    public static int lastSourceStartPos = 0;
    public static int lastSourceEndPos = 0;
    public static String lastSourceFile = null;


    /**
     * Term stack register lent to computations.
     */
    public Stack preTerm = new Stack();

    /**
     * Term register lent to instructions.
     */
    public Term current;

    /**
     * Uninstantiated variables registers.
     */
    public Term[] varRegister = new Term[freeVar];

    /**
     * Set of computations.
     */
    private Queue inputQueue = new Queue();

    private boolean halt = false;   //Tells the thread to halt
    protected boolean halted = false; //Indicates that the thread has halted


    public static Space instance = new Space();

    protected Space() {
    }

    private IOQueue outputQueue;

    public void setOutputQueue(IOQueue outputQueue) {
        this.outputQueue = outputQueue;
    }

    // The following method is complicated, because it
    // cannot be synchronized.
    // The first statement does the job when there is a long
    // queue of computations to be stepped on.
    // The second statement does the job when there is no
    // computations in the queue.
    private boolean abort = false;

    public void abort() {
        if (Tracer.space)
            Logger.logln("Space abort request (inqueue size=" +
                    inputQueue.size() +
                    "  outqueue size=" + outputQueue.size() + ")");
        abort = true;
        // outputQueue.clear();
        Space.instance.interrupt();
    }

    public synchronized void register(Computation computation) {
        if (Tracer.space)
            Logger.logln("Space register request " + computation.getResult());
        // register a top-level computations only when
        // no other top-level computation is being executed
        if (Thread.currentThread() != this) {
            while (inputQueue.size() > 0) {
                try {
                    if (Tracer.space)
                        Logger.logln("Space register waiting " +
                                computation.getTerm());
                    wait();
                } catch (InterruptedException ie) {
                    if (Tracer.space)
                        Logger.logln("Space register interrupted " +
                                computation.getTerm());
                    throw new RuntimeException(ie.getMessage());
                }
            }
        }
        inputQueue.enqueue(computation);
        if (Tracer.space)
            Logger.logln("Space register enqueue " + computation.getResult());
        // if (inputQueue.size () == 1) notify();
        if (Thread.currentThread() != this) notify();
    }

    public synchronized void run() {
        //For debug only
        Term inTerm = null;

        setPriority(getPriority() - 1);
        while (!halt) {
            try {
                if (abort) {
                    // any pending call to method register
                    // originates from a top-level computation
                    if (Tracer.space)
                        Logger.logln("Space run aborting (clearing both queues)");
                    while (inputQueue.size() > 0) {
                        Computation c = inputQueue.next();
                        inputQueue.dequeue();
                    }
                    // The output queue is filled by computations only.
                    // Computations are executed by this thread only.
                    // All the computations have been eliminated.
                    // The output queue can be re-filled by new inputs only.
                    outputQueue.clear();
                    abort = false;
                } else {
                    Computation computation = (Computation) inputQueue.next();
                    try {
                        if (Tracer.space)
                            Logger.logln("Space analyzing " + computation.debug() + "  " +
                                    Computation.stateName[computation.getState()]);


                        // SAVE
                        // System.out.println(inputQueue.PrintAsTxtLoadable());
                        switch (computation.getState()) {
                            case Computation.SUCCESS:
                            case Computation.FAILED:
                            case Computation.ABANDONED:
                            case Computation.FLOUNDER:
                                unregister();
                                break;
                            case Computation.ACTIVE:
                                computation.step();
                                break;
                            case Computation.WAITING:
                            case Computation.RESIDUATING:
                            case Computation.PAUSED:
                                break;
                        }
                    } catch (UndefinedSymbol ex) {
                        System.out.println("Undefined symbol \"" + ex.getMessage() + "\"");
                        while (!preTerm.empty()) preTerm.pop();
                        computation.selfSetState(Computation.ABANDONED);
                    }
                }
            } catch (Queue.UnderflowException ex) {
                try {
                    if (Tracer.space)
                        Logger.logln("Space run waiting (queue is empty)");
                    wait();
                } catch (InterruptedException ie) {
                    if (Tracer.space)
                        Logger.logln("Space run interrupted (queue is empty)");
                }
            }  catch (Exception ex) {
		ex.printStackTrace();
		System.exit(1);
	    }
        }
        halted = true;
    }

    private void unregister() {
        Computation computation = inputQueue.dequeue();
        if (Tracer.space) {
            String term;
            try {
                term = computation.getTerm().toString();
            } catch (EmptyStackException ok) {
                term = "<empty stack>";
            }
            Logger.logln("Space unregister " + computation.debug() + "  " + term);
        }
        if (inputQueue.size() == 0) {
            outputQueue.setOutput(null);
        }
    
    }

    public void unregister(Computation c) {
        Computation computation = inputQueue.remove(c);
        if (Tracer.space) {
            String term;
            try {
                term = computation.getTerm().toString();
            } catch (EmptyStackException ok) {
                term = "<empty stack>";
            }
            Logger.logln("Space unregister " + computation.debug() + "  " + term);
        }
        if (inputQueue.size() == 0) {
            outputQueue.setOutput(null);
        }
    }

}
