/*
 * Copyright (C) 2004-2013 L2J DataPack
 * 
 * This file is part of L2J DataPack.
 * 
 * L2J DataPack 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 3 of the License, or
 * (at your option) any later version.
 * 
 * L2J DataPack 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, see <http://www.gnu.org/licenses/>.
 */
package hellbound.TowerOfNaia;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javolution.util.FastList;
import javolution.util.FastMap;

import com.l2jserver.gameserver.ThreadPoolManager;
import com.l2jserver.gameserver.ai.CtrlIntention;
import com.l2jserver.gameserver.datatables.DoorTable;
import com.l2jserver.gameserver.datatables.SkillTable;
import com.l2jserver.gameserver.instancemanager.GlobalVariablesManager;
import com.l2jserver.gameserver.instancemanager.ZoneManager;
import com.l2jserver.gameserver.model.L2CharPosition;
import com.l2jserver.gameserver.model.L2Party;
import com.l2jserver.gameserver.model.actor.L2Npc;
import com.l2jserver.gameserver.model.actor.instance.L2MonsterInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.quest.Quest;
import com.l2jserver.gameserver.model.skills.L2Skill;
import com.l2jserver.gameserver.model.zone.L2ZoneType;
import com.l2jserver.gameserver.network.NpcStringId;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.clientpackets.Say2;
import com.l2jserver.gameserver.network.serverpackets.NpcSay;
import com.l2jserver.gameserver.util.MinionList;
import com.l2jserver.gameserver.util.Util;

/**
 * @author GKR
 */
public class TowerOfNaia extends Quest
{
	// Challenge states
	private static final int STATE_SPORE_CHALLENGE_IN_PROGRESS = 1;
	private static final int STATE_SPORE_CHALLENGE_SUCCESSFULL = 2;
	private static final int STATE_SPORE_IDLE_TOO_LONG = 3;
	
	// Some constants
	private static final int SELF_DESPAWN_LIMIT = 600; // Challenge discontinues after 600 self-despawns by timer
	private static final int ELEMENT_INDEX_LIMIT = 120; // Epidos spawns when index reaches 120 points
	
	private static final int LOCK = 18491;
	private static final int CONTROLLER = 18492;
	private static final int ROOM_MANAGER_FIRST = 18494;
	private static final int ROOM_MANAGER_LAST = 18505;
	private static final int MUTATED_ELPY = 25604;
	private static final int SPORE_BASIC = 25613;
	private static final int SPORE_FIRE = 25605;
	private static final int SPORE_WATER = 25606;
	private static final int SPORE_WIND = 25607;
	private static final int SPORE_EARTH = 25608;
	private static final int DWARVEN_GHOST = 32370;
	private static final int[] EPIDOSES =
	{
		25610,
		25609,
		25612,
		25611
	}; // Order is important!
	private static final int[] TOWER_MONSTERS =
	{
		18490,
		22393,
		22394,
		22395,
		22411,
		22412,
		22413,
		22439,
		22440,
		22441,
		22442
	};
	private static final int[] ELEMENTS =
	{
		25605,
		25606,
		25607,
		25608
	};
	private static final int[] OPPOSITE_ELEMENTS =
	{
		25606,
		25605,
		25608,
		25607
	};
	private static final String[] ELEMENTS_NAME =
	{
		"Fire",
		"Water",
		"Wind",
		"Earth"
	};
	private static final int[][] SPORES_MOVE_POINTS =
	{
		{
			-46080,
			246368,
			-14183
		},
		{
			-44816,
			246368,
			-14183
		},
		{
			-44224,
			247440,
			-14184
		},
		{
			-44896,
			248464,
			-14183
		},
		{
			-46064,
			248544,
			-14183
		},
		{
			-46720,
			247424,
			-14183
		}
	};
	private static final int[][] SPORES_MERGE_POSITION =
	{
		{
			-45488,
			246768,
			-14183
		},
		{
			-44767,
			247419,
			-14183
		},
		{
			-46207,
			247417,
			-14183
		},
		{
			-45462,
			248174,
			-14183
		}
	};
	private static final NpcStringId[] SPORES_NPCSTRING_ID =
	{
		NpcStringId.ITS_S1,
		NpcStringId.S1_IS_STRONG,
		NpcStringId.ITS_ALWAYS_S1,
		NpcStringId.S1_WONT_DO
	};
	
	private static Map<Integer, int[]> DOORS = new HashMap<>();
	private static Map<Integer, Integer> ZONES = new HashMap<>();
	private static Map<Integer, int[][]> SPAWNS = new HashMap<>();
	
	private L2MonsterInstance _lock;
	private final L2Npc _controller;
	private int _counter;
	private int _despawnedSporesCount;
	private final int[] _indexCount =
	{
		0,
		0
	};
	private int _challengeState;
	private int _winIndex;
	
	private final Map<Integer, Boolean> _activeRooms = new FastMap<>();
	private final Map<Integer, List<L2Npc>> _spawns = new FastMap<>();
	private final FastList<L2Npc> _sporeSpawn = new FastList<L2Npc>().shared();
	
	static
	{
		// Format: entrance_door, exit_door
		DOORS.put(18494, new int[]
		{
			18250001,
			18250002
		});
		DOORS.put(18495, new int[]
		{
			18250003,
			18250004
		});
		DOORS.put(18496, new int[]
		{
			18250005,
			18250006
		});
		DOORS.put(18497, new int[]
		{
			18250007,
			18250008
		});
		DOORS.put(18498, new int[]
		{
			18250009,
			18250010
		});
		DOORS.put(18499, new int[]
		{
			18250011,
			18250101
		});
		DOORS.put(18500, new int[]
		{
			18250013,
			18250014
		});
		DOORS.put(18501, new int[]
		{
			18250015,
			18250102
		});
		DOORS.put(18502, new int[]
		{
			18250017,
			18250018
		});
		DOORS.put(18503, new int[]
		{
			18250019,
			18250103
		});
		DOORS.put(18504, new int[]
		{
			18250021,
			18250022
		});
		DOORS.put(18505, new int[]
		{
			18250023,
			18250024
		});
		
		ZONES.put(18494, 200020);
		ZONES.put(18495, 200021);
		ZONES.put(18496, 200022);
		ZONES.put(18497, 200023);
		ZONES.put(18498, 200024);
		ZONES.put(18499, 200025);
		ZONES.put(18500, 200026);
		ZONES.put(18501, 200027);
		ZONES.put(18502, 200028);
		ZONES.put(18503, 200029);
		ZONES.put(18504, 200030);
		ZONES.put(18505, 200031);
		
		SPAWNS.put(18494, new int[][]
		{
			{
				22393,
				-46371,
				246400,
				-9120,
				0
			},
			{
				22394,
				-46435,
				245830,
				-9120,
				0
			},
			{
				22394,
				-46536,
				246275,
				-9120,
				0
			},
			{
				22393,
				-46239,
				245996,
				-9120,
				0
			},
			{
				22394,
				-46229,
				246347,
				-9120,
				0
			},
			{
				22394,
				-46019,
				246198,
				-9120,
				0
			}
		});
		SPAWNS.put(18495, new int[][]
		{
			{
				22439,
				-48146,
				249597,
				-9124,
				-16280
			},
			{
				22439,
				-48144,
				248711,
				-9124,
				16368
			},
			{
				22439,
				-48704,
				249597,
				-9104,
				-16380
			},
			{
				22439,
				-49219,
				249596,
				-9104,
				-16400
			},
			{
				22439,
				-49715,
				249601,
				-9104,
				-16360
			},
			{
				22439,
				-49714,
				248696,
				-9104,
				15932
			},
			{
				22439,
				-49225,
				248710,
				-9104,
				16512
			},
			{
				22439,
				-48705,
				248708,
				-9104,
				16576
			}
		});
		SPAWNS.put(18496, new int[][]
		{
			{
				22441,
				-51176,
				246055,
				-9984,
				0
			},
			{
				22441,
				-51699,
				246190,
				-9984,
				0
			},
			{
				22442,
				-52060,
				245956,
				-9984,
				0
			},
			{
				22442,
				-51565,
				246433,
				-9984,
				0
			}
		});
		SPAWNS.put(18497, new int[][]
		{
			{
				22440,
				-49754,
				243866,
				-9968,
				-16328
			},
			{
				22440,
				-49754,
				242940,
				-9968,
				16336
			},
			{
				22440,
				-48733,
				243858,
				-9968,
				-16208
			},
			{
				22440,
				-48745,
				242936,
				-9968,
				16320
			},
			{
				22440,
				-49264,
				242946,
				-9968,
				16312
			},
			{
				22440,
				-49268,
				243869,
				-9968,
				-16448
			},
			{
				22440,
				-48186,
				242934,
				-9968,
				16576
			},
			{
				22440,
				-48185,
				243855,
				-9968,
				-16448
			}
		});
		SPAWNS.put(18498, new int[][]
		{
			{
				22411,
				-46355,
				246375,
				-9984,
				0
			},
			{
				22411,
				-46167,
				246160,
				-9984,
				0
			},
			{
				22393,
				-45952,
				245748,
				-9984,
				0
			},
			{
				22394,
				-46428,
				246254,
				-9984,
				0
			},
			{
				22393,
				-46490,
				245871,
				-9984,
				0
			},
			{
				22394,
				-45877,
				246309,
				-9984,
				0
			}
		});
		SPAWNS.put(18499, new int[][]
		{
			{
				22395,
				-48730,
				248067,
				-9984,
				0
			},
			{
				22395,
				-49112,
				248250,
				-9984,
				0
			}
		});
		SPAWNS.put(18500, new int[][]
		{
			{
				22393,
				-51954,
				246475,
				-10848,
				0
			},
			{
				22394,
				-51421,
				246512,
				-10848,
				0
			},
			{
				22394,
				-51404,
				245951,
				-10848,
				0
			},
			{
				22393,
				-51913,
				246206,
				-10848,
				0
			},
			{
				22394,
				-51663,
				245979,
				-10848,
				0
			},
			{
				22394,
				-51969,
				245809,
				-10848,
				0
			},
			{
				22412,
				-51259,
				246357,
				-10848,
				0
			}
		});
		SPAWNS.put(18501, new int[][]
		{
			{
				22395,
				-48856,
				243949,
				-10848,
				0
			},
			{
				22395,
				-49144,
				244190,
				-10848,
				0
			}
		});
		SPAWNS.put(18502, new int[][]
		{
			{
				22441,
				-46471,
				246135,
				-11704,
				0
			},
			{
				22441,
				-46449,
				245997,
				-11704,
				0
			},
			{
				22441,
				-46235,
				246187,
				-11704,
				0
			},
			{
				22441,
				-46513,
				246326,
				-11704,
				0
			},
			{
				22441,
				-45889,
				246313,
				-11704,
				0
			}
		});
		SPAWNS.put(18503, new int[][]
		{
			{
				22395,
				-49067,
				248050,
				-11712,
				0
			},
			{
				22395,
				-48957,
				248223,
				-11712,
				0
			}
		});
		SPAWNS.put(18504, new int[][]
		{
			{
				22413,
				-51748,
				246138,
				-12568,
				0
			},
			{
				22413,
				-51279,
				246200,
				-12568,
				0
			},
			{
				22413,
				-51787,
				246594,
				-12568,
				0
			},
			{
				22413,
				-51892,
				246544,
				-12568,
				0
			},
			{
				22413,
				-51500,
				245781,
				-12568,
				0
			},
			{
				22413,
				-51941,
				246045,
				-12568,
				0
			}
		});
		SPAWNS.put(18505, new int[][]
		{
			{
				18490,
				-48238,
				243347,
				-13376,
				0
			},
			{
				18490,
				-48462,
				244022,
				-13376,
				0
			},
			{
				18490,
				-48050,
				244045,
				-13376,
				0
			},
			{
				18490,
				-48229,
				243823,
				-13376,
				0
			},
			{
				18490,
				-47871,
				243208,
				-13376,
				0
			},
			{
				18490,
				-48255,
				243528,
				-13376,
				0
			},
			{
				18490,
				-48461,
				243780,
				-13376,
				0
			},
			{
				18490,
				-47983,
				243197,
				-13376,
				0
			},
			{
				18490,
				-47841,
				243819,
				-13376,
				0
			},
			{
				18490,
				-48646,
				243764,
				-13376,
				0
			},
			{
				18490,
				-47806,
				243850,
				-13376,
				0
			},
			{
				18490,
				-48456,
				243447,
				-13376,
				0
			}
		});
	}
	
	public TowerOfNaia(int questId, String name, String descr)
	{
		super(questId, name, descr);
		
		addFirstTalkId(CONTROLLER);
		addStartNpc(CONTROLLER);
		addStartNpc(DWARVEN_GHOST);
		addTalkId(CONTROLLER);
		addTalkId(DWARVEN_GHOST);
		addAttackId(LOCK);
		addKillId(LOCK);
		addKillId(MUTATED_ELPY);
		addSpawnId(MUTATED_ELPY);
		addKillId(SPORE_BASIC);
		addSpawnId(SPORE_BASIC);
		
		for (int npcId = SPORE_FIRE; npcId <= SPORE_EARTH; npcId++)
		{
			addKillId(npcId);
			addSpawnId(npcId);
		}
		
		for (int npcId = ROOM_MANAGER_FIRST; npcId <= ROOM_MANAGER_LAST; npcId++)
		{
			addFirstTalkId(npcId);
			addTalkId(npcId);
			addStartNpc(npcId);
			initRoom(npcId);
		}
		
		for (int npcId : TOWER_MONSTERS)
		{
			addKillId(npcId);
		}
		
		_lock = (L2MonsterInstance) addSpawn(LOCK, 16409, 244438, 11620, -1048, false, 0, false);
		_controller = addSpawn(CONTROLLER, 16608, 244420, 11620, 31264, false, 0, false);
		_counter = 90;
		_despawnedSporesCount = 0;
		_challengeState = 0;
		_winIndex = -1;
		initSporeChallenge();
		spawnElpy();
	}
	
	@Override
	public final String onFirstTalk(L2Npc npc, L2PcInstance player)
	{
		int npcId = npc.getNpcId();
		
		if (npcId == CONTROLLER)
		{
			if (_lock == null)
			{
				return "18492-02.htm";
			}
			return "18492-01.htm";
		}
		
		else if ((npcId >= ROOM_MANAGER_FIRST) && (npcId <= ROOM_MANAGER_LAST))
		{
			if (_activeRooms.containsKey(npcId) && !_activeRooms.get(npcId))
			{
				if (player.getParty() == null)
				{
					player.sendPacket(SystemMessageId.CAN_OPERATE_MACHINE_WHEN_IN_PARTY);
					return null;
				}
				return "manager.htm";
			}
		}
		
		return null;
	}
	
	@Override
	public final String onAdvEvent(String event, L2Npc npc, L2PcInstance player)
	{
		String htmltext = event;
		
		// Timer. Spawns Naia Lock
		if (event.equalsIgnoreCase("spawn_lock"))
		{
			htmltext = null;
			_lock = (L2MonsterInstance) addSpawn(LOCK, 16409, 244438, 11620, -1048, false, 0, false);
			_counter = 90;
		}
		
		// Timer. Depending of _challengeState despans all spawned spores, or spores, reached assembly point
		else if (event.equalsIgnoreCase("despawn_total"))
		{
			// Spores is not attacked too long - despawn them all, reinit values
			if (_challengeState == STATE_SPORE_IDLE_TOO_LONG)
			{
				for (L2Npc spore : _sporeSpawn)
				{
					if ((spore != null) && !spore.isDead())
					{
						spore.deleteMe();
					}
				}
				_sporeSpawn.clear();
				initSporeChallenge();
			}
			// Spores are moving to assembly point. Despawn all reached, check for reached spores count.
			else if ((_challengeState == STATE_SPORE_CHALLENGE_SUCCESSFULL) && (_winIndex >= 0))
			{
				// Requirements are met, despawn all spores, spawn Epidos
				if ((_despawnedSporesCount >= 10) || _sporeSpawn.isEmpty())
				{
					if (!_sporeSpawn.isEmpty())
					{
						for (L2Npc spore : _sporeSpawn)
						{
							if ((spore != null) && !spore.isDead())
							{
								spore.deleteMe();
							}
						}
					}
					_sporeSpawn.clear();
					_despawnedSporesCount = 0;
					int[] coords = SPORES_MERGE_POSITION[_winIndex];
					addSpawn(EPIDOSES[_winIndex], coords[0], coords[1], coords[2], 0, false, 0, false);
					initSporeChallenge();
				}
				// Requirements aren't met, despawn reached spores
				else
				{
					Iterator<L2Npc> it = _sporeSpawn.iterator();
					while (it.hasNext())
					{
						L2Npc spore = it.next();
						if ((spore != null) && !spore.isDead() && (spore.getX() == spore.getSpawn().getLocx()) && (spore.getY() == spore.getSpawn().getLocy()))
						{
							spore.deleteMe();
							it.remove();
							_despawnedSporesCount++;
						}
					}
					
					startQuestTimer("despawn_total", 3000, null, null);
				}
			}
		}
		
		if (npc == null)
		{
			return null;
		}
		
		int npcId = npc.getNpcId();
		
		if (event.equalsIgnoreCase("despawn_spore") && !npc.isDead() && (_challengeState == STATE_SPORE_CHALLENGE_IN_PROGRESS))
		{
			htmltext = null;
			
			_sporeSpawn.remove(npc);
			npc.deleteMe();
			
			if (npcId == SPORE_BASIC)
			{
				spawnRandomSpore();
				spawnRandomSpore();
			}
			
			else if ((npcId >= SPORE_FIRE) && (npcId <= SPORE_EARTH))
			{
				_despawnedSporesCount++;
				
				/*
				 * if (_despawnedSporesCount % 20 == 0) { Announcements.getInstance().announceToAll("Despawned " + Integer.toString(_despawnedSporesCount) + " creatures"); }
				 */
				
				if (_despawnedSporesCount < SELF_DESPAWN_LIMIT)
				{
					spawnOppositeSpore(npcId);
				}
				else
				{
					_challengeState = STATE_SPORE_IDLE_TOO_LONG;
					startQuestTimer("despawn_total", 60000, null, null);
				}
			}
		}
		else if (event.equalsIgnoreCase("18492-05.htm"))
		{
			if ((_lock == null) || (_lock.getCurrentHp() > (_lock.getMaxHp() / 10)))
			{
				htmltext = null;
				_lock.deleteMe();
				_lock = null;
				cancelQuestTimers("spawn_lock");
				startQuestTimer("spawn_lock", 300000, null, null);
				npc.setTarget(player);
				npc.doCast(SkillTable.getInstance().getInfo(5527, 1));
			}
		}
		else if (event.equalsIgnoreCase("teleport") && (_lock != null))
		{
			htmltext = null;
			L2Party party = player.getParty();
			if (party != null)
			{
				if (Util.checkIfInRange(3000, party.getLeader(), npc, true))
				{
					for (L2PcInstance partyMember : party.getMembers())
					{
						if (Util.checkIfInRange(2000, partyMember, npc, true))
						{
							partyMember.teleToLocation(-47271, 246098, -9120, true);
						}
					}
					_lock.deleteMe();
					_lock = null;
					cancelQuestTimers("spawn_lock");
					startQuestTimer("spawn_lock", 1200000, null, null);
				}
				else
				{
					npc.setTarget(player);
					npc.doCast(SkillTable.getInstance().getInfo(5527, 1));
				}
			}
			else
			{
				player.teleToLocation(-47271, 246098, -9120);
				_lock.deleteMe();
				_lock = null;
				cancelQuestTimers("spawn_lock");
				startQuestTimer("spawn_lock", 1200000, null, null);
			}
		}
		else if (event.equalsIgnoreCase("go") && _activeRooms.containsKey(npcId) && !_activeRooms.get(npcId))
		{
			htmltext = null;
			L2Party party = player.getParty();
			
			if (party != null)
			{
				removeForeigners(npcId, party);
				startRoom(npcId);
				ThreadPoolManager.getInstance().scheduleGeneral(new StopRoomTask(npcId), 300000);
			}
			else
			{
				player.sendPacket(SystemMessageId.CAN_OPERATE_MACHINE_WHEN_IN_PARTY);
			}
		}
		
		return htmltext;
	}
	
	@Override
	public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon, L2Skill skill)
	{
		if ((_lock != null) && (npc.getObjectId() == _lock.getObjectId()))
		{
			int remaindedHpPercent = (int) ((npc.getCurrentHp() * 100) / npc.getMaxHp());
			
			if ((remaindedHpPercent <= _counter) && (_controller != null))
			{
				if (_counter == 50)
				{
					MinionList.spawnMinion(_lock, 18493);
				}
				
				else if (_counter == 10)
				{
					MinionList.spawnMinion(_lock, 18493);
					MinionList.spawnMinion(_lock, 18493);
				}
				
				_controller.broadcastPacket(new NpcSay(_controller.getObjectId(), Say2.NPC_ALL, _controller.getNpcId(), NpcStringId.EMERGENCY_EMERGENCY_THE_OUTER_WALL_IS_WEAKENING_RAPIDLY));
				_counter -= 10;
			}
		}
		
		return super.onAttack(npc, attacker, damage, isSummon, skill);
	}
	
	@Override
	public String onKill(L2Npc npc, L2PcInstance killer, boolean isSummon)
	{
		int npcId = npc.getNpcId();
		
		if (npcId == LOCK)
		{
			_lock = null;
			cancelQuestTimers("spawn_lock");
			startQuestTimer("spawn_lock", 300000, null, null);
		}
		
		else if (Arrays.binarySearch(TOWER_MONSTERS, npcId) >= 0)
		{
			int managerId = 0;
			
			for (L2ZoneType zone : ZoneManager.getInstance().getZones(npc.getX(), npc.getY(), npc.getZ()))
			{
				if (ZONES.containsValue(zone.getId()))
				{
					for (int i : ZONES.keySet())
					{
						if (ZONES.get(i) == zone.getId())
						{
							managerId = i;
							break;
						}
					}
				}
			}
			
			if ((managerId > 0) && _spawns.containsKey(managerId))
			{
				List<L2Npc> spawned = _spawns.get(managerId);
				spawned.remove(npc);
				if (spawned.isEmpty() && DOORS.containsKey(managerId))
				{
					int[] doorList = DOORS.get(managerId);
					DoorTable.getInstance().getDoor(doorList[1]).openMe();
					_spawns.remove(managerId);
				}
			}
		}
		else if (npcId == MUTATED_ELPY)
		{
			_challengeState = STATE_SPORE_CHALLENGE_IN_PROGRESS;
			markElpyRespawn();
			DoorTable.getInstance().getDoor(18250025).closeMe();
			ZoneManager.getInstance().getZoneById(200100).setEnabled(true);
			
			for (int i = 0; i < 10; i++)
			{
				addSpawn(SPORE_BASIC, -45474, 247450, -13994, 49152, false, 0, false);
			}
		}
		else if ((npcId == SPORE_BASIC) && (_challengeState == STATE_SPORE_CHALLENGE_IN_PROGRESS))
		{
			_sporeSpawn.remove(npc);
			spawnRandomSpore();
			spawnRandomSpore();
		}
		else if ((npcId >= SPORE_FIRE) && (npcId <= SPORE_EARTH) && ((_challengeState == STATE_SPORE_CHALLENGE_IN_PROGRESS) || (_challengeState == STATE_SPORE_CHALLENGE_SUCCESSFULL)))
		{
			_sporeSpawn.remove(npc);
			
			if (_challengeState == STATE_SPORE_CHALLENGE_IN_PROGRESS)
			{
				_despawnedSporesCount--;
				int sporeGroup = getSporeGroup(npcId);
				
				if (sporeGroup >= 0)
				{
					if ((npcId == SPORE_FIRE) || (npcId == SPORE_WIND))
					{
						_indexCount[sporeGroup] += 2;
					}
					else
					{
						_indexCount[sporeGroup] -= 2;
					}
					
					if (_indexCount[Math.abs(sporeGroup - 1)] > 0)
					{
						_indexCount[Math.abs(sporeGroup - 1)]--;
					}
					else if (_indexCount[Math.abs(sporeGroup - 1)] < 0)
					{
						_indexCount[Math.abs(sporeGroup - 1)]++;
					}
					
					if ((Math.abs(_indexCount[sporeGroup]) < ELEMENT_INDEX_LIMIT) && (Math.abs(_indexCount[sporeGroup]) > 0) && ((_indexCount[sporeGroup] % 20) == 0) && (getRandom(100) < 50))
					{
						String el = ELEMENTS_NAME[Arrays.binarySearch(ELEMENTS, npcId)];
						for (L2Npc spore : _sporeSpawn)
						{
							if ((spore != null) && !spore.isDead() && (spore.getNpcId() == npcId))
							{
								NpcSay ns = new NpcSay(spore.getObjectId(), Say2.NPC_ALL, spore.getNpcId(), SPORES_NPCSTRING_ID[getRandom(4)]);
								ns.addStringParameter(el);
								spore.broadcastPacket(ns);
							}
						}
					}
					if (Math.abs(_indexCount[sporeGroup]) < ELEMENT_INDEX_LIMIT)
					{
						if ((((_indexCount[sporeGroup] > 0) && ((npcId == SPORE_FIRE) || (npcId == SPORE_WIND))) || ((_indexCount[sporeGroup] <= 0) && ((npcId == SPORE_WATER) || (npcId == SPORE_EARTH)))) && (getRandom(1000) > 200))
						{
							spawnOppositeSpore(npcId);
						}
						else
						{
							spawnRandomSpore();
						}
					}
					
					else
					// index value was reached
					{
						_challengeState = STATE_SPORE_CHALLENGE_SUCCESSFULL;
						_despawnedSporesCount = 0;
						_winIndex = Arrays.binarySearch(ELEMENTS, npcId);
						int[] coord = SPORES_MERGE_POSITION[_winIndex];
						
						for (L2Npc spore : _sporeSpawn)
						{
							if ((spore != null) && !spore.isDead())
							{
								moveTo(spore, coord);
							}
						}
						
						startQuestTimer("despawn_total", 3000, null, null);
					}
				}
			}
		}
		return super.onKill(npc, killer, isSummon);
	}
	
	@Override
	public final String onSpawn(L2Npc npc)
	{
		int npcId = npc.getNpcId();
		
		if ((npcId == MUTATED_ELPY) && !npc.isTeleporting())
		{
			DoorTable.getInstance().getDoor(18250025).openMe();
			ZoneManager.getInstance().getZoneById(200100).setEnabled(false);
			ZoneManager.getInstance().getZoneById(200101).setEnabled(true);
			ZoneManager.getInstance().getZoneById(200101).setEnabled(false);
		}
		else if (((npcId == SPORE_BASIC) || ((npcId >= SPORE_FIRE) && (npcId <= SPORE_EARTH))) && (_challengeState == STATE_SPORE_CHALLENGE_IN_PROGRESS))
		{
			_sporeSpawn.add(npc);
			npc.setIsRunning(false);
			int[] coord = SPORES_MOVE_POINTS[getRandom(SPORES_MOVE_POINTS.length)];
			npc.getSpawn().setLocx(coord[0]);
			npc.getSpawn().setLocy(coord[1]);
			npc.getSpawn().setLocz(coord[2]);
			npc.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(coord[0], coord[1], coord[2], 0));
			startQuestTimer("despawn_spore", 60000, npc, null);
		}
		return super.onSpawn(npc);
	}
	
	private int getSporeGroup(int sporeId)
	{
		int ret;
		switch (sporeId)
		{
			case SPORE_FIRE:
			case SPORE_WATER:
				ret = 0;
				break;
			case SPORE_WIND:
			case SPORE_EARTH:
				ret = 1;
				break;
			default:
				ret = -1;
		}
		return ret;
	}
	
	protected void initRoom(int managerId)
	{
		removeAllPlayers(managerId);
		_activeRooms.put(managerId, false);
		
		if (DOORS.containsKey(managerId))
		{
			int[] doorList = DOORS.get(managerId);
			DoorTable.getInstance().getDoor(doorList[0]).openMe();
			DoorTable.getInstance().getDoor(doorList[1]).closeMe();
		}
		
		if (_spawns.containsKey(managerId) && (_spawns.get(managerId) != null))
		{
			for (L2Npc npc : _spawns.get(managerId))
			{
				if ((npc != null) && !npc.isDead())
				{
					npc.deleteMe();
				}
			}
			
			_spawns.get(managerId).clear();
			_spawns.remove(managerId);
		}
	}
	
	private void initSporeChallenge()
	{
		_despawnedSporesCount = 0;
		_challengeState = 0;
		_winIndex = -1;
		_indexCount[0] = 0;
		_indexCount[1] = 0;
		ZoneManager.getInstance().getZoneById(200100).setEnabled(false);
		ZoneManager.getInstance().getZoneById(200101).setEnabled(false);
		ZoneManager.getInstance().getZoneById(200101).setEnabled(true);
		
	}
	
	private void markElpyRespawn()
	{
		final long respawnTime = (getRandom(43200, 216000) * 1000) + System.currentTimeMillis();
		GlobalVariablesManager.getInstance().storeVariable("elpy_respawn_time", Long.toString(respawnTime));
	}
	
	private int moveTo(L2Npc npc, int[] coords)
	{
		int time = 0;
		if (npc != null)
		{
			double distance = Util.calculateDistance(coords[0], coords[1], coords[2], npc.getX(), npc.getY(), npc.getZ(), true);
			int heading = Util.calculateHeadingFrom(npc.getX(), npc.getY(), coords[0], coords[1]);
			time = (int) ((distance / npc.getWalkSpeed()) * 1000);
			npc.setIsRunning(false);
			npc.disableCoreAI(true);
			npc.setIsNoRndWalk(true);
			npc.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(coords[0], coords[1], coords[2], heading));
			npc.getSpawn().setLocx(coords[0]);
			npc.getSpawn().setLocy(coords[1]);
			npc.getSpawn().setLocz(coords[2]);
		}
		return time == 0 ? 100 : time;
	}
	
	private void spawnElpy()
	{
		final String tmp = GlobalVariablesManager.getInstance().getStoredVariable("elpy_respawn_time");
		final long respawnTime = tmp == null ? 0 : Long.parseLong(tmp);
		if (respawnTime <= System.currentTimeMillis())
		{
			addSpawn(MUTATED_ELPY, -45474, 247450, -13994, 49152, false, 0, false);
		}
		else
		{
			ThreadPoolManager.getInstance().scheduleGeneral(new Runnable()
			{
				@Override
				public void run()
				{
					addSpawn(MUTATED_ELPY, -45474, 247450, -13994, 49152, false, 0, false);
				}
			}, respawnTime - System.currentTimeMillis());
		}
	}
	
	private L2Npc spawnRandomSpore()
	{
		return addSpawn(getRandom(SPORE_FIRE, SPORE_EARTH), -45474, 247450, -13994, 49152, false, 0, false);
	}
	
	private L2Npc spawnOppositeSpore(int srcSporeId)
	{
		int idx = Arrays.binarySearch(ELEMENTS, srcSporeId);
		return idx >= 0 ? addSpawn(OPPOSITE_ELEMENTS[idx], -45474, 247450, -13994, 49152, false, 0, false) : null;
	}
	
	private void startRoom(int managerId)
	{
		_activeRooms.put(managerId, true);
		
		if (DOORS.containsKey(managerId))
		{
			int[] doorList = DOORS.get(managerId);
			DoorTable.getInstance().getDoor(doorList[0]).closeMe();
		}
		
		if (SPAWNS.containsKey(managerId))
		{
			int[][] spawnList = SPAWNS.get(managerId);
			List<L2Npc> spawned = new FastList<>();
			for (int[] spawn : spawnList)
			{
				L2Npc spawnedNpc = addSpawn(spawn[0], spawn[1], spawn[2], spawn[3], spawn[4], false, 0, false);
				spawned.add(spawnedNpc);
			}
			if (!spawned.isEmpty())
			{
				_spawns.put(managerId, spawned);
			}
		}
	}
	
	private void removeForeigners(int managerId, L2Party party)
	{
		if ((party != null) && ZONES.containsKey(managerId) && (ZoneManager.getInstance().getZoneById(ZONES.get(managerId)) != null))
		{
			L2ZoneType zone = ZoneManager.getInstance().getZoneById(ZONES.get(managerId));
			for (L2PcInstance player : zone.getPlayersInside())
			{
				if (player != null)
				{
					L2Party charParty = player.getParty();
					if ((charParty == null) || (charParty.getLeaderObjectId() != party.getLeaderObjectId()))
					{
						player.teleToLocation(16110, 243841, 11616);
					}
				}
			}
		}
	}
	
	private void removeAllPlayers(int managerId)
	{
		if (ZONES.containsKey(managerId) && (ZoneManager.getInstance().getZoneById(ZONES.get(managerId)) != null))
		{
			L2ZoneType zone = ZoneManager.getInstance().getZoneById(ZONES.get(managerId));
			for (L2PcInstance player : zone.getPlayersInside())
			{
				if (player != null)
				{
					player.teleToLocation(16110, 243841, 11616);
				}
			}
		}
	}
	
	private class StopRoomTask implements Runnable
	{
		private final int _managerId;
		
		public StopRoomTask(int managerId)
		{
			_managerId = managerId;
		}
		
		@Override
		public void run()
		{
			initRoom(_managerId);
		}
	}
	
	public static void main(String[] args)
	{
		new TowerOfNaia(-1, "TowerOfNaia", "hellbound");
	}
}
