/**
 * @file monster.h
 *
 * Interface of monster functionality, AI, actions, spawning, loading, etc.
 */
#pragma once

#include <cstdint>
#include <array>

#include "engine.h"
#include "engine/actor_position.hpp"
#include "engine/animationinfo.h"
#include "engine/cel_sprite.hpp"
#include "engine/point.hpp"
#include "miniwin/miniwin.h"
#include "utils/stdcompat/optional.hpp"
#include "monstdat.h"
#include "spelldat.h"
#include "textdat.h"

#ifndef NOSOUND
#include "sound.h"
#endif

namespace devilution {

#define MAXMONSTERS 200
#define MAX_LVLMTYPES 24

enum monster_flag : uint16_t {
	// clang-format off
	MFLAG_HIDDEN          = 1 << 0,
	MFLAG_LOCK_ANIMATION  = 1 << 1,
	MFLAG_ALLOW_SPECIAL   = 1 << 2,
	MFLAG_NOHEAL          = 1 << 3,
	MFLAG_TARGETS_MONSTER = 1 << 4,
	MFLAG_GOLEM           = 1 << 5,
	MFLAG_QUEST_COMPLETE  = 1 << 6,
	MFLAG_KNOCKBACK       = 1 << 7,
	MFLAG_SEARCH          = 1 << 8,
	MFLAG_CAN_OPEN_DOOR   = 1 << 9,
	MFLAG_NO_ENEMY        = 1 << 10,
	MFLAG_BERSERK         = 1 << 11,
	MFLAG_NOLIFESTEAL     = 1 << 12,
	// clang-format on
};

/** this enum contains indexes from UniqMonst array for special unique monsters (usually quest related) */
enum : uint8_t {
	UMT_GARBUD,
	UMT_SKELKING,
	UMT_ZHAR,
	UMT_SNOTSPIL,
	UMT_LAZURUS,
	UMT_RED_VEX,
	UMT_BLACKJADE,
	UMT_LACHDAN,
	UMT_WARLORD,
	UMT_BUTCHER,
	UMT_HORKDMN,
	UMT_DEFILER,
	UMT_NAKRUL,
};

enum MON_MODE : uint8_t {
	MM_STAND,
	/** Movement towards N, NW, or NE */
	MM_WALK,
	/** Movement towards S, SW, or SE */
	MM_WALK2,
	/** Movement towards W or E */
	MM_WALK3,
	MM_ATTACK,
	MM_GOTHIT,
	MM_DEATH,
	MM_SATTACK,
	MM_FADEIN,
	MM_FADEOUT,
	MM_RATTACK,
	MM_SPSTAND,
	MM_RSPATTACK,
	MM_DELAY,
	MM_CHARGE,
	MM_STONE,
	MM_HEAL,
	MM_TALK,
};

enum {
	MA_STAND,
	MA_WALK,
	MA_ATTACK,
	MA_GOTHIT,
	MA_DEATH,
	MA_SPECIAL,
};

enum monster_goal : uint8_t {
	MGOAL_NONE,
	MGOAL_NORMAL,
	MGOAL_RETREAT,
	MGOAL_HEALING,
	MGOAL_MOVE,
	MGOAL_ATTACK2,
	MGOAL_INQUIRING,
	MGOAL_TALKING,
};

enum placeflag : uint8_t {
	// clang-format off
	PLACE_SCATTER = 1 << 0,
	PLACE_SPECIAL = 1 << 1,
	PLACE_UNIQUE  = 1 << 2,
	// clang-format on
};

struct AnimStruct {
	std::unique_ptr<byte[]> CMem;
	std::array<std::optional<CelSprite>, 8> CelSpritesForDirections;
	int Frames;
	int Rate;
};

struct CMonster {
	_monster_id mtype;
	/** placeflag enum as a flags*/
	uint8_t mPlaceFlags;
	AnimStruct Anims[6];
#ifndef NOSOUND
	std::unique_ptr<TSnd> Snds[4][2];
#endif
	uint16_t mMinHP;
	uint16_t mMaxHP;
	uint8_t mAFNum;
	int8_t mdeadval;
	const MonsterDataStruct *MData;
};

struct MonsterStruct { // note: missing field _mAFNum
	int _mMTidx;
	MON_MODE _mmode;
	monster_goal _mgoal;
	int _mgoalvar1;
	int _mgoalvar2;
	int _mgoalvar3;
	uint8_t _pathcount;
	ActorPosition position;
	/** Direction faced by monster (direction enum) */
	Direction _mdir;
	/** The current target of the mosnter. An index in to either the plr or monster array based on the _meflag value. */
	int _menemy;
	/** Usually correspond's to the enemy's future position */
	Point enemyPosition;
	/**
	 * @brief Contains Information for current Animation
	 */
	AnimationInfo AnimInfo;
	bool _mDelFlag;
	int _mVar1;
	int _mVar2;
	int _mVar3;
	int _mmaxhp;
	int _mhitpoints;
	_mai_id _mAi;
	uint8_t _mint;
	uint32_t _mFlags;
	uint8_t _msquelch;
	/** Seed used to determine item drops on death */
	uint32_t _mRndSeed;
	/** Seed used to determine AI behaviour/sync sounds in multiplayer games? */
	uint32_t _mAISeed;
	uint8_t _uniqtype;
	uint8_t _uniqtrans;
	int8_t _udeadval;
	int8_t mWhoHit;
	int8_t mLevel;
	uint16_t mExp;
	uint16_t mHit;
	uint8_t mMinDamage;
	uint8_t mMaxDamage;
	uint16_t mHit2;
	uint8_t mMinDamage2;
	uint8_t mMaxDamage2;
	uint8_t mArmorClass;
	uint16_t mMagicRes;
	_speech_id mtalkmsg;
	uint8_t leader;
	uint8_t leaderflag;
	uint8_t packsize;
	int8_t mlid; // BUGFIX -1 is used when not emitting light this should be signed (fixed)
	const char *mName;
	CMonster *MType;
	const MonsterDataStruct *MData;

	/**
	 * @brief Check thats the correct stand Animation is loaded. This is needed if direction is changed (monster stands and looks to player).
	 * @param mdir direction of the monster
	 */
	void CheckStandAnimationIsLoaded(int mdir);

	/**
	 * @brief Sets _mmode to MM_STONE
	 */
	void Petrify();

	/**
	 * @brief Is the monster currently walking?
	 */
	bool IsWalking() const;
};

extern int monstkills[MAXMONSTERS];
extern int monstactive[MAXMONSTERS];
extern int nummonsters;
extern bool sgbSaveSoundOn;
extern MonsterStruct monster[MAXMONSTERS];
extern CMonster Monsters[MAX_LVLMTYPES];
extern int nummtypes;

void InitLevelMonsters();
void GetLevelMTypes();
void InitMonsterGFX(int monst);
void InitMonster(int i, Direction rd, int mtype, Point position);
void ClrAllMonsters();
void monster_some_crypt();
void PlaceGroup(int mtype, int num, int leaderf, int leader);
void InitMonsters();
void SetMapMonsters(const uint16_t *dunData, Point startPosition);
void DeleteMonster(int i);
int AddMonster(Point position, Direction dir, int mtype, bool InMap);
void AddDoppelganger(MonsterStruct &monster);
bool M_Talker(int i);
void M_StartStand(int i, Direction md);
void M_ClearSquares(int i);
void M_GetKnockback(int i);
void M_StartHit(int i, int pnum, int dam);
void M_StartKill(int i, int pnum);
void M_SyncStartKill(int i, Point position, int pnum);
void M_Teleport(int i);
void M_UpdateLeader(int i);
void DoEnding();
void PrepDoEnding();
void M_WalkDir(int i, Direction md);
void MAI_Zombie(int i);
void MAI_SkelSd(int i);
void MAI_Snake(int i);
void MAI_Bat(int i);
void MAI_SkelBow(int i);
void MAI_Fat(int i);
void MAI_Sneak(int i);
void MAI_Fireman(int i);
void MAI_Fallen(int i);
void MAI_Cleaver(int i);
void MAI_Round(int i, bool special);
void MAI_GoatMc(int i);
void MAI_Ranged(int i, missile_id missileType, bool special);
void MAI_GoatBow(int i);
void MAI_Succ(int i);
void MAI_Lich(int i);
void MAI_ArchLich(int i);
void MAI_Psychorb(int i);
void MAI_Necromorb(int i);
void MAI_AcidUniq(int i);
void MAI_Firebat(int i);
void MAI_Torchant(int i);
void MAI_Scav(int i);
void MAI_Garg(int i);
void MAI_RoundRanged(int i, missile_id missileType, bool checkdoors, int dam, int lessmissiles);
void MAI_Magma(int i);
void MAI_Storm(int i);
void MAI_BoneDemon(int i);
void MAI_Acid(int i);
void MAI_Diablo(int i);
void MAI_Mega(int i);
void MAI_Golum(int i);
void MAI_SkelKing(int i);
void MAI_Rhino(int i);
void MAI_HorkDemon(int i);
void MAI_Counselor(int i);
void MAI_Garbud(int i);
void MAI_Zhar(int i);
void MAI_SnotSpil(int i);
void MAI_Lazurus(int i);
void MAI_Lazhelp(int i);
void MAI_Lachdanan(int i);
void MAI_Warlord(int i);
void DeleteMonsterList();
void ProcessMonsters();
void FreeMonsters();
bool DirOK(int i, Direction mdir);
bool PosOkMissile(int entity, Point position);
bool LineClearSolid(Point startPoint, Point endPoint);
bool LineClearMissile(Point startPoint, Point endPoint);
bool LineClear(bool (*Clear)(int, Point), int entity, Point startPoint, Point endPoint);
void SyncMonsterAnim(int i);
void M_FallenFear(Point position);
void PrintMonstHistory(int mt);
void PrintUniqueHistory();
void MissToMonst(int i, Point position);
bool PosOkMonst(int i, Point position);
bool monster_posok(int i, Point position);
bool PosOkMonst2(int i, Point position);
bool PosOkMonst3(int i, Point position);
bool IsSkel(int mt);
bool IsGoat(int mt);
int M_SpawnSkel(Point position, Direction dir);
bool SpawnSkeleton(int ii, Point position);
int PreSpawnSkeleton();
void TalktoMonster(int i);
void SpawnGolum(int i, Point position, int mi);
bool CanTalkToMonst(int m);
bool CheckMonsterHit(int m, bool *ret);
int encode_enemy(int m);
void decode_enemy(int m, int enemy);

/* data */

extern Direction left[8];
extern Direction right[8];
extern Direction opposite[8];

} // namespace devilution
