content: /**
   * This file is part of Aion-Lightning <aion-lightning.org>.
   *
   *  Aion-Lightning 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.
   *
   *  Aion-Lightning 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 Aion-Lightning.
   *  If not, see <http://www.gnu.org/licenses/>.
   */
  package com.aionemu.gameserver.services.base;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.Future;
  import java.util.concurrent.atomic.AtomicBoolean;
  
  import com.aionemu.commons.callbacks.EnhancedObject;
  import com.aionemu.commons.utils.Rnd;
  import com.aionemu.gameserver.ai2.AbstractAI;
  import com.aionemu.gameserver.configs.main.BaseConfig;
  import com.aionemu.gameserver.controllers.NpcController;
  import com.aionemu.gameserver.dataholders.DataManager;
  import com.aionemu.gameserver.model.Race;
  import com.aionemu.gameserver.model.TaskId;
  import com.aionemu.gameserver.model.base.BaseLocation;
  import com.aionemu.gameserver.model.gameobjects.Npc;
  import com.aionemu.gameserver.model.gameobjects.player.Player;
  import com.aionemu.gameserver.model.templates.npc.NpcTemplate;
  import com.aionemu.gameserver.model.templates.npc.NpcTemplateType;
  import com.aionemu.gameserver.model.templates.spawns.SpawnGroup2;
  import com.aionemu.gameserver.model.templates.spawns.SpawnTemplate;
  import com.aionemu.gameserver.model.templates.spawns.basespawns.BaseSpawnTemplate;
  import com.aionemu.gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE;
  import com.aionemu.gameserver.services.BaseService;
  import com.aionemu.gameserver.spawnengine.SpawnEngine;
  import com.aionemu.gameserver.spawnengine.SpawnHandlerType;
  import com.aionemu.gameserver.utils.PacketSendUtility;
  import com.aionemu.gameserver.utils.ThreadPoolManager;
  import com.aionemu.gameserver.world.MapRegion;
  import com.aionemu.gameserver.world.World;
  import com.aionemu.gameserver.world.knownlist.Visitor;
  
  import javolution.util.FastList;
  
  /**
   * @author Source
   */
  public class Base<BL extends BaseLocation> {
  
  	private Future<?> startAssault, stopAssault;
  	private final BL baseLocation;
  	private List<Race> list = new ArrayList<>();
  	private final BossDeathListener bossDeathListener = new BossDeathListener(this);
  	private List<Npc> attackers = new ArrayList<>();
  	private final AtomicBoolean finished = new AtomicBoolean();
  	private boolean started;
  	private Npc boss, flag;
  
  	public Base(BL baseLocation) {
  		list.add(Race.ASMODIANS);
  		list.add(Race.ELYOS);
  		list.add(Race.NPC);
  		this.baseLocation = baseLocation;
  	}
  
  	public final void start() {
  
  		boolean doubleStart = false;
  
  		synchronized (this) {
  			if (started) {
  				doubleStart = true;
  			}
  			else {
  				started = true;
  			}
  		}
  
  		if (!doubleStart) {
  			spawn();
  		}
  	}
  
  	public final void stop() {
  		if (finished.compareAndSet(false, true)) {
  			if (getBoss() != null) {
  				rmvBossListener();
  			}
  			despawn();
  		}
  	}
  
  	private List<SpawnGroup2> getBaseSpawns() {
  		List<SpawnGroup2> spawns = DataManager.SPAWNS_DATA2.getBaseSpawnsByLocId(getId());
  
  		if (spawns == null) {
  			throw new NullPointerException("No spawns for base:" + getId());
  		}
  
  		return spawns;
  	}
  
  	protected void spawn() {
  		for (SpawnGroup2 group : getBaseSpawns()) {
  			for (SpawnTemplate spawn : group.getSpawnTemplates()) {
  				final BaseSpawnTemplate template = (BaseSpawnTemplate) spawn;
  				if (template.getBaseRace().equals(getRace())) {
  					if (template.getHandlerType() == null) {
  						Npc npc = (Npc) SpawnEngine.spawnObject(template, 1);
  						NpcTemplate npcTemplate = npc.getObjectTemplate();
  						if (npcTemplate.getNpcTemplateType().equals(NpcTemplateType.FLAG)) {
  							setFlag(npc);
  							MapRegion mr = npc.getPosition().getMapRegion();
  							mr.activate();
  						}
  					}
  				}
  			}
  		}
  
  		delayedAssault();
  		delayedSpawn(getRace());
  	}
  
  	private void delayedAssault() {
  		startAssault = ThreadPoolManager.getInstance().schedule(new Runnable() {
  
  			@Override
  			public void run() {
  				chooseAttackersRace();
  				sendLDF4AdvanceMsgKiller(getId()); // Akaron Base Message
  			}
  		}, Rnd.get(BaseConfig.ASSAULT_MIN_DELAY, BaseConfig.ASSAULT_MAX_DELAY) * 60000); // Randomly every 15 - 20 min start assault
  	}
  
  	// Akaron Base Message
  	public boolean sendLDF4AdvanceMsgKiller(int id) {
  		switch (id) {
  			case 100:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v13);
  					}
  				});
  				return true;
  			case 101:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v04);
  					}
  				});
  				return true;
  			case 102:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v12);
  					}
  				});
  				return true;
  			case 103:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v03);
  					}
  				});
  				return true;
  			case 104:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v06);
  					}
  				});
  				return true;
  			case 105:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v05);
  					}
  				});
  				return true;
  			case 106:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v01);
  					}
  				});
  				return true;
  			case 107:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v09);
  					}
  				});
  				return true;
  			case 108:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v11);
  					}
  				});
  				return true;
  			case 109:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v10);
  					}
  				});
  				return true;
  			case 110:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v07);
  					}
  				});
  				return true;
  			case 111:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v02);
  					}
  				});
  				return true;
  			case 112:
  				World.getInstance().doOnAllPlayers(new Visitor<Player>() {
  
  					@Override
  					public void visit(Player player) {
  						PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.STR_MSG_LDF4_Advance_killer_v08);
  					}
  				});
  				return true;
  			default:
  				return false;
  		}
  	}
  
  	private void delayedSpawn(final Race race) {
  		ThreadPoolManager.getInstance().schedule(new Runnable() {
  
  			@Override
  			public void run() {
  				if (getRace().equals(race) && getBoss() == null) {
  					spawnBoss();
  				}
  			}
  		}, Rnd.get(BaseConfig.BOSS_SPAWN_MIN_DELAY, BaseConfig.BOSS_SPAWN_MAX_DELAY) * 60000); // Boss spawn between 30 min and 4 hours delay on retail
  	}
  
  	protected void spawnBoss() {
  		for (SpawnGroup2 group : getBaseSpawns()) {
  			for (SpawnTemplate spawn : group.getSpawnTemplates()) {
  				final BaseSpawnTemplate template = (BaseSpawnTemplate) spawn;
  				if (template.getBaseRace().equals(getRace())) {
  					if (template.getHandlerType() != null && template.getHandlerType().equals(SpawnHandlerType.BOSS)) {
  						Npc npc = (Npc) SpawnEngine.spawnObject(template, 1);
  						setBoss(npc);
  						addBossListeners();
  					}
  				}
  			}
  		}
  	}
  
  	protected void chooseAttackersRace() {
  		AtomicBoolean next = new AtomicBoolean(Math.random() < 0.5);
  		for (Race race : list) {
  			if (race == null) {
  				throw new NullPointerException("Base:" + race + " race is null chooseAttackersRace!");
  			}
  			else if (!race.equals(getRace())) {
  				if (next.compareAndSet(true, false)) {
  					continue;
  				}
  				spawnAttackers(race);
  			}
  		}
  	}
  
  	public void spawnAttackers(Race race) {
  		if (getFlag() == null && !isNoFlag(getId())) {
  			throw new NullPointerException("Base:" + getId() + " flag is null!");
  		}
  		else if (!getFlag().getPosition().getMapRegion().isMapRegionActive()) {
  			// 20% chance to capture base in not active region by invaders assault
  			Race CurrentRace = getFlag().getRace();
  			if (Math.random() < 0.2 && !race.equals(CurrentRace)) {
  				BaseService.getInstance().capture(getId(), race);
  			}
  			else {
  				// Next attack
  				delayedAssault();
  			}
  			return;
  		}
  
  		if (!isAttacked()) {
  			despawnAttackers();
  
  			for (SpawnGroup2 group : getBaseSpawns()) {
  				for (SpawnTemplate spawn : group.getSpawnTemplates()) {
  					final BaseSpawnTemplate template = (BaseSpawnTemplate) spawn;
  					if (template.getBaseRace().equals(race)) {
  						if (template.getHandlerType() != null && template.getHandlerType().equals(SpawnHandlerType.ATTACKER)) {
  							Npc npc = (Npc) SpawnEngine.spawnObject(template, 1);
  							getAttackers().add(npc);
  						}
  					}
  				}
  			}
  
  			// Since patch 4.7 in kaldor are siege important bases that only have balaur attackers for back occupying.
  			if (getAttackers().isEmpty() && !isOnlyBalaur(getId())) {
  				throw new NullPointerException("No attackers was found for base:" + getId());
  			}
  			else {
  				stopAssault = ThreadPoolManager.getInstance().schedule(new Runnable() {
  
  					@Override
  					public void run() {
  						despawnAttackers();
  
  						// Next attack
  						delayedAssault();
  					}
  				}, 5 * 60000); // After 5 min attackers despawned
  			}
  		}
  	}
  
  	public boolean isOnlyBalaur(int id) {
  		switch (id) {
  			case 90: // Stonereach Outpost
  				return true;
  			case 91: // Flamecrest Outpost
  				return true;
  			case 134: // Rattlefrost Outpost
  				return true;
  			case 135: // Sliversleet Outpost
  				return true;
  			case 136: // Coldforge Outpost
  				return true;
  			case 137: // Shimmerfrost Outpost
  				return true;
  			case 138: // Icehowl Outpost
  				return true;
  			case 139: // Chillhaunt Outpost
  				return true;
  			case 140: // Wildersage Artifact Outpost
  				return true;
  			case 141: // Dauntless Artifact Outpost
  				return true;
  			case 142: // Anchorbrak Artifact Outpost
  				return true;
  			case 143: // Brokenblade Artifact Outpost
  				return true;
  			case 144: // Sootguzzle Outpost
  				return true;
  			case 145: // Flameruin Outpost
  				return true;
  			case 146: // Stokebellow Outpost
  				return true;
  			case 147: // Blazerack Outpost
  				return true;
  			case 148: // Smoldergeist Outpost
  				return true;
  			case 149: // Moltenspike Outpost
  				return true;
  			default:
  				return false;
  		}
  	}
  
  	public boolean isNoFlag(int id) {
  		switch (id) {
  			case 140: // Wildersage Artifact Outpost
  				return true;
  			case 141: // Dauntless Artifact Outpost
  				return true;
  			case 142: // Anchorbrak Artifact Outpost
  				return true;
  			case 143: // Brokenblade Artifact Outpost
  				return true;
  			default:
  				return false;
  		}
  	}
  
  	public boolean isAttacked() {
  		for (Npc attacker : getAttackers()) {
  			if (!attacker.getLifeStats().isAlreadyDead()) {
  				return true;
  			}
  		}
  		return false;
  	}
  
  	protected void despawn() {
  		setFlag(null);
  
  		FastList<Npc> spawned = World.getInstance().getBaseSpawns(getId());
  		if (spawned != null) {
  			for (Npc npc : spawned) {
  				npc.getController().onDelete();
  			}
  		}
  
  		if (startAssault != null) {
  			startAssault.cancel(true);
  		}
  
  		if (stopAssault != null) {
  			stopAssault.cancel(true);
  			despawnAttackers();
  		}
  	}
  
  	protected void despawnAttackers() {
  		NpcController controller;
  		for (Npc attacker : getAttackers()) {
  			controller = attacker.getController();
  			if (null != controller)// despawn fix
  			{
  				controller.cancelTask(TaskId.RESPAWN);
  				controller.onDelete();
  			}
  		}
  		getAttackers().clear();
  	}
  
  	protected void addBossListeners() {
  		AbstractAI ai = (AbstractAI) getBoss().getAi2();
  		EnhancedObject eo = (EnhancedObject) ai;
  		eo.addCallback(getBossListener());
  	}
  
  	protected void rmvBossListener() {
  		AbstractAI ai = (AbstractAI) getBoss().getAi2();
  		EnhancedObject eo = (EnhancedObject) ai;
  		eo.removeCallback(getBossListener());
  	}
  
  	public Npc getFlag() {
  		return flag;
  	}
  
  	public void setFlag(Npc flag) {
  		this.flag = flag;
  	}
  
  	public Npc getBoss() {
  		return boss;
  	}
  
  	public void setBoss(Npc boss) {
  		this.boss = boss;
  	}
  
  	public BossDeathListener getBossListener() {
  		return bossDeathListener;
  	}
  
  	public boolean isFinished() {
  		return finished.get();
  	}
  
  	public BL getBaseLocation() {
  		return baseLocation;
  	}
  
  	public int getId() {
  		return baseLocation.getId();
  	}
  
  	public Race getRace() {
  		return baseLocation.getRace();
  	}
  
  	public void setRace(Race race) {
  		baseLocation.setRace(race);
  	}
  
  	public List<Npc> getAttackers() {
  		return attackers;
  	}
  }
  