/**
 * Tibia GIMUD Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
 *
 * 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 of the License, 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifndef FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0
#define FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0

#include "tile.h"
#include "monsters.h"

class Creature;
class Game;
class Spawn;
class Combat;

typedef std::unordered_set<Creature*> CreatureHashSet;
typedef std::list<Creature*> CreatureList;

enum TargetSearchType_t {
	TARGETSEARCH_ANY,
	TARGETSEARCH_RANDOM,
	TARGETSEARCH_NEAREST,
	TARGETSEARCH_WEAKEST,
	TARGETSEARCH_MOSTDAMAGE,
};

class Monster final : public Creature
{
	public:
		static Monster* createMonster(const std::string& name);
		static int32_t despawnRange;
		static int32_t despawnRadius;

		explicit Monster(MonsterType* mtype);
		~Monster();

		// non-copyable
		Monster(const Monster&) = delete;
		Monster& operator=(const Monster&) = delete;

		Monster* getMonster() final {
			return this;
		}
		const Monster* getMonster() const final {
			return this;
		}

		void setID() final {
			if (id == 0) {
				id = monsterAutoID++;
			}
		}

		void removeList() final;
		void addList() final;

		const std::string& getName() const final {
			return mType->name;
		}
		const std::string& getNameDescription() const final {
			return mType->nameDescription;
		}
		std::string getDescription(int32_t) const final {
			return strDescription + '.';
		}

		const Position& getMasterPos() const {
			return masterPos;
		}
		void setMasterPos(Position pos) {
			masterPos = pos;
		}

		RaceType_t getRace() const final {
			return mType->info.race;
		}
		int32_t getArmor() const final {
			int32_t armor = mType->info.armor;
			if (armor > 1) {
				return rand() % (mType->info.armor >> 1) + (mType->info.armor >> 1);
			}

			return armor;
		}
		int32_t getDefense() final;
		bool isPushable() const final {
			return mType->info.pushable && baseSpeed != 0;
		}
		bool isAttackable() const final {
			return mType->info.isAttackable;
		}

		bool canPushItems() const {
			return mType->info.canPushItems;
		}
		bool canPushCreatures() const {
			return mType->info.canPushCreatures;
		}
		bool isHostile() const {
			return mType->info.isHostile;
		}
		bool canSee(const Position& pos) const final;
		bool canSeeInvisibility() const final {
			return isImmune(CONDITION_INVISIBLE);
		}
		uint32_t getManaCost() const {
			return mType->info.manaCost;
		}
		void setSpawn(Spawn* spawn) {
			this->spawn = spawn;
		}

		void onAttackedCreature(Creature* creature) final;

		void onCreatureAppear(Creature* creature, bool isLogin) final;
		void onRemoveCreature(Creature* creature, bool isLogout) final;
		void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) final;
		void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) final;

		void drainHealth(Creature* attacker, int32_t damage) final;
		void changeHealth(int32_t healthChange, bool sendHealthChange = true) final;
		void onWalk() final;
		bool getNextStep(Direction& direction, uint32_t& flags) final;
		void onFollowCreatureComplete(const Creature* creature) final;

		void onThink(uint32_t interval) final;

		bool challengeCreature(Creature* creature) final;
		bool convinceCreature(Creature* creature) final;

		void setNormalCreatureLight() final;
		bool getCombatValues(int32_t& min, int32_t& max) final;

		void doAttacking(uint32_t interval) final;

		bool searchTarget(TargetSearchType_t searchType);
		bool selectTarget(Creature* creature);

		const CreatureList& getTargetList() const {
			return targetList;
		}
		const CreatureHashSet& getFriendList() const {
			return friendList;
		}

		bool isTarget(const Creature* creature) const;
		bool isFleeing() const {
			return !isSummon() && getHealth() <= mType->info.runAwayHealth;
		}

		bool getDistanceStep(const Position& targetPos, Direction& direction, bool flee = false);
		bool isTargetNearby() const {
			return stepDuration >= 1;
		}

		BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage,
		                     bool checkDefense = false, bool checkArmor = false, bool field = false);

		static uint32_t monsterAutoID;

	private:
		CreatureHashSet friendList;
		CreatureList targetList;

		std::string strDescription;

		MonsterType* mType;
		Spawn* spawn = nullptr;

		int64_t lifetime = 0;
		int64_t nextDanceStepRound = 0;
		int64_t earliestAttackTime = 0;
		int64_t earliestWakeUpTime = 0;
		int64_t earliestDanceTime = 0;

		uint32_t targetChangeTicks = 0;
		int32_t minCombatValue = 0;
		int32_t maxCombatValue = 0;
		int32_t targetChangeCooldown = 0;
		int32_t stepDuration = 0;

		Position masterPos;

		bool isIdle = true;
		bool isMasterInRange = false;
		bool egibleToDance = true;

		void onCreatureEnter(Creature* creature);
		void onCreatureLeave(Creature* creature);
		void onCreatureFound(Creature* creature, bool pushFront = false);

		void updateLookDirection();

		void addFriend(Creature* creature);
		void removeFriend(Creature* creature);
		void addTarget(Creature* creature, bool pushFront = false);
		void removeTarget(Creature* creature);

		void updateTargetList();
		void clearTargetList();
		void clearFriendList();

		void death(Creature* lastHitCreature) final;
		Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final;

		void setIdle(bool idle);
		void updateIdleStatus();
		bool getIdleStatus() const {
			return isIdle;
		}

		void onAddCondition(ConditionType_t type) final;
		void onEndCondition(ConditionType_t type) final;
		void onCreatureConvinced(const Creature* convincer, const Creature* creature) final;

		bool canUseAttack(const Position& pos, const Creature* target) const;
		bool getRandomStep(const Position& creaturePos, Direction& direction) const;
		bool getDanceStep(const Position& creaturePos, Direction& direction,
		                  bool keepAttack = true, bool keepDistance = true);
		bool isInSpawnRange(const Position& pos) const;
		bool canWalkTo(Position pos, Direction direction) const;

		static bool pushItem(Item* item);
		static void pushItems(Tile* tile);
		static bool pushCreature(Creature* creature);
		static void pushCreatures(Tile* tile);

		void onThinkTarget(uint32_t interval);
		void onThinkYell(uint32_t interval);
		void onThinkDefense(uint32_t interval);

		bool isFriend(const Creature* creature) const;
		bool isOpponent(const Creature* creature) const;

		uint64_t getLostExperience() const final {
			return skillLoss ? mType->info.experience : 0;
		}
		uint16_t getLookCorpse() const final {
			return mType->info.lookcorpse;
		}
		void dropLoot(Container* corpse, Creature* lastHitCreature) final;
		uint32_t getDamageImmunities() const final {
			return mType->info.damageImmunities;
		}
		uint32_t getConditionImmunities() const final {
			return mType->info.conditionImmunities;
		}
		void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const final;
		bool useCacheMap() const final {
			return true;
		}

		friend class LuaScriptInterface;
		friend class AreaSpawnEvent;
		friend class Combat;
		friend class Creature;
};

#endif
