package at.oefai.aaa.agent.jam;

import java.text.NumberFormat;

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

/**
 * Represents an agent's goals.
 * @author Marc Huber
 * @author Jaeho Lee
 */
public abstract class AbstractGoal implements Goal {
    private Goal subgoal = null;
    private boolean newGoal = true; // Flag of whether goal is new or not.
    private Status status = Goal.Status.UNTRIED; // Run state of the intention (ACTIVE, SUSPENDED, SUCCESS, FAILURE, etc.)
    private APLElement intention = null; // Intended (instantiated with bindings) Plan
    private PlanRuntimeState runtimeState = null; // Plan information related to execution
    private IntentionStructure intentionStructure = null; // The IS from whence this came
    private NumberFormat nf = NumberFormat.getNumberInstance(); // for fixed point utility in toString


    /** Constructor with the goal specification, conclude relation, parent goal, and intention structure as parameters. */
    public AbstractGoal(final IntentionStructure is) {
        this.intentionStructure = is;
    }

    // Member functions

    public final boolean isNew() {
        return this.newGoal;
    }

    public final void setNew(final boolean flag) {
        this.newGoal = flag;
    }

    /** By default a goal is a toplevel goal.
     * therefore not final */
    public boolean isToplevelGoal() {
        return true;
    }

    public final boolean isLeafGoal() {
        return this.subgoal == null;
    }

    /** By default there is no GoalAction, sub-types override. */
    public GoalAction getGoalAction() {
        return null;
    }

    /** By default there is no conclude Relation, sub-types override. */
    public Relation getConcludeRelation() {
        return null;
    }

    public final Goal getSubgoal() {
        return this.subgoal;
    }

    public final void setSubgoal(final Goal g) {
        this.subgoal = g;
    }

    /** By default goals are toplevelgoals in a hierarchy, sub-types can override. */
    public Goal getPrevGoal() {
        return null;
    }

    public final APLElement getIntention() {
        return this.intention;
    }

    public final void setIntention(final APLElement se) {
        this.intention = se;
    }

    public final Status getStatus() {
        return this.status;
    }

    public final void setStatus(final Status st) {
        this.status = st;
    }

    public final PlanRuntimeState getRuntimeState() {
        return this.runtimeState;
    }

    public final void setRuntimeState(final PlanRuntimeState r) {
        this.runtimeState = r;
    }

    //public boolean hasRuntimeState() {
    //    return runtimeState != null;
    //}

    /** Verify that the plan's context is valid. */
    public final boolean confirmContext() {
        return (this.intention != null && !this.intention.getPlan().confirmContext(getIntentionBinding()));
    }

    /** Execute the procedure associated with a plan's BODY specification. */
    public final PlanRuntimeState.State execute() {
        return getRuntimeState().execute(getIntentionBinding(), this);
    }

    /** Execute the procedure associated with a plan's FAILURE specification. */
    public final PlanRuntimeState.State executeFailure() {
        return getIntention().getPlan().getFailure().newRuntimeState().execute(getIntentionBinding(), this);
    }

    /** Execute the procedure associated with a plan's EFFECTS specification. */
    public final PlanRuntimeState.State executeEffects() {
        return getIntention().getPlan().getEffects().newRuntimeState().execute(getIntentionBinding(), this);
    }

    /** Return the goal's name. null by default, override (e.g. relation label). */
    public String getName() {
        return null;
    }

    /** Check whether the goal should have an Applicable Plan List created for it. */
    public final boolean generateAPL() {
        if (// If the goal hasn't been completed yet and hasn't been UNPOSTed
                ((this.status != Goal.Status.SUCCESS) && (this.status != Goal.Status.ABANDONED))
                // and the goal is a "leaf" goal.
                && (this.subgoal == null)) {
            // If the goal already has an intended plan, then definitely do
            // not generate an APL
            if (this.intention != null) {
                return false;
            }
            // If it is "new" (meaning that it has just been POSTed, subgoaled,
            // or has been refreshed by the intention structure) then definitely
            // generate an APL
            if (isNew()) {
                return true;
            }
            // If anything in the world model has changed, then we need to
            // consider the goal for APL generation
            if (this.intentionStructure.getWorldModel().anyNew()) {
                return true;
            }
        }
        return false;
    }

    /** Get the binding of the goal arguments based upon the parent goal (if it exists, default null). */
    public Binding getGoalBinding() {
        return null;
    }

    /** Get the binding of the goal arguments based upon the plan's goal (if it exists). */
    public final Binding getIntentionBinding() {
        return (this.intention != null) ? this.intention.getBinding() : null;
    }

    /** Return whether the goal is still worth considering. */
    public final boolean isValid() {
        return ((this.status != Goal.Status.SUCCESS) && (this.status != Goal.Status.ABANDONED));
    }

    /** Check to see if the stack in which this goal is part is blocked. */
/*    public boolean isStackBlocked() {
        Goal gl = this.getPrevGoal();
        // Check this goal
        if (status == Goal.Status.BLOCKED) {
            return true;
        }
        // First move up to the top of the stack for this goal (if any), checking
        // on the way up.
        while (gl != null) {
            if (gl.getStatus() == Goal.Status.BLOCKED) {
                return true;
            }
            gl = gl.getPrevGoal();
        }
        // Then check from the goal down
        gl = subgoal;
        while (gl != null) {
            if (gl.getStatus() == Goal.Status.BLOCKED) {
                return true;
            }
            gl = gl.getSubgoal();
        }
        return false;
    }
*/

    /**
     * This function should be defined in this goal class because it must use the binding of the subgoaling plan,
     * not the candidate plans for this goal.
     */
    public float evalUtility() {
        return 0f;
    }

    public void reduceUtility() {
        // does nothing only in subclass GoalDrivenGoal
    }

    public final float getIntendedUtility() {
        if (this.intention != null) {
            return this.intention.evalUtility();
        }
        return this.evalUtility();
    }

    public final float getTopLevelIntendedUtility() {
        Goal aGoal = this;
        Goal tmpGoal;
        while ((tmpGoal = aGoal.getPrevGoal()) != null) {
            aGoal = tmpGoal;
        }
        return aGoal.getIntendedUtility();
    }

    protected final String getFixedPointIntendedUtility() {
        return this.nf.format(this.getIntendedUtility());
    }


    /** Find matches between bound and unbound variables. */
    public boolean matchRelation(final Relation dstRelation, final Binding dstBinding) {
        assert false;
        return false;
    }

    /** Check whether the goal's specification compares to the parameters. */
    public boolean matchGoal(final GoalAction pGoalAction, final Binding goalActionBinding) {
        return false;
    }

    /** Return the agent's intention structure. */
    public IntentionStructure getIntentionStructure() {
        return this.intentionStructure;
    }

    /** Remove the goal's intention and all subgoal intentions. */
    public void removeIntention(final boolean failed) {
        Goal aGoal = this;
        PlanAtomicConstruct failureSection;
        // Move down to bottom of subgoal string
        while (aGoal != null && aGoal.getSubgoal() != null) {
            aGoal = aGoal.getSubgoal();
        }
        // Now backtrack, cleaning things up on the way
        while (aGoal != null && (aGoal != this)) {
            if (failed && (aGoal.getIntention() != null)) {
                // Execute FAILURE section
                failureSection = aGoal.getIntention().getPlan().getFailure();
                if (failureSection != null) {
                    aGoal.setRuntimeState(failureSection.newRuntimeState());
                    aGoal.getRuntimeState().execute(aGoal.getIntentionBinding(), aGoal);
                }
            }
            // clear up memory
            aGoal.setIntention(null);
            // Remove the goal from the Intention Structure
            this.intentionStructure.removeGoal(aGoal);
            aGoal = aGoal.getPrevGoal();
        }
        // Execute FAILURE section for this plan too
        if (failed && this.intention != null) {
            failureSection = this.intention.getPlan().getFailure();
            if (failureSection != null) {
                this.runtimeState = failureSection.newRuntimeState();
                this.runtimeState.execute(aGoal.getIntentionBinding(), this);
            }
        }
        setIntention(null);
        setSubgoal(null);
        setRuntimeState(null);
    }

}
