/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package com.edelvez.gameserver.model;

import java.util.Iterator;
import java.util.concurrent.ScheduledFuture;
import java.util.logging.Logger;

import javolution.util.FastList;
import com.edelvez.Config;
import com.edelvez.gameserver.ThreadPoolManager;
import com.edelvez.gameserver.ai.L2AttackableAI;
import com.edelvez.gameserver.datatables.SpawnTable;
import com.edelvez.gameserver.model.actor.instance.L2NpcInstance;
import com.edelvez.gameserver.model.actor.instance.L2PlayableInstance;
import com.edelvez.gameserver.model.zone.L2ZoneType;
import com.edelvez.gameserver.model.zone.type.L2PeaceZone;
import com.edelvez.util.L2ObjectSet;

/**
 * This class ...
 *
 * @version $Revision: 1.3.4.4 $ $Date: 2005/03/27 15:29:33 $
 */
public final class L2WorldRegion
{
	private static Logger _log = Logger.getLogger(L2WorldRegion.class.getName());
	/** L2ObjectHashSet(L2PlayableInstance) containing L2PlayableInstance of all player & summon in game in this L2WorldRegion */
	private L2ObjectSet<L2PlayableInstance> _allPlayable;
	/** L2ObjectHashSet(L2Object) containing L2Object visible in this L2WorldRegion */
	private L2ObjectSet<L2Object> _visibleObjects;
	private FastList<L2WorldRegion> _surroundingRegions;
	private int _tileX, _tileY;
	private Boolean _active = false;
	private ScheduledFuture<?> _neighborsTask = null;
	private final FastList<L2ZoneType> _zones;

	public L2WorldRegion(int pTileX, int pTileY)
	{
		_allPlayable = L2ObjectSet.createL2PlayerSet(); // new L2ObjectHashSet<L2PcInstance>();
		_visibleObjects = L2ObjectSet.createL2ObjectSet(); // new L2ObjectHashSet<L2Object>();
		_surroundingRegions = new FastList<L2WorldRegion>();
		// _surroundingRegions.add(this); //done in L2World.initRegions()
		_tileX = pTileX;
		_tileY = pTileY;
		// default a newly initialized region to inactive, unless always on is specified
		if (Config.GRIDS_ALWAYS_ON) {
			_active = true;
		} else {
			_active = false;
		}
		_zones = new FastList<L2ZoneType>();
	}

	public void addZone(L2ZoneType zone)
	{
		_zones.add(zone);
	}

	public void removeZone(L2ZoneType zone)
	{
		_zones.remove(zone);
	}

	public FastList<L2ZoneType> getZones()
	{
		return _zones;
	}

	public boolean checkEffectRangeInsidePeaceZone(L2Skill skill, final int x, final int y, final int z)
	{
		if (getZones() != null)
		{
			final int range = skill.getEffectRange();
			final int up = y + range;
			final int down = y - range;
			final int left = x + range;
			final int right = x - range;
			for (L2ZoneType e : getZones())
			{
				if (e instanceof L2PeaceZone)
				{
					if (e.isInsideZone(x, up, z)) {
						return false;
					}
					if (e.isInsideZone(x, down, z)) {
						return false;
					}
					if (e.isInsideZone(left, y, z)) {
						return false;
					}
					if (e.isInsideZone(right, y, z)) {
						return false;
					}
					if (e.isInsideZone(x, y, z)) {
						return false;
					}
				}
			}
			return true;
		}
		return true;
	}

	public void revalidateZones(L2Character character)
	{
		// do NOT update the world region while the character is still in the process of teleporting
		// Once the teleport is COMPLETED, revalidation occurs safely, at that time.
		if (character.isTeleporting()) {
			return;
		}
		for (L2ZoneType z : getZones())
		{
			if (z != null) {
				z.revalidateInZone(character);
			}
		}
	}

	public void removeFromZones(L2Character character)
	{
		for (L2ZoneType z : getZones())
		{
			if (z != null) {
				z.removeCharacter(character);
			}
		}
	}

	public void onDeath(L2Character character)
	{
		for (L2ZoneType z : getZones())
		{
			if (z != null) {
				z.onDieInside(character);
			}
		}
	}

	public void onRevive(L2Character character)
	{
		for (L2ZoneType z : getZones())
		{
			if (z != null) {
				z.onReviveInside(character);
			}
		}
	}

	/** Task of AI notification */
	public class NeighborsTask implements Runnable
	{
		private boolean _isActivating;

		public NeighborsTask(boolean isActivating)
		{
			_isActivating = isActivating;
		}

		public void run()
		{
			if (_isActivating)
			{
				// for each neighbor, if it's not active, activate.
				for (L2WorldRegion neighbor : getSurroundingRegions()) {
					neighbor.setActive(true);
				}
			}
			else
			{
				if (areNeighborsEmpty()) {
					setActive(false);
				}
				// check and deactivate
				for (L2WorldRegion neighbor : getSurroundingRegions()) {
					if (neighbor.areNeighborsEmpty()) {
						neighbor.setActive(false);
					}
				}
			}
		}
	}

	private void switchAI(Boolean isOn)
	{
		int c = 0;
		if (!isOn)
		{
			for (L2Object o : _visibleObjects)
			{
				if (o instanceof L2Attackable)
				{
					c++;
					L2Attackable mob = (L2Attackable) o;
					// Set target to null and cancel Attack or Cast
					mob.setTarget(null);
					// Stop movement
					mob.stopMove(null);
					// Stop all active skills effects in progress on the L2Character
					mob.stopAllEffects();
					mob.clearAggroList();
					mob.getKnownList().removeAllKnownObjects();
					mob.getAI().setIntention(com.edelvez.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE);
					// stop the ai tasks
					((L2AttackableAI) mob.getAI()).stopAITask();
					// Stop HP/MP/CP Regeneration task
					// try this: allow regen, but only until mob is 100% full...then stop
					// it until the grid is made active.
					// mob.getStatus().stopHpMpRegeneration();
				}
			}
			_log.fine(c + " mobs were turned off");
		}
		else
		{
			for (L2Object o : _visibleObjects)
			{
				if (o instanceof L2Attackable)
				{
					c++;
					// Start HP/MP/CP Regeneration task
					((L2Attackable) o).getStatus().startHpMpRegeneration();
					// start the ai
					// ((L2AttackableAI) mob.getAI()).startAITask();
				}
				else if (o instanceof L2NpcInstance)
				{
					// Create a RandomAnimation Task that will be launched after the calculated delay if the server allow it
					// L2Monsterinstance/L2Attackable socials are handled by AI (TODO: check the instances)
					((L2NpcInstance) o).startRandomAnimationTimer();
				}
			}
            //KnownListUpdateTaskManager.getInstance().updateRegion(this, true, false);
			_log.fine(c + " mobs were turned on");
		}
	}

	public Boolean isActive()
	{
		return _active;
	}

	// check if all 9 neighbors (including self) are inactive or active but with no players.
	// returns true if the above condition is met.
	public Boolean areNeighborsEmpty()
	{
		// if this region is occupied, return false.
		if (isActive() && _allPlayable.size() > 0) {
			return false;
		}
		// if any one of the neighbors is occupied, return false
		for (L2WorldRegion neighbor : _surroundingRegions) {
			if (neighbor.isActive() && neighbor._allPlayable.size() > 0) {
				return false;
			}
		}
		// in all other cases, return true.
		return true;
	}

	/**
	 * this function turns this region's AI and geodata on or off
	 *
	 * @param value
	 */
	public void setActive(boolean value)
	{
		if (_active == value) {
			return;
		}
		_active = value;
		// turn the AI on or off to match the region's activation.
		switchAI(value);
		// TODO
		// turn the geodata on or off to match the region's activation.
		if (value) {
			_log.fine("Starting Grid " + _tileX + "," + _tileY);
		} else {
			_log.fine("Stoping Grid " + _tileX + "," + _tileY);
		}
	}

	/**
	 * Immediately sets self as active and starts a timer to set neighbors as active this timer is to avoid turning on neighbors in the case when a person just teleported into a region and then teleported out immediately...there is no reason to activate all the neighbors in that case.
	 */
	private void startActivation()
	{
		// first set self to active and do self-tasks...
		setActive(true);
		// if the timer to deactivate neighbors is running, cancel it.
		if (_neighborsTask != null)
		{
			_neighborsTask.cancel(true);
			_neighborsTask = null;
		}
		// then, set a timer to activate the neighbors
		_neighborsTask = ThreadPoolManager.getInstance().scheduleGeneral(new NeighborsTask(true), 1000 * Config.GRID_NEIGHBOR_TURNON_TIME);
	}

	/**
	 * starts a timer to set neighbors (including self) as inactive this timer is to avoid turning off neighbors in the case when a person just moved out of a region that he may very soon return to. There is no reason to turn self & neighbors off in that case.
	 */
	private void startDeactivation()
	{
		// if the timer to activate neighbors is running, cancel it.
		if (_neighborsTask != null)
		{
			_neighborsTask.cancel(true);
			_neighborsTask = null;
		}
		// start a timer to "suggest" a deactivate to self and neighbors.
		// suggest means: first check if a neighbor has L2PcInstances in it. If not, deactivate.
		_neighborsTask = ThreadPoolManager.getInstance().scheduleGeneral(new NeighborsTask(false), 1000 * Config.GRID_NEIGHBOR_TURNOFF_TIME);
	}

	/**
	 * Add the L2Object in the L2ObjectHashSet(L2Object) _visibleObjects containing L2Object visible in this L2WorldRegion <BR>
	 * If L2Object is a L2PcInstance, Add the L2PcInstance in the L2ObjectHashSet(L2PcInstance) _allPlayable containing L2PcInstance of all player in game in this L2WorldRegion <BR>
	 * Assert : object.getCurrentWorldRegion() == this
	 */
	public void addVisibleObject(L2Object object)
	{
		if (Config.ASSERT) {
			assert object.getWorldRegion() == this;
		}
		if (object == null) {
			return;
		}
		_visibleObjects.put(object);
		if (object instanceof L2PlayableInstance)
		{
			_allPlayable.put((L2PlayableInstance) object);
			// if this is the first player to enter the region, activate self & neighbors
			if (_allPlayable.size() == 1 && !Config.GRIDS_ALWAYS_ON) {
				startActivation();
			}
		}
	}

	/**
	 * Remove the L2Object from the L2ObjectHashSet(L2Object) _visibleObjects in this L2WorldRegion <BR>
	 * <BR>
	 * If L2Object is a L2PcInstance, remove it from the L2ObjectHashSet(L2PcInstance) _allPlayable of this L2WorldRegion <BR>
	 * Assert : object.getCurrentWorldRegion() == this || object.getCurrentWorldRegion() == null
	 */
	public void removeVisibleObject(L2Object object)
	{
		if (Config.ASSERT) {
			assert object.getWorldRegion() == this || object.getWorldRegion() == null;
		}
		if (object == null) {
			return;
		}
		_visibleObjects.remove(object);
		if (object instanceof L2PlayableInstance)
		{
			_allPlayable.remove((L2PlayableInstance) object);
			if (_allPlayable.size() == 0 && !Config.GRIDS_ALWAYS_ON) {
				startDeactivation();
			}
		}
	}

	public void addSurroundingRegion(L2WorldRegion region)
	{
		_surroundingRegions.add(region);
	}

	/**
	 * Return the FastList _surroundingRegions containing all L2WorldRegion around the current L2WorldRegion
	 */
	public FastList<L2WorldRegion> getSurroundingRegions()
	{
		// change to return L2WorldRegion[] ?
		// this should not change after initialization, so maybe changes are not necessary
		return _surroundingRegions;
	}

	public Iterator<L2PlayableInstance> iterateAllPlayers()
	{
		return _allPlayable.iterator();
	}

	public L2ObjectSet<L2Object> getVisibleObjects()
	{
		return _visibleObjects;
	}

    public L2ObjectSet<L2PlayableInstance> getVisiblePlayable()
    {
        return _allPlayable;
    }

	public String getName()
	{
		return "(" + _tileX + ", " + _tileY + ")";
	}

	/**
	 * Deleted all spawns in the world.
	 */
	public synchronized void deleteVisibleNpcSpawns()
	{
		_log.fine("Deleting all visible NPC's in Region: " + getName());
		for (L2Object obj : _visibleObjects)
		{
			if (obj instanceof L2NpcInstance)
			{
				L2NpcInstance target = (L2NpcInstance) obj;
				target.deleteMe();
				L2Spawn spawn = target.getSpawn();
				if (spawn != null)
				{
					spawn.stopRespawn();
					SpawnTable.getInstance().deleteSpawn(spawn, false);
				}
				_log.finest("Removed NPC " + target.getObjectId());
			}
		}
		_log.info("All visible NPC's deleted in Region: " + getName());
	}
}
