package com.mycompany.collectorbot;

import cz.cuni.amis.introspection.Property;
import java.util.Collection;
import java.util.logging.Level;

import cz.cuni.amis.pogamut.base.agent.navigation.IPathExecutorState;
import cz.cuni.amis.pogamut.base.agent.navigation.IPathFuture;
import cz.cuni.amis.pogamut.base.agent.navigation.PathExecutorState;
import cz.cuni.amis.pogamut.base.communication.worldview.listener.annotation.EventListener;
import cz.cuni.amis.pogamut.base.utils.guice.AgentScoped;
import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
import cz.cuni.amis.pogamut.ut2004.agent.module.utils.TabooSet;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.UT2004Navigation;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.UT2004PathAutoFixer;
import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004BotModuleController;
import cz.cuni.amis.pogamut.ut2004.communication.messages.ItemType;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Initialize;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.*;
import cz.cuni.amis.pogamut.ut2004.communication.messages.ItemType.*;
import cz.cuni.amis.pogamut.ut2004.utils.UT2004BotRunner;
import cz.cuni.amis.utils.Cooldown;
import cz.cuni.amis.utils.collections.MyCollections;
import cz.cuni.amis.utils.exception.PogamutException;
import cz.cuni.amis.utils.flag.FlagListener;
import java.util.Map;
import java.util.Set;

/**
 * Example of Simple Pogamut bot, that randomly walks around the map.
 * <p><p>
 * Bot is able to handle movers as well as teleporters.
 * <p><p>
 * It also implements player-following, that is, if it sees a player, it will start
 * to navigate to it.
 *
 * @author Rudolf Kadlec aka ik
 * @author Jakub Gemrot aka Jimmy
 */
@AgentScoped
public class CollectorBot extends UT2004BotModuleController {    
        
        /**
         * Items that the bot cannot pick up due to various reasons (full health etc.)
         */
        protected TabooSet<Item> tabooItems;
	
        /**
         * Cooldown that ensures that once all items are collected, the bot waits for some time before trying again.
         */
        protected Cooldown waitForItems;
        
	/**
	 * The closest navigation point to the bot.
	 */
	protected NavPoint currentNavPoint;
        
        /**
	 * Item we are navigating to
	 */
	protected Item targetItem;

	/**
	 * Path auto fixer watches for navigation failures and if some navigation link is found to be
	 * unwalkable, it removes it from underlying navigation graph.
	 * 
	 * Note that UT2004 navigation graphs are some times VERY stupid or contains VERY HARD TO FOLLOW links...
	 */
	protected UT2004PathAutoFixer autoFixer;
	
    /**
     * Here we can modify initializing command for our bot.
     *
     * @return
     */
    @Override
    public Initialize getInitializeCommand() {
        return new Initialize().setName("NavigationBot");
    }

    /**
     * The bot is initialized in the environment - a physical representation of the bot is present in the game.
     *
     * @param config information about configuration
     * @param init information about configuration
     */
    @SuppressWarnings("unchecked")
	@Override
    public void botInitialized(GameInfo gameInfo, ConfigChange config, InitedMessage init) {
        // Initialize the set of uncollectable items
        tabooItems = new TabooSet<Item>(bot);

    	// auto-removes wrong navigation links between navpoints
        autoFixer = new UT2004PathAutoFixer(bot, pathExecutor, fwMap, navBuilder);        
        
        // IMPORTANT
        // adds a listener to the path executor for its state changes, it will allow you to 
        // react on stuff like "PATH TARGET REACHED" or "BOT STUCK"
        pathExecutor.getState().addStrongListener(new FlagListener<IPathExecutorState>() {
			@Override
			public void flagChanged(IPathExecutorState changedValue) {
				pathExecutorStateChange(changedValue.getState());
			}
		});
        
        waitForItems = new Cooldown(3000);
        waitForItems.clear();
    }
    
   /**
     * The bot is initilized in the environment - a physical representation of the
     * bot is present in the game.
     *
     * @param config information about configuration
     * @param init information about configuration
     */
    @Override
    public void botFirstSpawn(GameInfo gameInfo, ConfigChange config, InitedMessage init, Self self) {
        // receive logs from the navigation so you can get a grasp on how it is working
        pathExecutor.getLog().setLevel(Level.ALL);
    }
    
    /**
     * This method is called only once right before actual logic() method is called for the first time.
     */
    @Override
    public void beforeFirstLogic() {
    }

    /**
     * If we pick up an item, tabooize it
     */
    @EventListener(eventClass = ItemPickedUp.class)
    protected void pickedUpItem(ItemPickedUp event) {
        // If adreanaline is full, start combo, so that we can collect more
        tabooItems.add(items.getItem(event.getId()), 60);
    }
    
    /**
     * Main method that controls the bot - makes decisions what to do next.
     * It is called iteratively by Pogamut engine every time a synchronous batch
     * from the environment is received. This is usually 4 times per second - it
     * is affected by visionTime variable, that can be adjusted in GameBots ini file in
     * UT2004/System folder.
     */
    @Override
    public void logic() {
        if (!navigation.isNavigating() && waitForItems.tryUse()) {
            collectNextItem();
        }

        return;
    }

    private void collectNextItem() {
        currentNavPoint = getNavigation().getNearestNavPoint(bot);
        
        Item toCollect = getNearestPickableItem(currentNavPoint);
        if (toCollect == null) return;
        
        IPathFuture<NavPoint> path = getPathPlanner().computePath(bot, toCollect);
        getPathExecutor().followPath(path);
        
        tabooItems.add(toCollect, 180);  // Tabooize item automatically. We would do so anyway
        targetItem = toCollect;
    }
        
    private Item getNearestPickableItem(NavPoint whereFrom) {
        Map<UnrealId, Item> allItems = items.getAllItems();
        Set<Item> spawnedItems = tabooItems.filter(allItems.values());

        
        if (spawnedItems.isEmpty()) {
            body.getCommunication().sendGlobalTextMessage("Damn, nothing to collect, what now!?");
            waitForItems.use();  // Don't try again in some time
            return null;
        }
        
        float closestDist = Float.MAX_VALUE;
        Item closestItem = null;
        for (Item it: spawnedItems) {
         /*   if (!pickupHealth && isHealth(it)) continue;
            if (!pickupArmor && isArmor(it)) continue;*/
            
            if (fwMap.getDistance(whereFrom, it.getNavPoint()) < closestDist) {
                closestDist = fwMap.getDistance(whereFrom, it.getNavPoint());
                closestItem = it;
            }
        }
        
        return closestItem;
    }
        
    /**
     * Called each time our bot die. Good for reseting all bot state dependent variables.
     *
     * @param event
     */
    @Override
    public void botKilled(BotKilled event) {
        navigation.stopNavigation();        
        currentNavPoint = null;
        tabooItems.clear();  // Clear the taboo set of items because now the bot can collect
                             // e.g. health again.
    }
    
    /**
     * Path executor has changed its state (note that {@link UT2004BotModuleController#getPathExecutor()} is internally used by
     * {@link UT2004BotModuleController#getNavigation()} as well!).
     * 
     * @param state
     */
    protected void pathExecutorStateChange(PathExecutorState state) {
    	switch(state) {
		case PATH_COMPUTATION_FAILED:
			// if path computation fails to whatever reason, just try another navpoint
                        collectNextItem();
            break;                    
			
		case TARGET_REACHED:
                        collectNextItem();
            break;                    
        
        case STUCK:
        	// the bot has stuck! ... target nav point is unavailable currently
        	tabooItems.add(targetItem, 60);                    
            break;
            
        case STOPPED:
        	// path execution has stopped
        	targetItem = null;
        	break;
	}
    }

    public static void main(String args[]) throws PogamutException {
    	// wrapped logic for bots executions, suitable to run single bot in single JVM
    	
    	// we're forcingly setting logging to aggressive level FINER so you can see (almost) all logs 
    	// that describes decision making behind movement of the bot as well as incoming environment events
    	new UT2004BotRunner(CollectorBot.class, "CollectorBot").setMain(true).setLogLevel(Level.FINER).startAgent();
    }
}
