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

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import com.google.inject.Inject;

import cz.cuni.amis.pogamut.base.agent.worldview.BatchAwareWorldView;
import cz.cuni.amis.pogamut.base.agent.worldview.EventDrivenWorldView;
import cz.cuni.amis.pogamut.base.agent.worldview.ILockableWorldView;
import cz.cuni.amis.pogamut.base.communication.mediator.IMediator;
import cz.cuni.amis.pogamut.base.communication.translator.IWorldChangeEvent;
import cz.cuni.amis.pogamut.base.factory.guice.AgentScoped;
import cz.cuni.amis.pogamut.base.utils.logging.AgentLogger;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BeginMessage;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.EndMessage;

/**
 * Lockable word view.
 * <p><p>
 * Contains GameBots2004 correct locking of the worldview.
 * <p><p>
 * All messages are processed always in batches (all messages between EndMessages are one batch) meaning that the world view is always 
 * correct!
 * <p><p>
 * When worldview is lock()ed it postpones the events until unlock()ed, which is triggering
 * raising all events that came from the lock().
 * <p><p>
 * The world view is unlocked from the beginning.
 * <p><p>
 * All those locking mechanisms start working when the first BEGIN message comes. 
 * 
 * @author Jimmy
 */
@AgentScoped
public class UT2004LockableWorldView extends DefaultUT2004WorldView implements ILockableWorldView {
	
	/**
	 * Here we store batches that are complete (ends with the EndMessage).
	 */
	private Queue<List<IWorldChangeEvent>> batches = new LinkedList<List<IWorldChangeEvent>>();
	
	/**
	 * Here we store new events that are coming from the Mediator.
	 */
	private List<IWorldChangeEvent> currentBatch = new ArrayList<IWorldChangeEvent>();
	
	/**
	 * Whether the world view is locked.
	 */
	private boolean locked = false;
	
	/**
	 * First BEG message 
	 */
	private boolean beginCame = false;
	
	/**
	 * Synchronization mutex for this class.
	 */
	private final Object objectMutex = new Object();

	
	@Inject
	public UT2004LockableWorldView(IMediator messageSource, AgentLogger log) {
		super(messageSource, log);		
	}
	
	/**
	 * When the world view is locked - no batches are processes until unlocked.
	 */
	public void lock() {
		synchronized(objectMutex) {
			if (isLocked()) return;
			locked = true;
			log.finer("World view locked.");
		}
	}
	
	/**
	 * Unlocks the world view - triggers processing of all events till the last EndMessage that
	 * came between lock() / unlock() calls.
	 */
	public void unlock() {
		synchronized(objectMutex) {
			if (!isLocked()) return;
			log.finer("World view is being unlocked.");
			locked = false;
			for (List<IWorldChangeEvent> batch : batches) {
				processBatch(batch);
			}
			batches.clear();			
			log.finer("World view unlocked.");
		}
	}
	
	public boolean isLocked() {
		return locked;
	}
	
	/**
	 * Does super.notifyEvent(event) for each event in the batch. 
	 * <p><p>
	 * <b>Unsync!</b>
	 * @param batch
	 */
	private void processBatch(List<IWorldChangeEvent> batch) {
		for (IWorldChangeEvent event : batch) {
			super.notify(event);
		}		
	}
	
	/**
	 * Implements locking logic.
	 */
    @Override
	public void notify(IWorldChangeEvent event) {
		synchronized(objectMutex) {
			if (!beginCame) {
				if (event instanceof BeginMessage) {
					beginCame = true;
				} else {
					super.notify(event);
					return;
				}
			}
			if (isLocked()) {
				if (event instanceof EndMessage) {
					currentBatch.add(event);
					batches.add(currentBatch);
					currentBatch = new ArrayList<IWorldChangeEvent>(currentBatch.size()+20);
				} else {
					currentBatch.add(event);				
				}
			} else {
				if (event instanceof EndMessage) {
					currentBatch.add(event);
					processBatch(currentBatch);
					currentBatch.clear();
				} else {
					currentBatch.add(event);
				}
			}
		}
	}


}
