package mining;

import jason.environment.grid.GridWorldModel;
import jason.environment.grid.Location;

import java.util.Random;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

import mining.MiningPlanet.Move;

public class WorldModel extends GridWorldModel {

    public static final int   GOLD  = 16;
    public static final int   DEPOT = 32;
    public static final int   ENEMY = 64;

    Location                  depot;
    Set<Integer>              agWithGold;  // which agent is carrying gold
    int                       goldsInDepot   = 0;
    int                       initialNbGolds = 0;

    private Logger            logger   = Logger.getLogger("jasonTeamSimLocal.mas2j." + WorldModel.class.getName());

    private String            id = "WorldModel";
    
	public static Random random = new Random(System.currentTimeMillis());
	int	nbEndOfSimulation = 0;  // Nb. EndOfSim received

    // singleton pattern
    protected static WorldModel model = null;
    
    synchronized public static WorldModel create(int w, int h, int nbAgs) {
        if (model == null) {
            model = new WorldModel(w, h, nbAgs);
        }
        return model;
    }
    
    public static WorldModel get() {
        return model;
    }
    
    public static void destroy() {
        model = null;
    }

    private WorldModel(int w, int h, int nbAgs) {
        super(w, h, nbAgs);
        agWithGold = new HashSet<Integer>();
    }

	/** Sets the number endOfSimulation received */
    public int setEndOfSimulation(int nb)
	{
		nbEndOfSimulation = nb;
        return nbEndOfSimulation;
    }

	/** Gets the number endOfSimulation received */
    public int getEndOfSimulation()
	{
        return nbEndOfSimulation;
    }

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String toString() {
        return id;
    }
    
    public Location getDepot() {
        return depot;
    }

    public int getGoldsInDepot() {
        return goldsInDepot;
    }
    
    public boolean isAllGoldsCollected() {
        return goldsInDepot == initialNbGolds;
    }
    
    public void setInitialNbGolds(int i) {
        initialNbGolds = i;
    }
    
    public int getInitialNbGolds() {
        return initialNbGolds;
    }

    public boolean isCarryingGold(int ag) {
        return agWithGold.contains(ag);
    }

    public void setDepot(int x, int y) {
        depot = new Location(x, y);
        data[x][y] = DEPOT;
    }

    public void setAgCarryingGold(int ag) {
        agWithGold.add(ag);
    }
    public void setAgNotCarryingGold(int ag) {
        agWithGold.remove(ag);
    }

    /** Actions **/
    boolean move(Move dir, int ag) throws Exception {
        Location l = getAgPos(ag);
        switch (dir) {
        case UP:
            if (inGrid(l.x, l.y - 1)) {
                setAgPos(ag, l.x, l.y - 1);
            }
            break;
        case DOWN:
            if (inGrid(l.x, l.y + 1)) {
                setAgPos(ag, l.x, l.y + 1);
            }
            break;
        case RIGHT:
			if (inGrid(l.x + 1, l.y)) {
                setAgPos(ag, l.x + 1, l.y);
            }
            break;
        case LEFT:
			if (inGrid(l.x - 1, l.y)) {
                setAgPos(ag, l.x - 1, l.y);
            }
            break;
        }
        return true;
    }

    boolean pick(int ag) {
        Location l = getAgPos(ag);
        if (hasObject(WorldModel.GOLD, l.x, l.y)) {
            if (!isCarryingGold(ag)) {
                remove(WorldModel.GOLD, l.x, l.y);
                setAgCarryingGold(ag);
                return true;
            } else {
                logger.warning("Agent " + (ag + 1) + " is trying the pick gold, but it is already carrying gold!");
            }
        } else {
            logger.warning("Agent " + (ag + 1) + " is trying the pick gold, but there is no gold at " + l.x + "x" + l.y + "!");
        }
        return false;
    }

    synchronized boolean drop(int ag) {
        Location l = getAgPos(ag);
        if (isCarryingGold(ag)) {
            if (l.equals(getDepot())) {
                goldsInDepot++;
                logger.info("Agent " + (ag + 1) + " carried a gold to depot!");
            } else {
                add(WorldModel.GOLD, l.x, l.y);
            }
            setAgNotCarryingGold(ag);
            return true;
        }
        return false;
    }

    /** world with gold  */
	static WorldModel world(int worldSize, float goldLoad, int nbAgents) throws Exception {
        WorldModel model = WorldModel.create(worldSize, worldSize, nbAgents);
        model.setId("Structured Gold Miners");
        model.setDepot(worldSize/2, worldSize/2);
		
		int x = 0, y = 0;
		int nbGolds = (int)( (float)worldSize * (float)worldSize * goldLoad);
		for (int i = 0; i < nbGolds; i++)
		{
			x = random.nextInt(worldSize/2);
			y = random.nextInt(worldSize/2);
			if (!model.hasObject(WorldModel.GOLD, x, y))
				model.add(WorldModel.GOLD, x, y);
			else
				i--;
		}

		for (int i = 0; i < nbAgents; i++)
		{
			switch(i % 4)
			{
				case 0:
				x = random.nextInt(worldSize/2);
				y = random.nextInt(worldSize/2);
				break;

				case 1:
				x = worldSize/2 + random.nextInt(worldSize/2);
				y = random.nextInt(worldSize/2);
				break;

				case 2:
				x = random.nextInt(worldSize/2);
				y = worldSize/2 + random.nextInt(worldSize/2);
				break;

				case 3:
				x = worldSize/2 + random.nextInt(worldSize/2);
				y = worldSize/2 + random.nextInt(worldSize/2);
				break;
			}
			if (!model.hasObject(WorldModel.GOLD, x, y))
				model.setAgPos(i, x, y);
			else
				i--;
		}
				
        model.setInitialNbGolds(model.countObjects(WorldModel.GOLD));
        return model;
    }

}
