package at.oefai.aaa.agent.jam;

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

/**
 * Represents an agent's goals.
 * @author Marc Huber
 * @author Jaeho Lee
 */
public class GoalDrivenGoal extends AbstractGoal {
    private final GoalAction goalAction; // The expression for the goal to be completed
    private final Goal prevGoal; // Goal for which this intention was created
    private Binding cachedGoalBinding = null;

    /** Constructor with the goal specification, conclude relation, parent goal, and intention structure as parameters. */
    public GoalDrivenGoal(final GoalAction pGoalAction, final Goal prev, final IntentionStructure is) {
        super(is);
        assert (pGoalAction != null) : "GoalDrivenGoal cant be created without GoalAction";
        this.goalAction = pGoalAction;
        this.prevGoal = prev;
        if (prev != null) {
            prev.setSubgoal(this);
            this.cachedGoalBinding = this.getGoalBinding();
        }
    }


    // Member functions


    public final boolean isToplevelGoal() {
        return this.prevGoal == null;
    }

    public final GoalAction getGoalAction() {
        return this.goalAction;
    }

    public final Goal getPrevGoal() {
        return this.prevGoal;
    }


    /** Return the goal's relation label. */
    public final String getName() {
        return (this.goalAction != null) ? this.goalAction.getName() : null;
    }

    /** Return the goal specification. */
    private Relation getRelation() {
        return (this.goalAction != null) ? this.goalAction.getRelation() : null;
    }

    /** Get the binding of the goal arguments based upon the parent goal (if it exists). */
    public final Binding getGoalBinding() {
        if (this.prevGoal != null) {
            Binding b = this.prevGoal.getIntentionBinding();
            if (b != null) {
                return b;
            }
        }
        return this.cachedGoalBinding;
    }


    /**
     * 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 final float evalUtility() {
        return this.goalAction.evalUtility(getGoalBinding());
    }

    public final void reduceUtility() {
        this.goalAction.reduceUtility(null);
    }



    /** Find matches between bound and unbound variables. */
    public final boolean matchRelation(final Relation dstRelation, final Binding dstBinding) {
        // pattRelation and pattBinding are from the goal to be POSTed
        // "this" is a goal that's already on the goal list
        return Relation.unify(dstRelation, dstBinding, getRelation(), getGoalBinding()); // srcRelation
    }

    /** Check whether the goal's specification compares to the parameters. */
    public final boolean matchGoal(final GoalAction pGoalAction, final Binding goalActionBinding) {
        if (pGoalAction != null) {
            if (matchRelation(pGoalAction.getRelation(), goalActionBinding)) {
                //if (goalAction.getUtility() != null ||
                //    evalUtility() == goalAction.evalUtility(goalActionBinding)) {
                //    return true;
                //}
                // Following fix from Alexander Staller (alexs@ai.univie.ac.at)
                // Austrian Research Institute for Artificial Intelligence
                if (pGoalAction.getUtility() == null) {
                    //log.info("  no utility specified -> TRUE");
                    return true;
                }
                if (this.evalUtility() == pGoalAction.evalUtility(goalActionBinding)) {
                    //log.info("  utility specified and matches -> TRUE");
                    return true;
                }
                //log.info("  utility specified and NOT matching -> FALSE");
            }
        }
        return false;
    }



    /** Format output to the given stream so that it can be in-line with other output. */
    public final String formattedString() {
        StringBuffer sb = new StringBuffer();
        sb.append("GoalDrivenGoal: ");
        if (this.goalAction != null) {
            sb.append(this.goalAction.formattedStringEnhanced(getGoalBinding(), "", ""));
        } else {
            sb.append("null" + "\n");
        }
        sb.append(" || Utility: " + this.getIntendedUtility() + "\n");
        sb.append(", New?: " + isNew());
        sb.append(", Status: " + getStatus());
        sb.append(", Subgoal: " + ((getSubgoal() != null) ? getSubgoal().getName() : "NONE"));
        sb.append(", Prev_Goal: " + ((this.prevGoal != null) ? this.prevGoal.getName() : "NONE"));
        sb.append(", Intention: " + getIntention() + "\n");
        sb.append(", RuntimeState: " + getRuntimeState() + "\n");
        return sb.toString();
    }

    public final String toString() {
        StringBuffer sb = new StringBuffer();
        if (isNew()) {
            sb.append("New ");
        }
        sb.append("Goal ");
        sb.append("|Utility:" + this.getFixedPointIntendedUtility() + "| ");
        if (this.goalAction != null) {
            sb.append(this.goalAction.simpleString(getGoalBinding()));
        } else {
            sb.append("null");
        }
        sb.append(" | " + getStatus());
        APLElement ae = getIntention();
        if (ae != null && ae.getPlan() != null) {
            //sb.append(", intended Plan: " + ae.getPlan().getName());
            sb.append(", Plan: " + ae.getPlan().getName()); // try if newline works in tree
        } else {
            sb.append(", no Intention");
        }
        return sb.toString();
    }
}
