package at.oefai.aaa.agent.jam;

import java.io.Serializable;
import java.util.List;
import java.util.ListIterator;

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

/**
 * The heart of the appraisal process.
 * Finds matches between the relations in the worldmodeltable and the goals
 * on the intentionstructure, and puts the results in an AppraisalRegister
 * @author Stefan Rank
 */
public class Appraiser implements Serializable {
    private static final float FAILED_ATTEMPT_FACTOR = 0.4f;
    private static final float ABS_RELEVANCE_THRESHOLD = 0.5f;
    private static final float ABS_CONFORMANCE_THRESHOLD = 0.5f;
    private static final float ABS_PREFERANCE_THRESHOLD = 0.75f;
    private static final float UTILITY_CORRECTION = 5f / Appraisal.UTILITY_FACTOR;

    private final String name;
    private final WorldModelTable worldModel;
    private final IntentionStructure intentionStructure;
    private final AgentLogger log;
    private final AppraisalRegister register;

    Appraiser(final Interpreter pInterpreter) {
        this.name = pInterpreter.getName();
        this.worldModel = pInterpreter.getWorldModel();
        this.intentionStructure = pInterpreter.getIntentionStructure();
        this.log = pInterpreter.getIntentionStructure().getLog();
        this.register = new AppraisalRegister(pInterpreter);
    }

    /**
     * Appraisal happens for each fact in the world model during each cycle,
     * the outcome influences the utility of already intended plans (otherwise utilities decrease over time).
     * (possibly put appraisal outcome in preconditions and check periodically (less than every cycle))
     */
    public void appraise() {
        // update effects of all existing appraisals
        // (also ages already retracted world model entries)
        this.register.updateEffects();

        // get relevant behaviour goals
        DList<Goal> behaviourGoals = this.intentionStructure.getBehaviourGoals(); // statt getsuccessgoals()
        List<WorldModelRelation> standards = this.worldModel.getRelations(Relation.STANDARD_FACT);

        // use the standards for a list of virtual goals:
        DList<Goal> fakeStdGoals = makeFakeStdGoals(standards);

        List<WorldModelRelation> agentWantsTos = this.worldModel.getRelations(Relation.AGENTWANTS_FACT);
        List<WorldModelRelation> agentDids = this.worldModel.getRelations(Relation.AGENTDID_FACT);
        List<WorldModelRelation> agentFaileds = this.worldModel.getRelations(Relation.AGENTFAILED_FACT);

        List<WorldModelRelation> iDids = this.worldModel.getRelations(Relation.IDID_FACT);
        List<WorldModelRelation> iFaileds = this.worldModel.getRelations(Relation.IFAILED_FACT);

        checkFactsGoalsStandards(agentWantsTos, behaviourGoals, null, fakeStdGoals);

        checkFactsGoalsStandards(agentDids, behaviourGoals, standards, null);
        checkFactsGoalsStandards(iDids, behaviourGoals, standards, null);

        checkFactsGoalsStandards(agentFaileds, behaviourGoals, null, null);
        checkFactsGoalsStandards(iFaileds, behaviourGoals, null, null);

        /** @done object appraisal
         * (maybe only crete an object appraisal if some update to a pref fact exceeds threshold)
         * no: iterate over all pref facts in the world model */
        for (WorldModelRelation wmr : this.worldModel.getRelations(Relation.PREFER_FACT)) {
            //all requests: check goal relevance
            if (wmr.isNew()) { // only new ones
                float prefValue = wmr.getPreferenceValue(null);
                if (Math.abs(prefValue) >= ABS_PREFERANCE_THRESHOLD) {
                    this.register.registerObjectMatch(prefValue, wmr);
                }
            }
        }
        // advance age of all world model entries (is now done after apl generation):

        this.keepOnlyOneImpulseGoal();
    }

    private DList<Goal> makeFakeStdGoals(final List<WorldModelRelation> pStandards) {
        DList<Goal> l = new DList<Goal>();
        for (WorldModelRelation currentStandard : pStandards) {
            l.add(new FakeStandardGoal(this.intentionStructure, currentStandard));
        }
        return l;
    }

    private void checkFactsGoalsStandards(final List<WorldModelRelation> facts, final DList<Goal> goals,
                                          final List<WorldModelRelation> standards, final DList<Goal> fakeStdGoals) {
        for (WorldModelRelation wmr : facts) {
            //all Dids: check goal relevance and if true standard
            if (wmr.isNew()) { // only new ones
                appraiseRelToGoals(wmr, goals);
                if (standards != null) {
                    /** @done check standards for agentDids */
                    // check if there are norms it violates or fulfills
                    appraiseRelToStandards(wmr, standards);
                }
                if (fakeStdGoals != null) {
                    appraiseRelToGoals(wmr, fakeStdGoals);
                }
            }
        }
    }

    private void appraiseRelToStandards(final WorldModelRelation wmr, final List<WorldModelRelation> standards) {
        float standardConformance = getConformance(wmr, standards, this.log);
        if (Math.abs(standardConformance) >= ABS_CONFORMANCE_THRESHOLD) {
            this.register.registerStandardsMatch(standardConformance, wmr);
        }
    }

    /**
     * @return
     */
    static float getConformance(final WorldModelRelation wmr, final List<WorldModelRelation> standards,
                                final AgentLogger log) {
        assert (log != null) : "logger needed for output";
        float standardConformance = 0f; // initialize for maximum abs value search
        for (WorldModelRelation currentStandard : standards) {
            float tmpConformanceValue = getStandardConformance(currentStandard, wmr);
            if (tmpConformanceValue != 0f) {
                if (log.getShowAppraisal()) { log.info("standard-conformance: " + tmpConformanceValue
                                                       + " of fact " + wmr.simpleString(null)
                                                       + " to standard " + currentStandard.simpleString(null)); }
                if (Math.abs(standardConformance) < Math.abs(tmpConformanceValue)) {
                    standardConformance = tmpConformanceValue;
                }
            }
        }
        return standardConformance;
    }

    private void appraiseRelToGoals(final WorldModelRelation wmr, final DList<Goal> behaviourGoals) {
        float relevanceValue = 0f;
        for (Goal currentGoal : behaviourGoals) {
            float tmpRelevanceValue = getRelevanceValue(currentGoal, wmr);
            if (tmpRelevanceValue != 0f) {
                if (this.log.getShowAppraisal()) { this.log.info("goal-relevance: " + tmpRelevanceValue
                            + " of fact " + wmr.simpleString(null)
                            + " to goal " + currentGoal); }
                if (Math.abs(relevanceValue) < Math.abs(tmpRelevanceValue)) {
                    relevanceValue = tmpRelevanceValue;
                }
            }
        }
        if (Math.abs(relevanceValue) >= ABS_RELEVANCE_THRESHOLD) {
            this.register.registerGoalsMatch(relevanceValue, wmr);
        }
    }

    /**
     * Calculate a value for the relevance of the fact to the goal.
     * the higher the absolute value, the more relevant
     * @param goal
     * @param wmr the fact to be assessed
     * @return if goal utilities are normally below 20 then this should give a float between -1 and 1
     */
    private float getRelevanceValue(final Goal goal, final WorldModelRelation wmr) {
        if (goal.getGoalAction() == null || wmr == null) {
            return 0f;
        }
        Relation goalRel = goal.getGoalAction().getRelation();
        if (! goalRel.isBehaviour()) {
            return 0f;
        }
        Binding b = goal.getGoalBinding(); // nicht Intentionbinding
        Relation goalSubRel = goalRel.getSubRelation(b);
        Relation subRel = wmr.getSubRelation(null);
        goalSubRel = goalSubRel.variableQuestionMarks(new SymbolMap());
        if (Relation.unify(goalSubRel, null, subRel, null)) {
            // check if conducive or obstructive:
            int goalSucVal = goalRel.getSuccessValue(b);
            // rethinking note: why did i think my own action relevant actions are not as
            // important for appraisal ????????? commented it out:
            /*// only half as important if the actor is me (i expect what i do)
            float factor = 1f;
            if (this.name.equals(wmr.getResponsible())) {
                factor = 0.5f;
            }*/
            // both of the following see Moffat: Personality Parameters and Programs
            /** @done consider the importance of the concern, i.e. the goals utility
             * adding 5 is done for getting initially higher values than the originating utils */
            float factor = Math.min((goal.getTopLevelIntendedUtility() / Appraisal.UTILITY_FACTOR)
                                    + UTILITY_CORRECTION, 1f);
            /* maybe also the degree of control, i.e. is there already a intention for the goal ? maybe not*/
            if (wmr.isAgentWants() || wmr.isIWant()) {
                return factor * goalSucVal; // its a request, so just return if i want it
            }
            return factor * wmr.getSuccessValue(null) * goalSucVal;
            // -1 if i want the other thing, 1 if i want the same (0 otherwise)
        }
        return 0f;
    }

    /**
     * Compare a fact against a standard and return a value for the conformance to the standard.
     * a success is worst/best, followed by attempting and failing at the action
     * @param standard the fact representing the standard (including a moral value -1..1)
     * @param wmr the action fact
     * @return a float between -1 and 1
     */
    private static float getStandardConformance(final WorldModelRelation standard, final WorldModelRelation wmr) {
        if (standard == null || wmr == null) {
            return 0f;
        }
        Relation standardSubRel = standard.getSubRelation(null);
        standardSubRel = standardSubRel.variableQuestionMarks(new SymbolMap());
        Relation subRel = wmr.getSubRelation(null);
        if (Relation.unify(standardSubRel, null, subRel, null)) {
            // get the 'moral' value of the standard (good or bad is in the sign):
            // these should be above the STANDARD_THRESHOLD but below 1
            float standardVal = standard.getStandardValue(null);
            float standardSign = (standardVal > 0) ? 1f : -1f;
            standardVal = Math.abs(standardVal);
            int sucValue = wmr.getSuccessValue(null);
            float sucFactor = (sucValue == 1) ? 1f : FAILED_ATTEMPT_FACTOR; // failed attempts are not so important
            return standardSign * Math.min((standardVal * sucFactor) + UTILITY_CORRECTION, 1f); // didnt succeed
        }
        return 0f;
    }

    /** Eliminate all but the highest utility showImpulse Goal. */
    private void keepOnlyOneImpulseGoal() {
        // drop all impulse goals with lower utility:
        // this synchronized use is very bad style, but elegantly avoiding it would mean a major rehaul
        // (making the intentionstructure aware of stuff like showImpulse goals...)
        // so i just go ahead and do an ugly hack
        synchronized (this.intentionStructure) {
            // needs a listiterator for .remove()
            ListIterator<Goal> stacks = this.intentionStructure.getSortedToplevelGoals().listIterator();
            Goal stackGoal; // go through all toplevel ShowImpulse goals
            GoalAction ga = null;
            boolean highestFound = false;
            while (stacks.hasNext()) {
                stackGoal = stacks.next();
                ga = stackGoal.getGoalAction();
                if (ga != null && ga.getRelation().getName().equals(Appraisal.IMPULSE_GOAL_STRING)) {
                    if (highestFound) {
                        stackGoal.setStatus(Goal.Status.ABANDONED);
                        stackGoal.setSubgoal(null); // remove any subgoal
                        stacks.remove(); // and remove
                    } else { // do nothing, keep it
                        highestFound = true;
                    }
                }
            }
        }
    }

    // maybe this loose lexical matching as a sieve for real precondition/effects matching
    // is unnecessary...
/*
    private float getMatchValue(Goal goal, WorldModelRelation wmr) {
        if (goal.getGoalAction() == null || wmr == null) {
            return 0f;
        }
        Relation goalRel = goal.getGoalAction().getRelation();
        ExpList goalList = goalRel.getArgs().explistEval(goal.getGoalBinding());
        goalList.addFirst(Value.newValue(goalRel.getName()));
        ExpList wmrList = wmr.getArgs().explistEval(null);
        float result = calculateMatchValue(goalList, wmrList);
        return result;
    }


    private float calculateMatchValue(ExpList a, ExpList b) {
        float result = 0f;
        for (int aIter = 1; aIter <= a.size(); ++aIter) { // getNth is 1-based
            Value aVal = (Value) a.getNth(aIter);
            for (int bIter = 1; bIter <= b.size(); ++bIter) {
                Value bVal = (Value) b.getNth(bIter);
                if (aVal.isDefined() && bVal.isDefined()) {
                    if (bVal.eq(aVal)) {
                        if (aIter == bIter) {
                            result += 2f;
                        } else {
                            result += 1f;
                        }
                    }
                } else { // one is a variable (or strangely undefined)
                    if (aIter == bIter) {
                        result += 2f;
                    }
                }
            }
        }
        return result;
    }
*/

}
