package cz.cuni.amis.pogamut.base.agent.worldview;

import cz.cuni.amis.pogamut.base.agent.worldview.objects.IWorldObject;
import cz.cuni.amis.pogamut.base.agent.worldview.objects.WorldObjectAppearedEvent;
import cz.cuni.amis.pogamut.base.agent.worldview.objects.WorldObjectDisappearedEvent;
import cz.cuni.amis.pogamut.base.agent.worldview.objects.WorldObjectUpdatedEvent;
import cz.cuni.amis.pogamut.base.communication.mediator.IMediator;
import cz.cuni.amis.pogamut.base.communication.translator.IWorldChangeEvent;
import cz.cuni.amis.pogamut.base.utils.logging.AgentLogger;
import cz.cuni.amis.pogamut.base3d.worldview.objects.IViewable;
import java.util.HashSet;
import java.util.Set;

/**
 * World view that is updated by protocol utilizing concept of batches. Each batch
 * is separated by some message. After receiving this message additional events
 * may be raised (eg. visibility update etc.).
 * @author ik
 */
public abstract class BatchAwareWorldView extends EventDrivenWorldView {

    Set<IWorldObject> lastBatch = new HashSet<IWorldObject>();
    Set<IWorldObject> currentBatch = new HashSet<IWorldObject>();

    public BatchAwareWorldView(IMediator messageSource, AgentLogger agentLogger) {
        super(messageSource, agentLogger);
    }

    /**
     * Is this event a batch end event? If so some extra events may be generated
     * in processing this message.
     * @param evt
     * @return true if this is a batch ending event
     */
    protected abstract boolean isBatchEndEvent(IWorldChangeEvent evt);

    /**
     * Could this object change event if it was visible in two successive batches?
     * If no then update event is generated only when the object appears/disappears from the FOV.
     * @param obj
     * @return
     */
    protected abstract boolean isMutable(IViewable obj);

    /**
     * Sets the disappeared flag to true on IViewable objects.
     * @param obj Object that disappeared
     */
    protected abstract void setDisappearedFlag(IWorldObject obj);

    @Override
    public synchronized void notify(IWorldChangeEvent event) {
        if (isBatchEndEvent(event)) {
            // handle disappeared objects
            lastBatch.removeAll(currentBatch);
            for (IWorldObject obj : lastBatch) {
                // generate disappeared event and set the visibility flag
                setDisappearedFlag(obj);
                // first generate update event
                raiseEvent(new WorldObjectUpdatedEvent(obj));
                // IMPORTANT:               
                // then generate disappear event ... usually appeared/disappeared events will 
                // wrap the update event processing (e.g. like html tags)                                               
                raiseEvent(new WorldObjectDisappearedEvent((IViewable)obj));
            }            
            // exchange the two sets and clear current batch
            Set<IWorldObject> swp = lastBatch;
            lastBatch = currentBatch;
            currentBatch = swp;
            currentBatch.clear();
        }
        super.notify(event);
    }

    @Override
    protected void objectUpdated(IWorldObject obj) {
        if (obj instanceof IViewable) {
            IViewable viewable = (IViewable) obj;
            currentBatch.add(obj);
            if (!lastBatch.contains(obj)) {
                // object appeared in the FOV, generate appeared event
                raiseEvent(new WorldObjectAppearedEvent(viewable));
                // and default updated event
                super.objectUpdated(obj);
                return;
            } else {
                // the object has been present in last batch
                if (!isMutable(viewable)) {
                    return; 
                    // update event won't be generated now, it will be generated
                    // when the object appears/disappears from the FOV
                }
            }
        }
        super.objectUpdated(obj);
    }
}
