package com.aionemu.gameserver.world;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import com.aionemu.gameserver.instance.handlers.GeneralInstanceHandler;
import com.aionemu.gameserver.model.templates.world.WorldMapTemplate;
import com.aionemu.gameserver.world.zone.ZoneAttributes;

/**
 * This object is representing one in-game map and can have instances.
 * 
 * @author -Nemesiss-
 */
public class WorldMap implements Iterable<WorldMapInstance> {

	private WorldMapTemplate worldMapTemplate;
	private AtomicInteger nextInstanceId = new AtomicInteger(0);
	private Map<Integer, WorldMapInstance> instances = new ConcurrentHashMap<>();
	private int worldOptions;

	public WorldMap(WorldMapTemplate worldMapTemplate) {
		this.worldMapTemplate = worldMapTemplate;
		this.worldOptions = worldMapTemplate.getFlags();

		for (int i = 1; i <= getInstanceCount(); i++) {
			if (isInstanceType()) // default instances are inaccessible but its handler methods are sometimes called via MainWorldMapInstance, e.g. on relog
				WorldMapInstanceFactory.createWorldMapInstance(this, 0, GeneralInstanceHandler::new, 0);
			else
				WorldMapInstanceFactory.createWorldMapInstance(this, 0);
		}
	}

	public String getName() {
		return worldMapTemplate.getName();
	}

	public int getWaterLevel() {
		return worldMapTemplate.getWaterLevel();
	}

	public int getDeathLevel() {
		return worldMapTemplate.getDeathLevel();
	}

	public WorldType getWorldType() {
		return worldMapTemplate.getWorldType();
	}

	public int getWorldSize() {
		return worldMapTemplate.getWorldSize();
	}

	public WorldDropType getWorldDropType() {
		return worldMapTemplate.getWorldDropType();
	}

	public int getMapId() {
		return worldMapTemplate.getMapId();
	}

	public boolean isFlightAllowed() {
		return (worldOptions & ZoneAttributes.FLY.getId()) != 0;
	}

	public boolean isExceptBuff() {
		return worldMapTemplate.isExceptBuff();
	}

	public boolean canGlide() {
		return (worldOptions & ZoneAttributes.GLIDE.getId()) != 0;
	}

	public boolean canPutKisk() {
		return (worldOptions & ZoneAttributes.BIND.getId()) != 0;
	}

	public boolean canRecall() {
		return (worldOptions & ZoneAttributes.RECALL.getId()) != 0;
	}

	public boolean canRide() {
		return (worldOptions & ZoneAttributes.RIDE.getId()) != 0;
	}

	public boolean canFlyRide() {
		return (worldOptions & ZoneAttributes.FLY_RIDE.getId()) != 0;
	}

	public boolean isPvpAllowed() {
		return (worldOptions & ZoneAttributes.PVP_ENABLED.getId()) != 0;
	}

	public boolean isSameRaceDuelsAllowed() {
		return (worldOptions & ZoneAttributes.DUEL_SAME_RACE_ENABLED.getId()) != 0;
	}

	public boolean isOtherRaceDuelsAllowed() {
		return (worldOptions & ZoneAttributes.DUEL_OTHER_RACE_ENABLED.getId()) != 0;
	}

	public boolean canReturnToBattle() {
		return (worldOptions & ZoneAttributes.NO_RETURN_BATTLE.getId()) == 0;
	}

	public void setWorldOption(ZoneAttributes option) {
		worldOptions |= option.getId();
	}

	public void removeWorldOption(ZoneAttributes option) {
		worldOptions &= ~option.getId();
	}

	public boolean hasOverridenOption(ZoneAttributes option) {
		if ((worldMapTemplate.getFlags() & option.getId()) == 0)
			return (worldOptions & option.getId()) != 0;
		return (worldOptions & option.getId()) == 0;
	}

	public int getInstanceCount() {
		int twinCount = worldMapTemplate.getTwinCount();
		if (twinCount == 0)
			twinCount = 1;
		twinCount += worldMapTemplate.getBeginnerTwinCount();
		return twinCount;
	}

	/**
	 * Return a WorldMapInstance - depends on map configuration one map may have twins instances to balance player. This method will return
	 * WorldMapInstance by server chose.
	 * 
	 * @return WorldMapInstance.
	 */
	public WorldMapInstance getMainWorldMapInstance() {
		// TODO Balance players into instances.
		return instances.get(1);
	}

	public WorldMapInstance getWorldMapInstance(int instanceId) {
		// instanceId is a count, some code still uses 0 for the default instance
		if (instanceId == 0)
			instanceId = 1;
		if (!isInstanceType()) {
			if (instanceId > getInstanceCount()) {
				throw new IllegalArgumentException("WorldMapInstance " + getMapId() + " has lower instances count than " + instanceId);
			}
		}
		return instances.get(instanceId);
	}

	/**
	 * Remove WorldMapInstance by instanceId.
	 * 
	 * @param instanceId
	 */
	public void removeWorldMapInstance(int instanceId) {
		// instanceId is a count, some code still uses 0 for the default instance
		if (instanceId == 0)
			instanceId = 1;
		instances.remove(instanceId);
	}

	/**
	 * Add instance to map
	 * 
	 * @param instanceId
	 * @param instance
	 */
	public void addInstance(int instanceId, WorldMapInstance instance) {
		// instanceId is a count, some code still uses 0 for the default instance
		if (instanceId == 0)
			instanceId = 1;
		instances.put(instanceId, instance);
	}

	public final WorldMapTemplate getTemplate() {
		return worldMapTemplate;
	}

	/**
	 * @return the nextInstanceId
	 */
	public int getNextInstanceId() {
		return nextInstanceId.incrementAndGet();
	}

	/**
	 * Whether this world map is instance type
	 * 
	 * @return
	 */
	public boolean isInstanceType() {
		return worldMapTemplate.isInstance();
	}

	public Iterator<WorldMapInstance> iterator() {
		return instances.values().iterator();
	}

	/**
	 * All instance ids of this map
	 */
	public Collection<Integer> getAvailableInstanceIds() {
		return instances.keySet();
	}
}
