package at.oefai.aaa.agent.jam;

import java.util.Iterator;

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

/**
 * An abstract superclass for appraisals of a fact (a WorldModelRelation).
 * @author Stefan Rank
 */
abstract class AbstractFactAppraisal implements Appraisal {

    private final WorldModelRelation relation; // the appraised fact
    private final IntentionStructure intentionStructure;
    private final Interpreter interpreter;
    // recency calculated on the fly from age of wmr
    private final float copingPotential;

    private Goal copingGoal = null;


    AbstractFactAppraisal(final WorldModelRelation pRelation, final Interpreter pInterpreter) {
        this.interpreter = pInterpreter;
        this.intentionStructure = pInterpreter.getIntentionStructure();
        this.relation = pRelation;
        this.copingPotential = calcCopingPotential(pInterpreter.getPlanLibrary());
    }

    /**
     * ATTENTION: equals of AbstractFactAppraisal violates the hashCode-equals contract.
     * equals here only considers the type of Appraisal and the appraised relation (no intensity...),
     * do not use Appraisal as Map-keys!
     */
    public boolean equals(final Object o) {
        if (o == null || o.getClass() != this.getClass()) {
            return false;
        }
        return Relation.unify(((AbstractFactAppraisal) o).getRelation(), null, this.getRelation(), null);
    }

    /**
     * ATTENTION: hashCode is simply calling super.hashCode(), i.e. Object.hashCode().
     * This must be changed if Appraisals should be used as keys in maps.
     */
    public int hashCode() {
        return super.hashCode();
    }

    protected final String getOwnerName() {
        return this.interpreter.getName();
    }

    private float calcCopingPotential(final PlanTable pt) {
        /** @TODO calc by counting coping applicable plans */
        return 1f;
    }

    public final void tryToCope() {
        final int numArgsToConsider = 3;
        final float utility = getUtility();
        ExpList explist = new ExpList();
        explist.add(Value.newValue(getImpulseExpression()));
        explist.add(Value.newValue(getIntensity()));
        Relation r = this.getRelation().getSubRelation(null);
        explist.add(Value.newValue(r.getName())); // the type of appraised action
        // from the args: first is the responsible then there could be more
        Iterator<Expression> iter = r.getArgs().iterator();
        for (int i = 0; i < numArgsToConsider; ++i) { // substitute missing args with empty strings
            if (iter.hasNext()) {
                explist.add(iter.next());
            } else {
                explist.add(Value.newValue("")); // empty string placeholder
            }
        }
        explist.add(Value.newValue(this.getRelation().getSuccessValue(null)));
        // whole appraisal in goal is faster to compare for removing in UserFunctions
        // StefanRank: not used at the moment I think, and fewer references are good :)
        //explist.add(Value.newValue(this));
        Relation goal = new Relation("tryToCope", explist);
        GoalAction ga = new PerformGoalAction(goal, Value.newValue(utility),
                                              (ExpList) null, (ExpList) null);
        this.copingGoal = this.intentionStructure.addUnique(ga);
    }

    public final void removeCoping() {
        if (this.copingGoal != null) {
            this.copingGoal.removeIntention(false);
            this.intentionStructure.removeGoal(this.copingGoal);
            this.copingGoal = null;
        }
    }

    protected float getImpulseThreshold() {
        return IMPULSE_THRESHOLD;
    }

    public final void postImpulse() {
        final float impulseUtility = getUtility() + IMPULSE_BONUS;
        if (impulseUtility > getImpulseThreshold()) {
            // dropping of lower intensity impulse goals is done centrally in Appraiser
            // add the impulse goal
            ExpList explist = new ExpList();
            explist.add(Value.newValue(getImpulseExpression()));
            explist.add(Value.newValue(getIntensity()));
            explist.add(Value.newValue(this)); // to see the immediate reason
            Relation goal = new Relation(Appraisal.IMPULSE_GOAL_STRING, explist);
            GoalAction ga = new PerformGoalAction(goal, Value.newValue(impulseUtility),
                                                  (ExpList) null, (ExpList) null);
            this.intentionStructure.addUnique(ga);
        }
    }

    // returns true if the update was successful, false if not --> appraisal could be removed
    public boolean updateEffects() {
        // check if the relation is retracted, if yes age it, if its too old, return false (im too old)
        WorldModelRelation r = this.getRelation();
        if (! r.isAsserted()) {
            r.advanceAge();
        }
        // also update the utility only if it isnt the highest utility goal on the stack
        // (also check if it has a subgoal, otherwise it stays there without executing)
        // so that probably executing coping plans are not interrupted prematurely.
        // the first goal from the intentionstack it should be, as they are sorted
        // by util during normal operation (except in the first cycle where no updateEffects can happen...)
        if (this.copingGoal != null) {
            float newUtility = this.getUtility();
            Goal g = this.intentionStructure.getToplevelGoals().getFirst();
            // SP: if for some reasons no execution is possible (i.e. no subgoal)
            // (StefanRank:) decrease utility also if top-level
            if (g == this.copingGoal && g.getSubgoal() != null) { // first goal identity check
                return true;
            }
            //if (r.getAge() < OLD_WMR_AGE_THRESHOLD) {
            if (newUtility > 1f) {
                // update coping goal utility
                this.copingGoal.getGoalAction().setUtility(Value.newValue(newUtility));
                return true;
            }
        }
        return false;
    }



    public float getIntensity() {
        return getRecency() * this.copingPotential;
    }

    private float getUtility() {
        return getIntensity() * UTILITY_FACTOR;
    }


    private float getRecency() {
        // recency of the fact: 1 means new, 0 is oldest (for multiplication)
        final int age = this.relation.getAge();
        if (age < OLD_WMR_AGE_THRESHOLD) {
            return 1f - (age / OLD_WMR_AGE_THRESHOLD);
        }
        return 0f;
    }

    public final WorldModelRelation getRelation() {
        return this.relation;
    }

    public final String verboseString() {
        String fullname = this.getClass().getName();
        StringBuffer sb = new StringBuffer(fullname.substring(fullname.lastIndexOf('.') + 1) + ": " + this.relation.simpleString(null));
        sb.append(this.valuesString());
        return sb.toString();
    }

    protected StringBuffer valuesString() {
        StringBuffer sb = new StringBuffer();
        sb.append("; recency: " + getRecency() + " copingPotential: " + this.copingPotential);
        return sb;
    }

    public String toString() {
        return this.verboseString();
    }

}
