package at.oefai.aaa.agent.jam;

import at.oefai.aaa.agent.jam.types.Binding;

/**
 * Represents the runtime state of parallel execution of sequences within plans.
 * @author Marc Huber
 * @author Jaeho Lee
 */
class PlanRuntimeParallelState implements PlanRuntimeState {

    private PlanConstruct thisConstruct = null;
    private PlanRuntimeState substate = null;

    private boolean threadsStarted;
    private PlanRuntimeThreadState[] threads;
    private State[] threadState;
    public static final boolean OUTPUTFLAG1 = false;
    public static final boolean OUTPUTFLAG2 = false;

    //
    // Constructors
    //
    PlanRuntimeParallelState(final PlanParallelConstruct be) {
        this.thisConstruct = be;
        this.substate = be.getConstruct(0).newRuntimeState();
        // Initialize the array to all constructs incomplete.
        this.threadsStarted = false;
        int threadNum;
        this.threadState = new State[be.getNumConstructs()];
        this.threads = new PlanRuntimeThreadState[be.getNumConstructs()];
        for (threadNum = 0; threadNum < be.getNumConstructs(); threadNum++) {
            this.threadState[threadNum] = State.CONSTRUCT_INCOMP;
        }
    }

    //
    // Member functions
    //
    public State execute(final Binding b, final Goal thisGoal) {
        // Spawn off each thread and then, every execution cycle, wake them up so
        // they'll execute the next action/construct.
        int threadNum;
        boolean anyFailed = false;
        boolean allComplete = true;
        final int numConstructs = ((PlanParallelConstruct) this.thisConstruct).getNumConstructs();

        AgentLogger log = thisGoal.getIntentionStructure().getLog();

        if (OUTPUTFLAG2) log.info("PRPS: entering execute method");

        // If threads have not been started then created them and start them.
        if (! this.threadsStarted) {
            for (threadNum = 0; threadNum < numConstructs; threadNum++) {
                this.threads[threadNum] = new PlanRuntimeThreadState(((PlanSequenceConstruct)
                    ((PlanParallelConstruct) this.thisConstruct).getConstruct(threadNum)), b, thisGoal, threadNum, this.threadState);
                if (OUTPUTFLAG2) log.info("PRPS: Starting thread# " + threadNum);
                this.threads[threadNum].start();
            }
            this.threadsStarted = true;
        }

        // Comment: Without the sleep statement the interpreter is blocked.
        // Checked with jdk1.2.2 under Windows NT by klesen on August 30 2000.
        try {
            synchronized (this) { Thread.sleep(0, 1); }
        } catch (InterruptedException e) {
            if (OUTPUTFLAG2) log.info("PRPS: Interrupted while in wait(): " + e + ".");
            e.printStackTrace();
            return State.CONSTRUCT_FAILED;
        }

        // Go through the threads and check if any have failed or if all
        // have completed.
        for (threadNum = 0; threadNum < numConstructs; threadNum++) {
            if (this.threadState[threadNum] == State.CONSTRUCT_FAILED) {
                if (OUTPUTFLAG2) log.info("PRPS: Found that thread# " + threadNum + " has failed.");
                anyFailed = true;
                allComplete = false;
            } else if (this.threadState[threadNum] == State.CONSTRUCT_INCOMP) {
                if (OUTPUTFLAG1) log.info("PRPS: Found that thread# " + threadNum + " is incomplete.");
                allComplete = false;
            } else {
                if (OUTPUTFLAG1) log.info("PRPS: Found that thread# " + threadNum + " has status:" + this.threadState[threadNum]);
            }
        }

        // Make sure all the threads are done before we signal that
        // the construct completed.
        if (allComplete) {
            if (OUTPUTFLAG2) log.info("PRPS: All threads complete.");
            for (threadNum = 0; threadNum < numConstructs; threadNum++) {
                try {
                    if (OUTPUTFLAG1) log.info("PRPS: 1.Telling thread#" + threadNum + " to join.");
                    this.threads[threadNum].join();
                } catch (InterruptedException e) {
                    if (OUTPUTFLAG2) log.info("Interrupted while in waiting for join()s " + e + ".");
                    e.printStackTrace();
                    return State.CONSTRUCT_FAILED;
                }
            }
            return State.CONSTRUCT_COMPLETE;
        } // else
        if (OUTPUTFLAG2) log.info("PRPS: All threads were NOT complete.");

        // At least one thread failed, so stop the threads and wait for them to die
        if (anyFailed) {
            if (OUTPUTFLAG2) log.info("PRPS: At least one thread failed.");

            for (threadNum = 0; threadNum < numConstructs; threadNum++) {
                //:modified by klesen June Thu 29 2000
                if (this.threads[threadNum].isAlive()) {
                    if (OUTPUTFLAG2) log.info("PRPS: Telling thread# " + threadNum + " to myStop.");
                    this.threads[threadNum].myStop();
                } else {
                    if (OUTPUTFLAG2) log.info("PRPS: Found thread# " + threadNum + " was dead.");
                }
            }

            for (threadNum = 0; threadNum < numConstructs; threadNum++) {
                try {
                    if (OUTPUTFLAG2) log.info("PRPS: 2.Telling thread#" + threadNum + " to join.");
                    this.threads[threadNum].join();
                } catch (InterruptedException e) {
                    if (OUTPUTFLAG2) log.info("Interrupted while in waiting for join()s " + e + ".");
                    e.printStackTrace();
                    return State.CONSTRUCT_FAILED;
                }
            }
            return State.CONSTRUCT_FAILED;
        } // else {
        if (OUTPUTFLAG2) log.info("PRPS: No threads failed.");

        // If they're all complete, then kick them to go on to the next step.
        // Note that the stepComplete method will wait for a thread that hasn't
        // yet had its construct completed.
        for (threadNum = 0; threadNum < numConstructs; threadNum++) {
            if (this.threadState[threadNum] == State.CONSTRUCT_INCOMP) {
                //:modified by klesen August Wed 30 2000
                if (this.threads[threadNum].stepComplete()) {
                    if (OUTPUTFLAG2)
                        log.info("PRPS: 2. Plan construct incomplete and stepComplete true. Telling thread#"
                            + threadNum + " to myResume.");
                    this.threads[threadNum].myResume();
                }
            } else {
                if (OUTPUTFLAG1) log.info("PRPS: 2. Plan construct had threadstate of " + this.threadState[threadNum]);
            }
        }

        return State.CONSTRUCT_INCOMP;
    }

}

