package at.oefai.aaa.agent.jam;

import java.util.List;

import at.oefai.aaa.agent.jam.types.FixedSizeList;
import at.oefai.aaa.agent.jam.types.SymbolMap;

/**
 * Class that stores needed meta-data and offers methods to retrieve and change it.
 *
 * @author Sandro Pirkwieser
 */
public class MetaData {
    private static final int EMOTION_HISTORY_SIZE = 20;
    private static final int INTENTION_HISTORY_SIZE = 6;
    private static final int APPRAISAL_COUNT_FOR_STANDARD_CHANGE = 2;
    private static final float STANDARD_INTENSITY_CHANGE_FACTOR = 0.025f;

    private final FixedSizeList<Float> emotionIntensities = new FixedSizeList<Float>(EMOTION_HISTORY_SIZE);
    private final FixedSizeList<Appraisal> emotions = new FixedSizeList<Appraisal>(EMOTION_HISTORY_SIZE);
    private final FixedSizeList<IntentionPair> failedIntentions = new FixedSizeList<IntentionPair>(INTENTION_HISTORY_SIZE);

    private final AgentLogger log;
    private final Interpreter interpreter;

    MetaData(final Interpreter pInterpreter) {
        this.interpreter = pInterpreter;
        this.log = interpreter.getLog();
    }

    // The Failed Intention - Part:

    // saves the failed intention
    public void saveFailedIntention(final IntentionPair intentPair) {
        this.failedIntentions.add(intentPair);
        if (intentPair != null) {
            this.log.info("Save failed IntentionPair: " + intentPair);
        }
    }

    public boolean intentionFailedRecently(final IntentionPair intentPair) {
        assert (intentPair != null) : "non-null IntentionPair required";
        if (this.failedIntentions.indexOf(intentPair) > -1) {
            this.log.info("Prevent intention from failing again: " + intentPair);
            return true;
        }
        return false;
    }

    // The Emotion History - Part:

    // updates mood and possibly saves the appraisal
    public void saveEmotion(final AbstractFactAppraisal afa) {
        assert (afa != null) : "null ref of AbstractFactAppraisal.";
        // (SP:) here is finer differentiation possible
        //if (afa instanceof ActionAppraisal) { }
        emotionIntensities.add(afa.getSignedIntensity());
        // only save if it did not contribute to a standard change
        if (!this.checkForStandardChange(afa)) {
            emotions.add(afa);
        }
    }

    public float getMood() {
        float mood = 0f;
        for (float fl : emotionIntensities) {
            mood += fl;
        }
        mood /= MetaData.EMOTION_HISTORY_SIZE;
        return mood;
    }

    // checks if the given appraisal occured often enough in the past to justify a change
    // of the corresponding standard, and changes the standard if so.
    private boolean checkForStandardChange(final AbstractFactAppraisal afa) {
        // get indices of already saved appraisals, that are equal
        List<Integer> indices = this.emotions.indicesOf(afa);
        if (indices.size() >= APPRAISAL_COUNT_FOR_STANDARD_CHANGE) { // sufficiently often
            // search for highest valued matching standard
            float maxAbsVal = 0f;
            WorldModelRelation standardToChange = null;
            Relation afaSubRel = afa.getRelation().getSubRelation(null);
            for (WorldModelRelation standard : this.interpreter.getWorldModel().getRelations(Relation.STANDARD_FACT)) {
                Relation standardSubRel = standard.getSubRelation(null);
                standardSubRel = standardSubRel.variableQuestionMarks(new SymbolMap());
                if (Relation.unify(standardSubRel, null, afaSubRel, null)) {
                    float standardAbsVal = Math.abs(standard.getStandardValue(null));
                    if (standardAbsVal > maxAbsVal) {
                        standardToChange = standard;
                        maxAbsVal = standardAbsVal;
                    }
                }
            }
            // check if standard found, and if so, change it, and then remove the appraisals,
            // otherwise it could change too often
            if (standardToChange != null) {
                float newValue = this.interpreter.getWorldModel().changeStandard(standardToChange,
                                      -afa.getSignedIntensity() * STANDARD_INTENSITY_CHANGE_FACTOR);
                this.emotions.remove(indices);
                this.log.info("Changed Standard: " + standardToChange.simpleString(null) + " to " + newValue);
                return true;
            }

        }
        return false;
    }

}
