package cz.cuni.amis.pogamut.ut2004.communication.translator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import cz.cuni.amis.pogamut.base.communication.translator.IWorldChangeEvent;
import cz.cuni.amis.pogamut.ut2004.communication.messages.UnrealId;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Item;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPointNeighbourLink;
import cz.cuni.amis.pogamut.ut2004.communication.translator.events.MapPointListObtained;
import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.ItemTranslator;

/**
 * Translator context serves as the context during the FSM work. It provides respective fsm states an access
 * to the instances of:
 * <ul>
 * <li>event queue - object where we're sending new world events</li>
 * <li>item translator - an object with factories of respective items</li>
 * <li>log</li>
 * </ul>
 * 
 * @author Jimmy
 */
public class TranslatorContext {
    
    private IWorldEventQueue events;
    private ItemTranslator itemTranslator;
    private Logger log;
	private List<NavPointNeighbourLink> neighbours = null;
	private Map<UnrealId, NavPoint> navPoints = null;
	private Map<UnrealId, Item> items;
	private Map<UnrealId, List<NavPointNeighbourLink>> links;
    
    public TranslatorContext(IWorldEventQueue events, ItemTranslator itemTranslator, Logger log) {
        this.events = events;
        this.itemTranslator = itemTranslator;
        this.log = log;
    }
    
    public IWorldEventQueue getEventQueue() {
        return events;
    }
    
    public ItemTranslator getItemTranslator() {
        return itemTranslator;
    }
    
    public Logger getLogger() {
    	return log;
    }
    
    public List<NavPointNeighbourLink> getNeighbours() {
    	return neighbours;
    }
    
    public void setNeighbours(List<NavPointNeighbourLink> neighs) {
    	this.neighbours  = neighs;
    }
    
    public void setNavPointLinks(Map<UnrealId, List<NavPointNeighbourLink>> links) {
    	this.links = links;
    }
    
    public Map<UnrealId, List<NavPointNeighbourLink>> getNavPointLinks() {
		return links;
	}

	public void setNavPoints(Map<UnrealId, NavPoint> navPoints) {
    	this.navPoints = navPoints;
    }
    
    public Map<UnrealId, NavPoint> getNavPoints() {
    	return navPoints ;
    }

	public void setItems(Map<UnrealId, Item> items) {
		this.items = items;		
	}

	public Map<UnrealId, Item> getItems() {
		return items;
	}
	
	/**
	 * Reads getNavPointsLinks() and alters navpoints incoming and outgoing edges.
	 * <p><p>
	 * Does nothing if getNavPoints() or getNavPointsLinks() returns null.
	 */
	public void processNavPointLinks() {
		if (getNavPoints() == null || getNavPointLinks() == null) {
			return;
		}
		
		getLogger().fine("Processing NavPoints<->Links.");
		for (NavPoint navPoint : getNavPoints().values()) {
			navPoint.getIncomingEdges().clear();
			navPoint.getOutgoingEdges().clear();
		}
		for(NavPoint navPoint : getNavPoints().values()) {
			List<NavPointNeighbourLink> links = getNavPointLinks().get(navPoint.getId());
			List<NavPointNeighbourLink> fixedLinks = new ArrayList<NavPointNeighbourLink>(links.size());
			for (NavPointNeighbourLink link : links) {
				NavPoint targetNavPoint = navPoints.get(link.getId());
				NavPointNeighbourLink fixedLink = new NavPointNeighbourLink(link, navPoint, targetNavPoint );
				fixedLinks.add(fixedLink);
				navPoint.getOutgoingEdges().put(fixedLink.getId(), fixedLink);
				targetNavPoint.getIncomingEdges().put(navPoint.getId(), fixedLink);
			}
			getNavPointLinks().put(navPoint.getId(), fixedLinks);
		}
		getLogger().fine("Processing finished.");
	}
	
	/**
	 * Interconnects instances of NavPoint and Item from getNavPoints() and getItems() map.
	 * <p><p>
	 * Note that new instances of nav points are created during this process thus
	 * the getNavPoints() will return a new map after this method finishes.
	 * <p><p>
	 * Does nothing if getNavPoints() or getItems() returns null.
	 */
	public void processNavPointsAndItems() {
		if (getItems() == null || getNavPoints() == null) {
			return;
		}
		
		getLogger().fine("Processing NavPoints<->Items.");
		
		Map<UnrealId, Item> items = getItems();
		Map<UnrealId, NavPoint> origNavPoints = getNavPoints();
		Map<UnrealId, NavPoint> navPoints = new HashMap<UnrealId, NavPoint>();
		
		for (NavPoint navPoint : origNavPoints.values()) {
			if (navPoint.getItem() != null) {
				Item item = items.get(navPoint.getItem());
				NavPoint newNavPoint = new NavPoint(navPoint, item);
				item.setNavPoint(newNavPoint);
				navPoints.put(navPoint.getId(), newNavPoint);
			} else {
				navPoints.put(navPoint.getId(), navPoint);
			}			
		}
		
		setNavPoints(navPoints);
		
		getLogger().fine("Processing finished.");
	}
	
}
