package com.aionemu.gameserver.model.gameobjects;

import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.aionemu.gameserver.controllers.NpcController;
import com.aionemu.gameserver.controllers.movement.NpcMoveController;
import com.aionemu.gameserver.dataholders.DataManager;
import com.aionemu.gameserver.dataholders.loadingutils.adapters.NpcEquipmentList;
import com.aionemu.gameserver.model.CreatureType;
import com.aionemu.gameserver.model.DialogAction;
import com.aionemu.gameserver.model.Race;
import com.aionemu.gameserver.model.TribeClass;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.items.NpcEquippedGear;
import com.aionemu.gameserver.model.skill.NpcSkillEntry;
import com.aionemu.gameserver.model.skill.NpcSkillList;
import com.aionemu.gameserver.model.skill.NpcSkillTemplateEntry;
import com.aionemu.gameserver.model.stats.container.NpcGameStats;
import com.aionemu.gameserver.model.stats.container.NpcLifeStats;
import com.aionemu.gameserver.model.templates.item.ItemAttackType;
import com.aionemu.gameserver.model.templates.npc.*;
import com.aionemu.gameserver.model.templates.npcskill.NpcSkillTargetAttribute;
import com.aionemu.gameserver.model.templates.npcskill.QueuedNpcSkillTemplate;
import com.aionemu.gameserver.model.templates.spawns.SpawnTemplate;
import com.aionemu.gameserver.network.aion.serverpackets.SM_CUSTOM_SETTINGS;
import com.aionemu.gameserver.network.aion.serverpackets.SM_LOOKATOBJECT;
import com.aionemu.gameserver.services.TribeRelationService;
import com.aionemu.gameserver.skillengine.effect.SummonOwner;
import com.aionemu.gameserver.spawnengine.WalkerGroup;
import com.aionemu.gameserver.spawnengine.WalkerGroupShift;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.PositionUtil;
import com.aionemu.gameserver.utils.idfactory.IDFactory;
import com.aionemu.gameserver.world.WorldPosition;

/**
 * This class is a base class for all in-game NPCs, what includes: monsters and npcs that player can talk to (aka Citizens)
 * 
 * @author Luno
 */
public class Npc extends Creature {

	private WalkerGroup walkerGroup;
	private NpcSkillList skillList;
	private ConcurrentLinkedQueue<NpcSkillEntry> queuedSkills;
	private WalkerGroupShift walkerGroupShift;
	private String masterName;
	private int creatorId = 0;
	private int townId;
	private CreatureType type = null;
	private ItemAttackType attacktype = ItemAttackType.PHYSICAL;
	private NpcEquippedGear overridenEquipment;
	private SummonOwner summonOwner = null;

	public Npc(NpcController controller, SpawnTemplate spawnTemplate, NpcTemplate objectTemplate) {
		super(IDFactory.getInstance().nextId(), controller, spawnTemplate, objectTemplate, new WorldPosition(spawnTemplate.getWorldId()), true);
		Objects.requireNonNull(objectTemplate, "Npcs should be based on template");
		controller.setOwner(this);
		moveController = new NpcMoveController(this);
		skillList = new NpcSkillList(this);
		queuedSkills = new ConcurrentLinkedQueue<>();
		setupStatContainers();
	}

	@Override
	public NpcMoveController getMoveController() {
		return (NpcMoveController) super.getMoveController();
	}

	protected void setupStatContainers() {
		setGameStats(new NpcGameStats(this));
		setLifeStats(new NpcLifeStats(this));
	}

	@Override
	public NpcTemplate getObjectTemplate() {
		return (NpcTemplate) super.getObjectTemplate();
	}

	public int getNpcId() {
		return getObjectTemplate().getTemplateId();
	}

	@Override
	public byte getLevel() {
		return getObjectTemplate().getLevel();
	}

	public AbyssNpcType getAbyssNpcType() {
		return getObjectTemplate().getAbyssNpcType();
	}

	public NpcRating getRating() {
		return getObjectTemplate().getRating();
	}

	public NpcRank getRank() {
		return getObjectTemplate().getRank();
	}

	public NpcTemplateType getNpcTemplateType() {
		return getObjectTemplate().getNpcTemplateType();
	}

	public int getHpGauge() {
		return getObjectTemplate().getHpGauge();
	}

	@Override
	public NpcLifeStats getLifeStats() {
		return (NpcLifeStats) super.getLifeStats();
	}

	@Override
	public NpcGameStats getGameStats() {
		return (NpcGameStats) super.getGameStats();
	}

	@Override
	public NpcController getController() {
		return (NpcController) super.getController();
	}

	@Override
	public ItemAttackType getAttackType() {
		return getAi().modifyAttackType(attacktype);
	}

	public NpcSkillList getSkillList() {
		return skillList;
	}

	public ConcurrentLinkedQueue<NpcSkillEntry> getQueuedSkills() {
		return queuedSkills;
	}

	public void clearQueuedSkills() {
		queuedSkills.clear();
	}

	public void queueSkill(int skillId, int level) {
		queuedSkills.offer(new NpcSkillTemplateEntry(new QueuedNpcSkillTemplate(skillId, level)));
	}

	public void queueSkill(int skillId, int level, int nextSkillTime) {
		queuedSkills.offer(new NpcSkillTemplateEntry(new QueuedNpcSkillTemplate(skillId, level, nextSkillTime, NpcSkillTargetAttribute.MOST_HATED)));
	}

	public void queueSkill(int skillId, int level, int nextSkillTime, NpcSkillTargetAttribute npcSkillTargetAttribute) {
		queuedSkills.offer(new NpcSkillTemplateEntry(new QueuedNpcSkillTemplate(skillId, level, nextSkillTime, npcSkillTargetAttribute)));
	}

	public boolean isWalker() {
		return isRandomWalker() || isPathWalker();
	}

	public boolean isRandomWalker() {
		return getSpawn().getRandomWalkRange() > 0;
	}

	public boolean isPathWalker() {
		return getSpawn().getWalkerId() != null;
	}

	@Override
	public TribeClass getTribe() {
		TribeClass transformTribe = isTransformed() ? getTransformModel().getTribe() : null;
		if (transformTribe != null) {
			return transformTribe;
		}
		return getObjectTemplate().getTribe();
	}

	@Override
	public TribeClass getBaseTribe() {
		return DataManager.TRIBE_RELATIONS_DATA.getBaseTribe(getTribe());
	}

	public int getAggroRange() {
		return getAi().modifyAggroRange(getObjectTemplate().getAggroRange());
	}

	public int getShortAggroRange() {
		int aggroRange = getAggroRange();
		return aggroRange < 8 ? aggroRange / 2 : 4;
	}

	public int getAggroAngle() {
		return getAi().modifyAggroAngle(getObjectTemplate().getAggroAngle());
	}

	/**
	 * @return True if the npc is within 1m of it's spawn location
	 */
	public boolean isAtSpawnLocation() {
		return PositionUtil.isInRange(this, getSpawn().getX(), getSpawn().getY(), getSpawn().getZ(), 1);
	}

	@Override
	public boolean isEnemy(Creature creature) {
		return creature.isEnemyFrom(this) || this.isEnemyFrom(creature);
	}

	@Override
	public boolean isEnemyFrom(Creature creature) {
		return TribeRelationService.isAggressive(creature, this) || TribeRelationService.isHostile(creature, this);
	}

	@Override
	public boolean isEnemyFrom(Npc npc) {
		return TribeRelationService.isAggressive(this, npc) || TribeRelationService.isHostile(this, npc);
	}

	@Override
	public boolean isEnemyFrom(Player player) {
		return player.isEnemyFrom(this);
	}

	public CreatureType getType(Creature creature) {
		if (type != null)
			return type;
		if (TribeRelationService.isNone(this, creature))
			return CreatureType.PEACE;
		else if (TribeRelationService.isAggressive(this, creature))
			return CreatureType.AGGRESSIVE;
		else if (TribeRelationService.isHostile(this, creature))
			return CreatureType.ATTACKABLE;
		else if (TribeRelationService.isFriend(this, creature) || TribeRelationService.isNeutral(this, creature))
			return CreatureType.FRIEND;
		else if (TribeRelationService.isSupport(this, creature))
			return CreatureType.SUPPORT;
		return CreatureType.ATTACKABLE;
	}

	/**
	 * Sets a constant type and broadcasts it, if the npc is spawned. Set to null, to disable it.
	 */
	public void overrideNpcType(CreatureType newType) {
		type = newType;
		if (isSpawned()) {
			if (type != null)
				PacketSendUtility.broadcastPacket(this, new SM_CUSTOM_SETTINGS(getObjectId(), 0, type.getId(), 0));
			else
				getKnownList().forEachPlayer(p -> PacketSendUtility.sendPacket(p, new SM_CUSTOM_SETTINGS(getObjectId(), 0, getType(p).getId(), 0)));
		}
	}

	/**
	 * @return distance to spawn location
	 */
	public double getDistanceToSpawnLocation() {
		return PositionUtil.getDistance(getSpawn().getX(), getSpawn().getY(), getSpawn().getZ(), getX(), getY(), getZ());
	}

	@Override
	public int getSeeState() {
		int skillSeeState = super.getSeeState();
		int congenitalSeeState = getObjectTemplate().getRating().getCongenitalSeeState().getId();
		return Math.max(skillSeeState, congenitalSeeState);
	}

	/**
	 * @return Name of the Master
	 */
	public String getMasterName() {
		return masterName;
	}

	public void setMasterName(String masterName) {
		this.masterName = masterName;
	}

	/**
	 * @return UniqueId of the VisibleObject which created this Npc (could be player or house)
	 */
	public int getCreatorId() {
		return creatorId;
	}

	public void setCreatorId(int creatorId) {
		this.creatorId = creatorId;
	}

	public int getTownId() {
		return townId;
	}

	public void setTownId(int townId) {
		this.townId = townId;
	}

	public VisibleObject getCreator() {
		return null;
	}

	@Override
	public void setTarget(VisibleObject creature) {
		if (getTarget() != creature) {
			super.setTarget(creature);
			super.clearAttackedCount();
			getGameStats().renewLastChangeTargetTime();
			if (!isDead()) {
				if (creature != null && !this.equals(creature))
					getPosition().setH(PositionUtil.getHeadingTowards(this, creature));
				PacketSendUtility.broadcastPacket(this, new SM_LOOKATOBJECT(this));
			}
		}
	}

	public void setWalkerGroup(WalkerGroup wg) {
		this.walkerGroup = wg;
	}

	public WalkerGroup getWalkerGroup() {
		return walkerGroup;
	}

	public void setWalkerGroupShift(WalkerGroupShift shift) {
		this.walkerGroupShift = shift;
	}

	public WalkerGroupShift getWalkerGroupShift() {
		return walkerGroupShift;
	}

	@Override
	public boolean isFlag() {
		return getObjectTemplate().getNpcTemplateType() == NpcTemplateType.FLAG;
	}

	@Override
	public boolean isRaidMonster() {
		return getObjectTemplate().getNpcTemplateType() == NpcTemplateType.RAID_MONSTER;
	}

	public boolean isBoss() {
		return getObjectTemplate().getRating() == NpcRating.HERO || getObjectTemplate().getRating() == NpcRating.LEGENDARY;
	}

	public boolean hasStatic() {
		return getSpawn().getStaticId() != 0;
	}

	@Override
	public Race getRace() {
		return getObjectTemplate().getRace();
	}

	/**
	 * @return True if this npc sells items.
	 */
	public boolean canSell() {
		return DataManager.TRADE_LIST_DATA.getTradeListTemplate(getNpcId()) != null && getObjectTemplate().supportsAction(DialogAction.BUY);
	}

	/**
	 * @return True if this npc buys items.
	 */
	public boolean canBuy() {
		return getObjectTemplate().supportsAction(DialogAction.SELL) || canSell();
	}

	/**
	 * @return True if this npc trades items for other items.
	 */
	public boolean canTradeIn() {
		return DataManager.TRADE_LIST_DATA.getTradeInListTemplate(getNpcId()) != null && getObjectTemplate().supportsAction(DialogAction.TRADE_IN);
	}

	/**
	 * @return True if this npc buys specific items.
	 */
	public boolean canPurchase() {
		return DataManager.TRADE_LIST_DATA.getPurchaseTemplate(getNpcId()) != null && getObjectTemplate().supportsAction(DialogAction.TRADE_SELL_LIST);
	}

	public GroupDropType getGroupDrop() {
		return getObjectTemplate().getGroupDrop();
	}

	@Override
	public float getVisibleDistance() {
		return isFlag() || isRaidMonster() ? Float.MAX_VALUE : super.getVisibleDistance();
	}

	public void overrideEquipmentList(NpcEquipmentList v) {
		overridenEquipment = new NpcEquippedGear(v);
	}

	@Override
	public NpcEquippedGear getOverrideEquipment() {
		if (overridenEquipment != null)
			return overridenEquipment;
		return getObjectTemplate().getEquipment();
	}

	public void setSummonOwner(SummonOwner summonOwner) {
		this.summonOwner = summonOwner;
	}

	public SummonOwner getSummonOwner() {
		return summonOwner;
	}
}
