/*
 * Copyright (C) 2004-2014 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 ai.individual.Baium;

import static com.l2jserver.gameserver.datatables.SkillData.*;

import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantLock;

import ai.npc.AbstractNpcAI;

import com.l2jserver.Config;
import com.l2jserver.gameserver.ai.CtrlIntention;
import com.l2jserver.gameserver.enums.CategoryType;
import com.l2jserver.gameserver.enums.MountType;
import com.l2jserver.gameserver.instancemanager.GrandBossManager;
import com.l2jserver.gameserver.instancemanager.ZoneManager;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.Location;
import com.l2jserver.gameserver.model.StatsSet;
import com.l2jserver.gameserver.model.actor.L2Attackable;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.actor.L2Npc;
import com.l2jserver.gameserver.model.actor.L2Playable;
import com.l2jserver.gameserver.model.actor.instance.L2GrandBossInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.skills.Skill;
import com.l2jserver.gameserver.model.variables.NpcVariables;
import com.l2jserver.gameserver.model.zone.type.L2NoRestartZone;
import com.l2jserver.gameserver.network.NpcStringId;
import com.l2jserver.gameserver.network.clientpackets.Say2;
import com.l2jserver.gameserver.network.serverpackets.Earthquake;
import com.l2jserver.gameserver.network.serverpackets.ExShowScreenMessage;
import com.l2jserver.gameserver.network.serverpackets.NpcSay;
import com.l2jserver.gameserver.network.serverpackets.PlaySound;
import com.l2jserver.gameserver.network.serverpackets.SocialAction;
import com.l2jserver.gameserver.util.Util;

/**
 * Baium AI.
 * @author St3eT
 */
public final class Baium extends AbstractNpcAI
{
	// NPCs
	private static final int BAIUM = 29020; // Baium
	private static final int BAIUM_STONE = 29025; // Baium
	private static final int ANG_VORTEX = 31862; // Angelic Vortex
	private static final int ARCHANGEL = 29021; // Archangel
	private static final int TELE_CUBE = 31842; // Teleportation Cubic
	// Skills
	private static final int BAIUM_ATTACK = getSkillHashCode(4127, 1); // Baium: General Attack
	private static final int ENERGY_WAVE = getSkillHashCode(4128, 1); // Wind Of Force
	private static final int EARTH_QUAKE = getSkillHashCode(4129, 1); // Earthquake
	private static final int THUNDERBOLT = getSkillHashCode(4130, 1); // Striking of Thunderbolt
	private static final int GROUP_HOLD = getSkillHashCode(4131, 1); // Stun
	private static final int SPEAR_ATTACK = getSkillHashCode(4132, 1); // Spear: Pound the Ground
	private static final int ANGEL_HEAL = getSkillHashCode(4133, 1); // Angel Heal
	private static final int HEAL_OF_BAIUM = getSkillHashCode(4135, 1); // Baium Heal
	private static final int BAIUM_PRESENT = getSkillHashCode(4136, 1); // Baium's Gift
	private static final int ANTI_STRIDER = getSkillHashCode(4258, 1); // Hinder Strider/*=>*/
	// Items
	private static final int FABRIC = 4295; // Blooded Fabric
	// Zone
	private static final L2NoRestartZone zone = ZoneManager.getInstance().getZoneById(70051, L2NoRestartZone.class); // Baium zone
	// Status
	private static final int ALIVE = 0;
	private static final int WAITING = 1;
	private static final int IN_FIGHT = 2;
	private static final int DEAD = 3;
	// Misc
	private static final int INACTIVITY_TIME = 900000;	// Inactivity time
	private static final int CLEAR_DELAY = 900000;	// Clear delay
	// Locations
	private static final Location BAIUM_GIFT_LOC = new Location(115910, 17337, 10105);
	private static final Location BAIUM_LOC = new Location(116033, 17447, 10107, -25348);
	private static final Location TELEPORT_CUBIC_LOC = new Location(115017, 15549, 10090);
	private static final Location TELEPORT_IN_LOC = new Location(114077, 15882, 10078);
	private static final Location[] TELEPORT_OUT_LOC =
	{
		new Location(108784, 16000, -4928),
		new Location(113824, 10448, -5164),
		new Location(115488, 22096, -5168),
	};
	private static final Location[] ARCHANGEL_LOC =
	{
		new Location(115792, 16608, 10136, 0),
		new Location(115168, 17200, 10136, 0),
		new Location(115780, 15564, 10136, 13620),
		new Location(114880, 16236, 10136, 5400),
		new Location(114239, 17168, 10136, -1992)
	};
	// Misc
	private L2GrandBossInstance _baium;
	private static long _lastAttack;
	private static L2PcInstance _standbyPlayer;
	private static WeakHashMap<L2Object, ReentrantLock> _aiLocks;
	
	private void init()
	{
		_lastAttack = 0;
		_standbyPlayer = null;
		_aiLocks = null;
	}
	
	private Baium()
	{
		super(Baium.class.getSimpleName(), "ai/individual");
		addFirstTalkId(ANG_VORTEX);
		addTalkId(ANG_VORTEX, TELE_CUBE, BAIUM_STONE);
		addStartNpc(ANG_VORTEX, TELE_CUBE, BAIUM_STONE);
		addAttackId(BAIUM, ARCHANGEL);
		addKillId(BAIUM);
		addSeeCreatureId(BAIUM);
		addSpellFinishedId(BAIUM);
		addSpawnId(BAIUM, ARCHANGEL);
		
		final StatsSet info = GrandBossManager.getInstance().getStatsSet(BAIUM);
		final int curr_hp = info.getInt("currentHP");
		final int curr_mp = info.getInt("currentMP");
		final int loc_x = info.getInt("loc_x");
		final int loc_y = info.getInt("loc_y");
		final int loc_z = info.getInt("loc_z");
		final int heading = info.getInt("heading");
		final long respawnTime = info.getLong("respawn_time");
		
		switch (getStatus())
		{
			case WAITING:
			{
				setStatus(ALIVE);
			}
			case ALIVE:
			{
				addSpawn(BAIUM_STONE, BAIUM_LOC, false, 0);
				break;
			}
			case IN_FIGHT:
			{
				_baium = (L2GrandBossInstance) addSpawn(BAIUM, loc_x, loc_y, loc_z, heading, false, 0);
				_baium.setCurrentHpMp(curr_hp, curr_mp);
				_lastAttack = System.currentTimeMillis();
				addBoss(_baium);
				
				for (Location loc : ARCHANGEL_LOC)
				{
					final L2Npc archangel = addSpawn(ARCHANGEL, loc, false, 0, true);
					startQuestTimer("SELECT_TARGET", 5000, archangel, null, Repeating.WithFixedDelay);
				}
				startQuestTimer("CHECK_ATTACK", 60000, _baium, null, Repeating.WithFixedDelay);
				break;
			}
			case DEAD:
			{
				final long remain = respawnTime - System.currentTimeMillis();
				if (remain > 0)
				{
					startQuestTimer("CLEAR_STATUS", remain, null, null);
				}
				else
				{
					notifyEvent("CLEAR_STATUS", null, null);
				}
				break;
			}
		}
	}
	
	@Override
	public String onAdvEvent(String event, L2Npc npc, L2PcInstance player)
	{
		switch (event)
		{
			case "31862-04.html":	// ANG_VORTEX
			{
				return event;
			}
			case "enter":	// ANG_VORTEX 31862.html「エンジェリック バーテックスが見せてくれる場所へ移動する」
			{
				String htmltext = null;
				if (getStatus() == DEAD)
				{
					htmltext = "31862-03.html";
				}
				else if (getStatus() == IN_FIGHT)
				{
					htmltext = "31862-02.html";
				}
				else if (!hasQuestItems(player, FABRIC))
				{
					htmltext = "31862-01.html";
				}
				else
				{
					takeItems(player, FABRIC, 1);
					player.teleToLocation(TELEPORT_IN_LOC);
				}
				return htmltext;
			}
			case "teleportOut":
			{
				final Location destination = TELEPORT_OUT_LOC[getRandom(TELEPORT_OUT_LOC.length)];
				player.teleToLocation(destination.getX() + getRandom(100), destination.getY() + getRandom(100), destination.getZ());
				break;
			}
			case "wakeUp":	// BAIUM_STONE 29025.htm「バイウムを目覚めさせる」*/
			{
				if (getStatus() == ALIVE)
				{
					setStatus(IN_FIGHT);
					npc.deleteMe();	// BAIUM_STONE
					_lastAttack = System.currentTimeMillis();
					_standbyPlayer = player;
					_baium = (L2GrandBossInstance) addSpawn(BAIUM, BAIUM_LOC, false, 0);
					_baium.disableCoreAI(true);
					addBoss(_baium);
					startQuestTimer("WAKEUP_ACTION", 50, _baium, null);
					startQuestTimer("MANAGE_EARTHQUAKE", 2000, _baium, null);
					startQuestTimer("CHECK_ATTACK", 60000, _baium, null, Repeating.WithFixedDelay);
				}
				break;
			}
			case "WAKEUP_ACTION":	// BAIUM
			{
				zone.broadcastPacket(new SocialAction(_baium.getObjectId(), 2));
				break;
			}
			case "MANAGE_EARTHQUAKE":	// BAIUM
			{
				zone.broadcastPacket(new Earthquake(npc.getX(), npc.getY(), npc.getZ(), 40, 10));
				zone.broadcastPacket(new PlaySound("BS02_A"));
				startQuestTimer("SOCIAL_ACTION", 8000, npc, player);
				break;
			}
			case "SOCIAL_ACTION":	// BAIUM
			{
				zone.broadcastPacket(new SocialAction(npc.getObjectId(), 3));
				startQuestTimer("PLAYER_PORT", 6000, npc, player);
				break;
			}
			case "PLAYER_PORT":	// BAIUM
			{
				if (_standbyPlayer.isInsideRadius(npc, 16000, true, false))
				{
					if (!_standbyPlayer.isInsideRadius(npc, getSkill(BAIUM_PRESENT).getCastRange(), true, false))
						_standbyPlayer.teleToLocation(BAIUM_GIFT_LOC);
					startQuestTimer("PLAYER_KILL", 3000, npc, null);
				}
				break;
			}
			case "PLAYER_KILL":	// BAIUM
			{
				if (_standbyPlayer.isInsideRadius(npc, 16000, true, false))
				{
					zone.broadcastPacket(new SocialAction(npc.getObjectId(), 1));
					zone.broadcastPacket(new NpcSay(npc, Say2.NPC_ALL, _standbyPlayer.getName() + "、私の眠りを妨げるとは！、死ぬがいい！")); // TODO: replace with NpcStringId when are done core support
					npc.setTarget(_standbyPlayer);
					npc.doCast(getSkill(BAIUM_PRESENT));
				}
				
				for (L2PcInstance players : zone.getPlayersInside())
				{
					if (players.isHero())
					{
						zone.broadcastPacket(new ExShowScreenMessage(NpcStringId.NOT_EVEN_THE_GODS_THEMSELVES_COULD_TOUCH_ME_BUT_YOU_S1_YOU_DARE_CHALLENGE_ME_IGNORANT_MORTAL, 2, 4000, players.getName()));
						break;
					}
				}
				startQuestTimer("SPAWN_ARCHANGEL", 8000, npc, null);
				break;
			}
			case "SPAWN_ARCHANGEL":	// BAIUM
			{
				_baium.disableCoreAI(false);
				
				for (Location loc : ARCHANGEL_LOC)
				{
					final L2Npc archangel = addSpawn(ARCHANGEL, loc, false, 0, true);
					startQuestTimer("SELECT_TARGET", 5000, archangel, null, Repeating.WithFixedDelay);
				}
				
				if (!_standbyPlayer.isDead() && zone.isInsideZone(_standbyPlayer))
				{
					attackPlayer((L2Attackable) npc, _standbyPlayer);
				}
				else
				{
					for (L2PcInstance pc : npc.getKnownList().getKnownPlayersInRadius(2000))
					{
						if (pc != null && !pc.isDead() && zone.isInsideZone(pc))
						{
							attackPlayer((L2Attackable) npc, pc);
							break;
						}
					}
				}
				break;
			}
			case "SELECT_TARGET":	// ARCHANGEL
			{
				final L2Attackable mob = (L2Attackable) npc;
				final L2Character mostHated = mob.getMostHated();
				
				if (mob.isDead() || !mob.isVisible())
				{
					cancelQuestTimer(event, mob, player);
					break;
				}
				if (_baium == null || _baium.isDead())
				{
					cancelQuestTimer(event, mob, player);
					mob.deleteMe();
					break;
				}
				
				if (mostHated != null && mostHated.isPlayer() && zone.isInsideZone(mostHated))
				{
					if (mob.getTarget() != mostHated)
					{
						mob.clearAggroList();
					}
					attackPlayer(mob, (L2Playable) mostHated);
				}
				else
				{
					boolean found = false;
					for (L2Object obj : mob.getKnownList().getKnownObjects().values())
					{
						if (obj instanceof L2Playable && Util.checkIfInRange(1000, mob, obj, false) && zone.isInsideZone(obj))
						{
							L2Playable pc = (L2Playable) obj;
							if (!pc.isDead())
							{
								if (mob.getTarget() != pc)
								{
									mob.clearAggroList();
								}
								attackPlayer(mob, pc);
								found = true;
								break;
							}
						}
					}
					
					if (!found)
					{
						if (mob.isInsideRadius(_baium, 40, true, false))
						{
							if (mob.getTarget() != _baium)
							{
								mob.clearAggroList();
							}
							mob.setIsRunning(true);
							mob.addDamageHate(_baium, 0, 999);
							mob.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, _baium);
						}
						else
						{
							mob.getAI().setIntention(CtrlIntention.AI_INTENTION_FOLLOW, _baium);
						}
					}
				}
				break;
			}
			case "CHECK_ATTACK":	// BAIUM
			{
				if (_lastAttack + INACTIVITY_TIME < System.currentTimeMillis())
				{
					cancelQuestTimers("CHECK_ATTACK"); //cancelQuestTimer(event, npc, player);
					_baium = null;
					notifyEvent("CLEAR_ZONE", null, null);
					notifyEvent("CLEAR_STATUS", null, null);
				//	addSpawn(BAIUM_STONE, BAIUM_LOC, false, 0);
				//	setStatus(ALIVE);
				}
				else
				{
					if (_lastAttack + 300000 < System.currentTimeMillis() && npc.getCurrentHp() < npc.getMaxHp() * 0.75)
					{
						npc.setTarget(npc);
						npc.doCast(getSkill(HEAL_OF_BAIUM));
					}
				}
				break;
			}
			case "CLEAR_STATUS":
			{
				setStatus(ALIVE);
				addSpawn(BAIUM_STONE, BAIUM_LOC, false, 0);
				break;
			}
			case "CLEAR_ZONE":
			{
//TODO:★★★★★cancelAllQuestTimers();
				for (L2Character charInside : zone.getCharactersInside())
				{
					if (charInside != null)
					{
						if (charInside.isNpc())
						{
							charInside.deleteMe();
						}
						else if (charInside.isPlayer())
						{
							notifyEvent("teleportOut", null, (L2PcInstance) charInside);
						}
					}
				}
				init();
				break;
			}
			case "RESPAWN_BAIUM":	// AdminGrandBoss.java "admin_grandboss_respawn 29020"
			{
				if (getStatus() == DEAD)
				{
					setRespawn(0);
					cancelQuestTimer("CLEAR_STATUS", null, null);
					notifyEvent("CLEAR_STATUS", null, null);
				}
				else
				{
					player.sendMessage(getClass().getSimpleName() + ": You cant respawn Baium while Baium is alive!");
				}
				break;
			}
			case "ABORT_FIGHT":	// AdminGrandBoss.java "admin_grandboss_abort 29020"
			{
				if (getStatus() == IN_FIGHT)
				{
					_baium = null;
					notifyEvent("CLEAR_ZONE", null, null);
					notifyEvent("CLEAR_STATUS", null, null);
					player.sendMessage(getClass().getSimpleName() + ": Aborting fight!");
				}
				else
				{
					player.sendMessage(getClass().getSimpleName() + ": You cant abort attack right now!");
				}
				cancelQuestTimers("CHECK_ATTACK");
				cancelQuestTimers("SELECT_TARGET");
				break;
			}
			case "DESPAWN_MINIONS":	// AdminGrandBoss.java "admin_grandboss_minions 29020"
			{
				if (getStatus() == IN_FIGHT)
				{
					for (L2Character charInside : zone.getCharactersInside())
					{
						if ((charInside != null) && charInside.isNpc() && (charInside.getId() == ARCHANGEL))
						{
							charInside.deleteMe();
						}
					}
					if (player != null)
					{
						player.sendMessage(getClass().getSimpleName() + ": All archangels has been deleted!");
					}
				}
				else if (player != null)
				{
					player.sendMessage(getClass().getSimpleName() + ": You cant despawn archangels right now!");
				}
				break;
			}
			case "MANAGE_SKILLS":	// BAIUM onSpellFinished
			{
				manageSkills(npc);
				break;
			}
		}
		return super.onAdvEvent(event, npc, player);
	}
	
	@Override
	public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon, Skill skill)
	{
		_lastAttack = System.currentTimeMillis();
		
		if (npc.getId() == BAIUM)
		{
			if ((attacker.getMountType() == MountType.STRIDER) && !attacker.isAffectedBySkill(getSkillId(ANTI_STRIDER)))
			{
				if (!npc.isSkillDisabled(getSkill(ANTI_STRIDER)))
				{
					npc.setTarget(attacker);
					npc.doCast(getSkill(ANTI_STRIDER));
				}
			}
			
			if (skill == null)
			{
				refreshAiParams(attacker, npc, damage * 1000);
			}
			else
			{
				final double hpRatio = npc.getCurrentHp() / npc.getMaxHp();
				if (hpRatio < 0.25)
				{
					refreshAiParams(attacker, npc, damage / 3 * 100);
				}
				else if (hpRatio < 0.5)
				{
					refreshAiParams(attacker, npc, damage / 3 * 60);
				}
				else if (hpRatio < 0.75)
				{
					refreshAiParams(attacker, npc, damage / 3 * 30);
				}
				else
				{
					refreshAiParams(attacker, npc, damage / 3 * 20);
				}
			}
			manageSkills(npc);
		}
		else // ARCHANGEL
		{
			final L2Attackable mob = (L2Attackable) npc;
			final L2Character mostHated = mob.getMostHated();
			
			if (getRandom(100) < 10 && mob.checkDoCastConditions(getSkill(SPEAR_ATTACK)))
			{
				if ((mostHated != null) && (npc.calculateDistance(mostHated, true, false) < 1000) && zone.isCharacterInZone(mostHated))
				{
					mob.setTarget(mostHated);
					mob.doCast(getSkill(SPEAR_ATTACK));
				}
				else if (zone.isCharacterInZone(attacker))
				{
					mob.setTarget(attacker);
					mob.doCast(getSkill(SPEAR_ATTACK));
				}
			}
			
			if (getRandom(100) < 5 && npc.getCurrentHp() < npc.getMaxHp() * 0.5 && mob.checkDoCastConditions(getSkill(ANGEL_HEAL)))
			{
				npc.setTarget(npc);
				npc.doCast(getSkill(ANGEL_HEAL));
			}
		}
		return super.onAttack(npc, attacker, damage, isSummon, skill);
	}
	
	@Override
	public String onKill(L2Npc npc, L2PcInstance killer, boolean isSummon)
	{
		// BAIUM
		if (zone.isCharacterInZone(killer))
		{
			cancelQuestTimers("CHECK_ATTACK");
			cancelQuestTimers("SELECT_TARGET");
			setStatus(DEAD);
			addSpawn(TELE_CUBE, TELEPORT_CUBIC_LOC, false/*, CLEAR_DELAY*/);
			long respawnTime = (Config.BAIUM_SPAWN_INTERVAL + getRandom(-Config.BAIUM_SPAWN_RANDOM, Config.BAIUM_SPAWN_RANDOM)) * 3600000;
			if (respawnTime < CLEAR_DELAY + 10000) respawnTime = CLEAR_DELAY + 10000;
			setRespawn(respawnTime);
			startQuestTimer("CLEAR_ZONE", CLEAR_DELAY, null, null);
			startQuestTimer("CLEAR_STATUS", respawnTime, null, null);
			zone.broadcastPacket(new PlaySound("BS01_D"));
		}
		return super.onKill(npc, killer, isSummon);
	}
	
	@Override
	public String onSeeCreature(L2Npc npc, L2Character creature, boolean isSummon)
	{
		// BAIUM
		if (!zone.isInsideZone(creature) || creature.isNpc() && creature.getId() == BAIUM_STONE)
		{
			return super.onSeeCreature(npc, creature, isSummon);
		}
		
	//	if (creature.isPlayer() && !creature.isDead() && (_standbyPlayer == null))
	//	{
	//		_standbyPlayer = (L2PcInstance) creature;		//[JOJO] "wakeUp" に移動
	//	}
		
		if (creature.isInCategory(CategoryType.CLERIC_GROUP))
		{
			final double hpRatio = npc.getCurrentHp() / npc.getMaxHp();
			if (hpRatio < 0.25)
			{
				refreshAiParams(creature, npc, 10000, 10000);
			}
			else if (hpRatio < 0.5)
			{
				refreshAiParams(creature, npc, 10000, 6000);
			}
			else if (hpRatio < 0.75)
			{
				refreshAiParams(creature, npc, 10000, 3000);
			}
			else
			{
				refreshAiParams(creature, npc, 10000, 2000);
			}
		}
		else
		{
			refreshAiParams(creature, npc, 10000, 1000);
		}
		manageSkills(npc);
		return super.onSeeCreature(npc, creature, isSummon);
	}
	
	@Override
	public String onSpawn(L2Npc npc)
	{
		// BAIUM, ARCHANGEL
		if (!npc.isTeleporting())
		{
			if (npc.getNpcId() == BAIUM)
				_aiLocks = new WeakHashMap<>();
			_aiLocks.put(npc, new ReentrantLock());
		}
		return super.onSpawn(npc);
	}
	
	@Override
	public String onSpellFinished(L2Npc npc, L2PcInstance player, Skill skill)
	{
		// BAIUM
		startQuestTimer("MANAGE_SKILLS", 1000, npc, null);
		
		if (!zone.isCharacterInZone(npc) && (_baium != null))
		{
			_baium.teleToLocation(BAIUM_LOC);
		}
		return super.onSpellFinished(npc, player, skill);
	}
	
	@Override
	public boolean unload(boolean removeFromList)
	{
		cancelAllQuestTimers();
		onAdvEvent("CLEAR_ZONE", null, null);
	//	if (_baium != null)
	//	{
	//		_baium.deleteMe();
	//	}
		return super.unload(removeFromList);
	}
	
	private final void refreshAiParams(L2Character attacker, L2Npc npc, int damage)
	{
		refreshAiParams(attacker, npc, damage, damage);
	}
	
	private final void refreshAiParams(L2Character attacker, L2Npc npc, int damage, int aggro)
	{
		final int newAggroVal = damage + getRandom(3000);
		final int aggroVal = aggro + 1000;
		final NpcVariables npcVariables = npc.getVariables();
		NpcHateVariables vars;
		if ((vars = npcVariables.getObject("Hate", NpcHateVariables.class)) == null)
		{
			vars = new NpcHateVariables();
			npcVariables.set("Hate", vars);
		}
		for (int i = 0; i < 3; i++)
		{
			if (attacker == vars.attacker[i])
			{
				if (vars.hate[i] < aggroVal)
					vars.hate[i] = newAggroVal;
				return;
			}
		}
		final int index = Util.getIndexOfMinValue(vars.hate);
		vars.hate[index] = newAggroVal;
		vars.attacker[index] = attacker;
	}
	
	private final L2Character manageAiParam(L2Npc npc)
	{
		final NpcVariables npcVariables = npc.getVariables();
		final NpcHateVariables vars = npcVariables.getObject("Hate", NpcHateVariables.class);
		for (int i = 0; i < 3; i++)
		{
			final L2Character attacker = vars.attacker[i];
			if (attacker != null && (npc.calculateDistance(attacker, true, false) > 9000 || attacker.isDead()))
				vars.hate[i] = 0;
		}
		final int index = Util.getIndexOfMaxValue(vars.hate);
		if (vars.hate[index] > 0 && getRandom(100) < 70)
			vars.hate[index] = 500;
		return vars.attacker[index];
	}
	
	static class NpcHateVariables
	{
		L2Character[] attacker = new L2Character[3];	// "c_quest0","c_quest1","c_quest2"
		int[] hate = new int[3];						// "i_quest0","i_quest1","i_quest2"
	}
	
	private int getStatus()
	{
		return GrandBossManager.getInstance().getBossStatus(BAIUM);
	}
	
	private void addBoss(L2GrandBossInstance grandboss)
	{
		GrandBossManager.getInstance().addBoss(grandboss);
	}
	
	private void setStatus(int status)
	{
		GrandBossManager.getInstance().setBossStatus(BAIUM, status);
	}
	
	private void setRespawn(long respawnTime)
	{
		GrandBossManager.getInstance().getStatsSet(BAIUM).set("respawn_time", (System.currentTimeMillis() + respawnTime));
	}
	
	private void manageSkills(L2Npc npc)
	{
		if (npc.isCastingNow() || npc.isCoreAIDisabled() || !npc.isInCombat())
		{
			return;
		}
		
		final ReentrantLock lock;
		if ((lock = _aiLocks.get(npc)).tryLock()) try
		{
			final L2Character target = manageAiParam(npc);
			if (target == null || target.isDead())
			{
				return;
			}
			
			final int skillToCast;
			int rnd = getRandom(100);
			final double hpRatio = npc.getCurrentHp() / npc.getMaxHp();
			if (hpRatio > 0.75)
			{
				if ((rnd -= 10) < 0)
				{
					skillToCast = ENERGY_WAVE;
				}
				else if ((rnd -= 10) < 0)
				{
					skillToCast = EARTH_QUAKE;
				}
				else
				{
					skillToCast = BAIUM_ATTACK;
				}
			}
			else if (hpRatio > 0.5)
			{
				if ((rnd -= 10) < 0)
				{
					skillToCast = GROUP_HOLD;
				}
				else if ((rnd -= 10) < 0)
				{
					skillToCast = ENERGY_WAVE;
				}
				else if ((rnd -= 10) < 0)
				{
					skillToCast = EARTH_QUAKE;
				}
				else
				{
					skillToCast = BAIUM_ATTACK;
				}
			}
			else if (hpRatio > 0.25)
			{
				if ((rnd -= 10) < 0)
				{
					skillToCast = THUNDERBOLT;
				}
				else if ((rnd -= 10) < 0)
				{
					skillToCast = GROUP_HOLD;
				}
				else if ((rnd -= 10) < 0)
				{
					skillToCast = ENERGY_WAVE;
				}
				else if ((rnd -= 10) < 0)
				{
					skillToCast = EARTH_QUAKE;
				}
				else
				{
					skillToCast = BAIUM_ATTACK;
				}
			}
			else
			{
				if ((rnd -= 10) < 0)
				{
					skillToCast = THUNDERBOLT;
				}
				else if ((rnd -= 10) < 0)
				{
					skillToCast = GROUP_HOLD;
				}
				else if ((rnd -= 10) < 0)
				{
					skillToCast = ENERGY_WAVE;
				}
				else if ((rnd -= 10) < 0)
				{
					skillToCast = EARTH_QUAKE;
				}
				else
				{
					skillToCast = BAIUM_ATTACK;
				}
			}
			
			final Skill skill = getSkill(skillToCast);
			if (npc.checkDoCastConditions(skill))
			{
				npc.setTarget(target);
				npc.doCast(skill);
			}
		}
		finally { lock.unlock(); }
	}
	
	public static void main(String[] args)
	{
		new Baium();
	}
}