package at.oefai.aaa.agent.jam;

import at.oefai.aaa.agent.jam.types.Binding;
import at.oefai.aaa.agent.jam.types.ExpList;
import at.oefai.aaa.agent.jam.types.Expression;
import at.oefai.aaa.agent.jam.types.Value;

/**
 * A subgoal action within a plan.
 * @author Marc Huber
 * @author Jaeho Lee
 */
abstract class GoalAction extends AbstractAction {
    private static final int FLOAT_MINIMUM_FACTOR = 3;
    private static final float UTILITY_REDUCE_FACTOR = 0.999f; // very slowly

    private final Relation goalRelation;
    private Expression utility;

    private ExpList by;
    private ExpList notBy;

    /** Full constructor. */
    GoalAction(final Relation pGoal, final Expression pUtility, final ExpList pBy, final ExpList pNotby) {
        this.goalRelation = pGoal;
        this.utility = pUtility;
        this.by = pBy;
        this.notBy = pNotby;
    }

    /** Partial constructor. */
    GoalAction(final Relation pGoal, final Expression pUtility) {
        this.goalRelation = pGoal;
        this.utility = pUtility;
        this.by = null;
        this.notBy = null;
    }

    // Member functions

    Relation getGoal() { return this.goalRelation; }

    Relation getRelation() { return this.goalRelation; }

    Expression getUtility() { return this.utility; }

    void setUtility(final Expression pUtility) { this.utility = pUtility; }

    /** returns the utility specified  after a toplevel goal.
     * @param binding
     * @return a float as utilities out of float range would be creatures of hellich insanity
     */
    public float evalUtility(final Binding binding) {
        return (this.utility != null) ? (float) this.utility.eval(binding).getReal() : 0f;
    }

    void reduceUtility(final Binding b) {
        float currUtil = this.evalUtility(b);
        if (currUtil > Float.MIN_VALUE * FLOAT_MINIMUM_FACTOR) {
            setUtility(Value.newValue(UTILITY_REDUCE_FACTOR * currUtil));
        }
    }

    void setBy(final ExpList pBy) { this.by = pBy; }

    ExpList getBy() { return this.by; }

    void setNotBy(final ExpList pNotBy) { this.notBy = pNotBy; }

    ExpList getNotBy() { return this.notBy; }

    public boolean isExecutableAction() { return false; }

    public Result execute(final Binding b, final Goal currentGoal) { return Result.CANNOT_EXECUTE; }

    //

    /** Check to see if the goal is applicable to the specified plan. */
    boolean isEligible(final Plan plan, final Binding binding) {
        Value str;
        String planName = plan.getName();
        // Check goal specifications
        // Currently, PERFORMS can use any plans, but ACHIEVE and MAINTAIN
        // can only use plans with an ACHIEVE GOAL: specification.
        // Stefan Rank: changed to MAINTAIN can also use PERFORM, as I use perform for
        // achieving external (perceived) state and
        // maintain for eternal goals...
        //if (((this instanceof AchieveGoalAction) || (this instanceof MaintainGoalAction))
        //    && (!(plan.getGoalSpecification() instanceof AchieveGoalAction))) {
        if ((this instanceof AchieveGoalAction)
            && (!(plan.getGoalSpecification() instanceof AchieveGoalAction))) {
            return false;
        } //else if (this instanceof PerformGoalAction) {
            // Accept everything
        //}
        // Check ":BY" list
        if (this.by != null) {
            for (Expression e : this.by) {
                str = e.eval(binding);
                if (str.isDefined() && str.getString().compareTo(planName) == 0) {
                    return true;
                }
            }
            return false;
        }
        // Check ":NOT-BY" list
        if (this.notBy != null) {
            for (Expression e : this.notBy) {
                str = e.eval(binding);
                if (str.isDefined() && str.getString().compareTo(planName) == 0) {
                    return false;
                }
            }
            return true;
        }
        return true;
    }

    /** Format the output and don't worry about being being printed out in-line with other information. */
    public String verboseString(final Binding b, final String head, final String tail) {
        return head + this.goalRelation.verboseString(b) + tail;
    }

    /** Format the output so that it's conducive to being printed out in-line with other information. */
    public String formattedStringEnhanced(final Binding b, final String head, final String tail) {
        return head + this.goalRelation.formattedString(b) + tail;
    }

    /** Format the output for in-line printing no binding output. */
    public String simpleString(final Binding b) {
        return this.goalRelation.simpleString(b);
    }

    public abstract String formattedString(final Binding b);

    public String getName() {
        return this.goalRelation.getName();
    }
}

