package at.oefai.aaa.agent;


import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import at.oefai.aaa.agent.jam.Animator;
import at.oefai.aaa.agent.jam.Relation;
import at.oefai.aaa.agent.jam.WorldModelTableEvent;
import at.oefai.aaa.agent.jam.WorldModelTableEventListener;
import at.oefai.aaa.agent.jam.types.Binding;
import at.oefai.aaa.agent.jam.types.ExpList;
import at.oefai.aaa.agent.jam.types.SymbolMap;
import at.oefai.aaa.agent.jam.types.Value;
import at.oefai.aaa.agent.jam.ParseException;


/**
 * a StoppableInterpreter that only reacts to externally asserted Relations
 * (using CONCLUDE plans)
 * and that offers a list of changed facts for every interested listener
 * @author Stefan Rank
 */
public class EnvironmentInterpreter extends ModelInterpreter
        implements WorldModelTableEventListener, Animator {
    private final Animator animator;


    /**
     * Members for remembering worldmodel changes per querying agent
     */
    Map<String,List<WorldModelTableEvent>> agentLists =
        Collections.synchronizedMap(new HashMap<String,List<WorldModelTableEvent>>(3));

    public EnvironmentInterpreter(String name, String[] argv, Animator anim) throws ParseException, IOException {
        super(name, argv);
        assert anim != null;
        this.animator = anim;
        //log.setLevel(Level.SEVERE);
        getWorldModel().addWorldModelTableEventListener(this);
        // no metalevel reasoning or appraising necessary here
        this.setAssertsAPLs(false);
        this.setAppraises(false);
        //this.getLog().setShowWorldModel(true);

    }


    public void assertObject(final String object) {
        this.simpleAssert("IsObject", new String[] {object});
    }

    public void assertAgent(final String agent) {
        this.simpleAssert("IsAgent", new String[] {agent});
    }


    public Animator.ITask animate(final String actor, final String actionName, final List<Value> arguments) {
        return this.animator.animate(actor, actionName, arguments);
    }


    public WorldModelTableEvent[] getPerceptionsOf(final String agentName) {
        List<WorldModelTableEvent> l = this.agentLists.get(agentName);
        if (l == null) { // a new agent that wants to perceive
            List<WorldModelTableEvent> newList =
                Collections.synchronizedList(new LinkedList<WorldModelTableEvent>()); // synchronized !!!
            this.agentLists.put(agentName,newList);
            // now get an array with ASSERTs for all relations in the worldmodeltable
            return this.getWorldModel().getAllCurrent();
        }
        if (l.size() == 0) return null;
        WorldModelTableEvent[] wmtes = null;
        synchronized (l) { // prevent change of the list between size() and clear()
            wmtes = new WorldModelTableEvent[l.size()];
            wmtes = l.toArray(wmtes);
            l.clear();
        }
        return wmtes;
    }

    public void worldModelTableChanged(WorldModelTableEvent wmte) {
        for (List<WorldModelTableEvent> list : this.agentLists.values()) {
            list.add(wmte);
        }
    }

    /**
     * this one is called by an observer of the environment to request an action on its behalf,
     * @param actor
     * @param actionName
     * @param arguments
     * @return always true for now
     */
    public boolean doAction(final String actor, final String actionName, final List<Value> arguments) {
         /* different implementation approaches (last one used):
          * - search a plan, if found execute, careful synchronization
          * - on second thought post a perform goal
          *   somehow use a goalperformed listener for: careful synchronization
          * - on third thought post a actionRequested FACT for a conclude plan
          *   use the perceptions for synchronization and actionSuccess facts
          */
         // first retract the last actionsuccess:
         ExpList elSuc = listToExpList(arguments);
         elSuc.addFirst(Value.newValue(actor)); // second argument is agent requesting the action
         elSuc.addFirst(Value.newValue(actionName)); // first argument is the action
         SymbolMap tmp = new SymbolMap();
         elSuc.addLast(tmp.getVariable("dontCareVariableTrueOrFalse")); // last one as variable
         Relation successRel = Relation.newSuccessRelation(elSuc);
         getWorldModel().retractRelation(successRel,new Binding(tmp));
         // then update an actionRequest: (update..old requests of same actor for same action disappear)
         ExpList elReq = listToExpList(arguments);
         elReq.addFirst(Value.newValue(actor)); // second argument is agent requesting the action
         elReq.addFirst(Value.newValue(actionName)); // first argument is the action
         Relation newRel = Relation.newRequestRelation(elReq);
         ExpList elOld = new ExpList();
         elOld.addFirst(Value.newValue(actor)); // second argument is agent requesting the action
         elOld.addFirst(Value.newValue(actionName)); // first argument is the action
         Relation oldRel = Relation.newRequestRelation(elOld);
         //getWorldModel().assertRelation(newRel,null);
         getWorldModel().update(oldRel, newRel, null);
         /** @done how to wait for the success or failure of the goal ?
          *  done: is done by using wait in the plan itself */
         return true;
     }

    private ExpList listToExpList(final List<Value> theList) {
        ExpList el = new ExpList();
        for (Value v : theList) {
            el.addLast(v);
        }
        return el;
    }

}
