/**
 * @file
 * @brief Misc monster related functions.
**/

#pragma once

#include <functional>
#include <vector>

#include "enum.h"
#include "gender-type.h"
#include "los-type.h"
#include "mon-dam-level-type.h"
#include "mon-enum.h"
#include "mon-inv-type.h"
#include "player.h"

using std::vector;

struct bolt;
struct mgen_data;

struct mon_attack_def
{
    attack_type     type;
    attack_flavour  flavour;
    int             damage;
};

// Amount of mons->speed_increment used by different actions; defaults
// to 10.
#define DEFAULT_ENERGY {10, 10, 10, 10, 10}
struct mon_energy_usage
{
    uint8_t move;
    uint8_t swim;
    uint8_t attack;
    uint8_t missile; // Arrows/crossbows/etc
    uint8_t spell;

    static mon_energy_usage attack_cost(int cost, int sw = 10)
    {
        mon_energy_usage me = DEFAULT_ENERGY;
        me.attack = cost;
        me.swim = sw;
        return me;
    }

    static mon_energy_usage missile_cost(int cost)
    {
        mon_energy_usage me = DEFAULT_ENERGY;
        me.missile = cost;
        return me;
    }

    static mon_energy_usage swim_cost (int cost)
    {
        mon_energy_usage me = DEFAULT_ENERGY;
        me.swim = cost;
        return me;
    }

    static mon_energy_usage move_cost(int mv, int sw = 10)
    {
        mon_energy_usage me = DEFAULT_ENERGY;
        me.move = mv;
        me.swim = sw;
        return me;
    }

    mon_energy_usage operator | (const mon_energy_usage &o) const
    {
        mon_energy_usage me;
        me.move = combine(move, o.move);
        me.swim = combine(swim, o.swim);
        me.attack = combine(attack, o.attack);
        me.missile = combine(missile, o.missile);
        me.spell = combine(spell, o.spell);
        return me;
    }

    bool operator == (const mon_energy_usage &rvalue) const
    {
        return move == rvalue.move
               && swim == rvalue.swim
               && attack == rvalue.attack
               && missile == rvalue.missile
               && spell == rvalue.spell;
    }
private:
    static uint8_t combine(uint8_t a, uint8_t b, uint8_t def = 10)
    {
        return b != def? b : a;
    }
};

enum body_part_class_flags
{
    BPART_INTERNAL = 1,
    BPART_EXTERNAL = 1 << 1,
    BPART_ANY      = (BPART_INTERNAL|BPART_EXTERNAL),
};

/// in what ways do a monster's tiles vary?
enum mon_type_tile_variation
{
    TVARY_NONE,   ///< fixed tile (or special case)
    TVARY_MOD,    ///< monster instances have fixed but random tiles
    TVARY_CYCLE,  ///< cycle through tileset with every redraw
    TVARY_RANDOM, ///< choose a random tile in set with every redraw
    TVARY_WATER,  ///< if in water, incr tile enum by 1 (merfolk &c)
};

/// Tiles display information for a monster type.
struct mon_type_tile_info
{
    tileidx_t base; ///< The base tile for the monster type.
    mon_type_tile_variation variation; ///< How (and if) the tile should vary.
};

struct monsterentry
{
    short mc;            // monster number

    char basechar;
    colour_t colour;
    const char *name;

    monclass_flags_t bitfields;
    resists_t resists;

    // The monster's XP value.
    int exp;
    // If true, exp is a multiplier used to calculate their XP; see
    // exp_value().
    bool exp_is_mult;

    monster_type genus,         // "team" the monster plays for
                 species;       // corpse type of the monster

    mon_holy_type holiness;

    short willpower;  // (positive sets value, negative is relative to hd)

    // max damage in a turn is total of these four?
    mon_attack_def attack[MAX_NUM_ATTACKS];

    /// Similar to player level; used for misc purposes.
    int HD;
    /// Average hp; multiplied by 10 for precision.
    int avg_hp_10x;

    int8_t AC; // armour class
    int8_t ev; // evasion
    int sec;   // actually mon_spellbook_type
    bool leaves_corpse;
    shout_type         shouts;
    mon_intel_type     intel;
    habitat_type     habitat;
    int8_t           speed;        // How quickly speed_increment increases
    mon_energy_usage energy_usage; // And how quickly it decreases
    mon_itemuse_type gmon_use;
    size_type size;
    mon_body_shape shape;
    god_type god;
    mon_type_tile_info tile;
    tileidx_t corpse_tile; // XXX: ideally this would be autogenerated...
};

enum mon_threat_level_type
{
    MTHRT_TRIVIAL,
    MTHRT_EASY,
    MTHRT_TOUGH,
    MTHRT_NASTY,
    MTHRT_UNDEF,
};

void set_resist(resists_t &all, mon_resist_flags res, int lev);

// In all cases this will be simplified to a bit field access, so let's let
// the compiler inline it.
static inline int get_resist(resists_t all, mon_resist_flags res)
{
    if (res > MR_LAST_MULTI)
        return all & res ? 1 : 0;
    int v = (all / res) & 7;
    if (v > 4)
        return v - 8;
    return v;
}

constexpr int SLYMDRA_HP_PER_HEAD = 27;

dungeon_feature_type preferred_feature_type(monster_type mt);

monsterentry *get_monster_data(monster_type mc) IMMUTABLE;
int get_mons_class_ac(monster_type mc) IMMUTABLE;
int get_mons_class_ev(monster_type mc) IMMUTABLE;
resists_t get_mons_class_resists(monster_type mc) IMMUTABLE;
resists_t get_mons_resists(const monster& mon);
int get_mons_resist(const monster& mon, mon_resist_flags res);
bool monster_resists_this_poison(const monster& mons, bool force = false);

void init_monsters();
void init_monster_symbols();

monster *monster_at(const coord_def &pos);

// this is the old moname()
string mons_type_name(monster_type type, description_level_type desc);

bool give_monster_proper_name(monster& mon);

size_type mons_class_body_size(monster_type mc);

mon_itemuse_type mons_class_itemuse(monster_type mc);
mon_itemuse_type mons_itemuse(const monster& mon);

int mons_res_blind(monster_type mc);

bool mons_resists_drowning(monster_type type, monster_type base);

int get_shout_noise_level(const shout_type shout);
shout_type mons_shouts(monster_type mclass, bool demon_shout = false);
bool mons_can_shout(monster_type mclass);

bool mons_is_ghost_demon(monster_type mc);
bool mons_is_unique(monster_type mc);
bool mons_is_or_was_unique(const monster& mon);
bool mons_is_the(monster_type mc);
bool mons_is_pghost(monster_type mc);
bool mons_is_draconian_job(monster_type mc);
bool mons_is_hepliaklqana_ancestor(monster_type mc);

int mutant_beast_tier(int xl);

int mons_avg_hp(monster_type mc, int scale = 1);
int mons_max_hp(monster_type mc);
int exp_value(const monster& mon, bool real = true, bool legacy = false);

int hit_points(int avg_hp, int scale = 10);

int mons_class_hit_dice(monster_type mc);
int mons_class_willpower(monster_type type, monster_type base);
bool mons_class_sees_invis(monster_type type, monster_type base);

bool mons_invuln_will(const monster& mon);

mon_attack_def mons_attack_spec(const monster& mon, int attk_number, bool base_flavour = true);
string mon_attack_name(attack_type attack, bool with_object = true);
string mon_attack_name_short(attack_type attack);
bool flavour_triggers_damageless(attack_flavour flavour);
int flavour_damage(attack_flavour flavour, int HD, bool random = true);
bool flavour_has_reach(attack_flavour flavour);
bool flavour_has_mobility(attack_flavour flavour);

bool mons_class_flag(monster_type mc, monclass_flags_t bits);

mon_holy_type holiness_by_name(string name);
const char * holiness_name(mon_holy_type_flags which_holiness);
const char * single_holiness_description(mon_holy_type holiness);
string holiness_description(mon_holy_type holiness);
mon_holy_type mons_class_holiness(monster_type mc);

const char * intelligence_description(mon_intel_type intel);

void discover_mimic(const coord_def& pos);
void discover_shifter(monster& shifter);

bool mons_is_statue(monster_type mc);
bool mons_is_demon(monster_type mc);
bool mons_is_draconian(monster_type mc);
bool mons_is_base_draconian(monster_type mc);
bool mons_is_beast(monster_type mc);
bool mons_is_avatar(monster_type mc);
int mons_demon_tier(monster_type mc);

bool mons_class_wields_two_weapons(monster_type mc);
bool mons_wields_two_weapons(const monster& m);
bool mons_self_destructs(const monster& m);
bool mons_blows_up(const monster& m);
bool mons_destroyed_on_impact(const monster& m);

bool should_shout_at_mons(const monster &m);
bool should_attract_mons(const monster &m);

mon_intel_type mons_class_intel(monster_type mc);
mon_intel_type mons_intel(const monster& mon);

// Use mons_habitat() wherever possible, since the class variants do not
// handle zombies correctly.
habitat_type mons_habitat_type(monster_type t, monster_type base_t,
                               bool core_only = false);
habitat_type mons_class_habitat(monster_type t, bool core_only = false);
habitat_type mons_habitat(const monster& mon, bool core_only = false);

bool mons_has_skeleton(monster_type mc);

int max_corpse_chunks(monster_type mc);
int mons_class_base_speed(monster_type mc);
mon_energy_usage mons_class_energy(monster_type mc);
mon_energy_usage mons_energy(const monster& mon);
int mons_class_zombie_base_speed(monster_type zombie_base_mc, bool slow);
int mons_base_speed(const monster& mon);

bool monster_class_flies(monster_type mc);
bool monster_inherently_flies(const monster &mons);

bool mons_class_can_regenerate(monster_type mc);
bool mons_can_regenerate(const monster& mon);
bool mons_class_fast_regen(monster_type mc);
int mons_class_regen_amount(monster_type mc);
int mons_leash_range(monster_type mc);
int mons_zombie_size(monster_type mc);
monster_type mons_zombie_base(const monster& mon);
bool mons_class_is_zombified(monster_type mc);
bool mons_class_is_animated_weapon(monster_type type);
bool mons_class_is_animated_object(monster_type type);
bool mons_class_is_remnant(monster_type type);
monster_type mons_base_type(const monster& mon);
bool mons_class_can_leave_corpse(monster_type mc);
bool mons_class_leaves_hide(monster_type mc);
bool mons_class_leaves_wand(monster_type mc);
bool mons_class_leaves_organ(monster_type mc);
bool mons_is_zombified(const monster& mons);
bool mons_class_can_be_zombified(monster_type mc);
bool mons_can_be_zombified(const monster& mon);
bool mons_class_can_be_spectralised(monster_type mc, bool divine = false);
bool mons_can_be_spectralised(const monster& mon, bool divine = false,
                              bool only_known = false);
bool mons_class_can_use_stairs(monster_type mc);
bool mons_class_can_use_transporter(monster_type mc);
bool mons_can_use_stairs(const monster& mon,
                         dungeon_feature_type stair = DNGN_UNSEEN);
void name_zombie_from_class(monster& mon, monster_type mc, const string& mon_name);
void name_zombie_from_mon(monster& mon, const monster& orig);

int mons_power(monster_type mc);

char32_t mons_char(monster_type mc);
char mons_base_char(monster_type mc);

int mons_class_colour(monster_type mc);

monster_type royal_jelly_ejectable_monster();
monster_type random_draconian_monster_species();
monster_type random_draconian_job();
monster_type draconian_colour_for_job(monster_type job);
monster_type draconian_job_for_colour(monster_type colour);

void define_monster(monster& mons, bool friendly = false);

void mons_pacify(monster& mon, mon_attitude_type att = ATT_GOOD_NEUTRAL,
                 bool no_xp = false);

bool mons_should_fire(const bolt &beam, const targeting_tracer& tracer,
                      bool ignore_good_idea = false);

bool mons_has_los_ability(monster_type mon_type);
bool is_offensive_spell(spell_type spell, maybe_bool needs_lof = maybe_bool::maybe);
bool _mons_has_smite_attack(const monster* mons);

gender_type mons_class_gender(monster_type mc);
const char *mons_pronoun(monster_type mon_type, pronoun_type variant,
                         bool visible = true);

bool mons_aligned(const actor *m1, const actor *m2);
bool mons_atts_aligned(mon_attitude_type fr1, mon_attitude_type fr2);

bool mons_att_wont_attack(mon_attitude_type fr);
mon_attitude_type mons_attitude(const monster& m);

// Whether the monster is temporarily confused (class_too = false)
// or confused at all (class_too = true; temporarily or by class).
bool mons_is_confused(const monster& m, bool class_too = false);

bool mons_is_wandering(const monster& m);
bool mons_is_seeking(const monster& m);
bool mons_is_fleeing(const monster& m);
bool mons_is_retreating(const monster& m);
bool mons_is_cornered(const monster& m);
bool mons_is_batty(const monster& m);
bool mons_is_influenced_by_sanctuary(const monster& m);
bool mons_is_fleeing_sanctuary(const monster& m);
bool mons_just_slept(const monster& m);
bool mons_is_deep_asleep(const monster& m);
bool mons_class_is_slime(monster_type mc);
bool mons_is_slime(const monster& mon);
bool mons_class_is_plant(monster_type mc);
bool mons_class_is_draconic(monster_type mc);
bool mons_is_plant(const monster& mon);
bool mons_eats_items(const monster& mon);
bool actor_is_susceptible_to_vampirism(const actor& act, bool known = false);
monster_type mons_genus(monster_type mc);
monster_type mons_species(monster_type mc);
monster_type draconian_subspecies(const monster& mon);
monster_type draconian_subspecies(monster_type type, monster_type base);
monster_type mons_detected_base(monster_type mt);
bool mons_is_siren_beholder(monster_type mc);
bool mons_is_siren_beholder(const monster& mons);

bool mons_is_removed(monster_type mc);

bool mons_looks_distracted(const monster& m);

void mons_start_fleeing_from_sanctuary(monster& mons);
void mons_stop_fleeing_from_sanctuary(monster& mons);

bool mons_class_is_stationary(monster_type mc);
bool mons_class_is_firewood(monster_type mc);
bool mons_class_is_peripheral(monster_type mc);
bool mons_class_is_test(monster_type mc);
bool mons_class_angered_by_attacks(monster_type mc);
bool mons_is_active_ballisto(const monster& mon);
bool mons_has_body(const monster& mon);
bool mons_is_unbreathing(monster_type mc);

bool herd_monster(const monster& mon);

int cheibriados_monster_player_speed_delta(const monster& mon);
bool cheibriados_thinks_mons_is_fast(const monster& mon);
bool mons_is_projectile(monster_type mc);
bool mons_is_projectile(const monster& mon);
bool mons_is_seeker(monster_type mc);
bool mons_is_seeker(const monster& mon);
cloud_type seeker_trail_type(const monster& mon);
bool mons_has_blood(monster_type mc);
bool mons_is_sensed(monster_type mc);
bool mons_offers_beogh_conversion(const monster& mon);
bool mons_offers_beogh_conversion_now(const monster& mon);

bool invalid_monster(const monster* mon);
bool invalid_monster_type(monster_type mt);
bool invalid_monster_index(int i);

void mons_load_spells(monster& mon);

void mons_remove_from_grid(const monster& mon);

bool monster_shover(const monster& m);

bool monster_senior(const monster& first, const monster& second,
                    bool fleeing = false);
string ugly_thing_colour_name(colour_t colour);
colour_t ugly_thing_random_colour();
int str_to_ugly_thing_colour(const string &s);
colour_t random_monster_colour();
int ugly_thing_colour_offset(colour_t colour);

/**
 * @brief
 *  Apply uniform colour when generating a band containing only ugly things.
 *
 * If the passed band does not contain only ugly things, @p mg is not modified.
 *
 * @param mg
 *  The generation data to adjust.
 * @param band_monsters
 *  The array of monsters types comprising the band.
 * @param num_monsters_in_band
 *  The number of elements in @p band_monsters.
 */
void ugly_thing_apply_uniform_band_colour(mgen_data &mg,
    const monster_type *band_monsters, size_t num_monsters_in_band);

string  draconian_colour_name(monster_type mon_type);
monster_type draconian_colour_by_name(const string &colour);
mon_spell_slot drac_breath(monster_type drac_type);

void         init_mon_name_cache();
monster_type get_monster_by_name(string name, bool substring = false);

string random_body_part_name(bool plural, int part_class);

string do_mon_str_replacements(const string& msg, const monster& mons,
                               int s_type = -1);
string do_mon_name_replacements(const string& name);

mon_body_shape get_mon_shape(const monster& mon);
mon_body_shape get_mon_shape(const monster_type mc);
string get_mon_shape_str(const mon_body_shape shape);
bool mon_shape_is_humanoid(mon_body_shape shape);

tileidx_t get_mon_base_tile(monster_type mc);
mon_type_tile_variation get_mon_tile_variation(monster_type mc);
tileidx_t get_mon_base_corpse_tile(monster_type mc);

bool mons_class_can_pass(monster_type mc, dungeon_feature_type grid);
bool mons_can_open_door(const monster& mon, const coord_def& pos);
bool mons_can_eat_door(const monster& mon, const coord_def& pos);
bool mons_can_destroy_door(const monster& mon, const coord_def& pos);
bool mons_can_traverse(const monster& mon, const coord_def& pos,
                       bool only_in_sight = false,
                       bool checktraps = true);

mon_inv_type equip_slot_to_mslot(equipment_slot eq);
mon_inv_type item_to_mslot(const item_def &item);

bool mons_is_immotile(const monster& mons);

int get_dist_to_nearest_monster();
bool monster_nearby();
actor *actor_by_mid(mid_t m, bool require_valid = false);
monster *monster_by_mid(mid_t m, bool require_valid = false);
monster *cached_monster_copy_by_mid(mid_t m);
bool mons_is_recallable(const actor* caller, const monster& targ);
void init_anon();
actor *find_agent(mid_t m, kill_category kc);
const char* mons_class_name(monster_type mc);
mon_threat_level_type mons_threat_level(const monster &mon,
                                        bool real = false);

bool mons_foe_is_marked(const monster& mons);

bool mons_stores_tracking_data(const monster& mons);

bool mons_is_player_shadow(const monster& mon);
bool mons_is_wrath_avatar(const monster &mon);

bool mons_has_attacks(const monster& mon, bool allow_damageless = false);

void reset_all_monsters();
void debug_mondata();
void debug_monspells();

bool choose_any_monster(const monster& mon);
monster *choose_random_nearby_monster(
    bool (*suitable)(const monster& mon) =
        choose_any_monster);

monster *choose_random_monster_on_level(
    bool (*suitable)(const monster& mon) =
        choose_any_monster);

int spell_freq_for_hd(int hd);
void normalize_spell_freq(monster_spells &spells, int total_freq);

void print_wounds(const monster& mons);
bool wounded_damaged(mon_holy_type holi);

mon_dam_level_type mons_get_damage_level(const monster& mons);

string get_damage_level_string(mon_holy_type holi, mon_dam_level_type mdam);
bool mons_class_is_threatening(monster_type mo);
bool mons_is_threatening(const monster& mon);
bool mons_class_gives_xp(monster_type mc, bool indirect = false);
bool mons_gives_xp(const monster& mon, const actor& agent);
bool mons_is_notable(const monster& mon);

bool mons_class_is_fragile(monster_type mc);
bool mons_is_fragile(const monster& mons);

int max_mons_charge(monster_type m);

void init_mutant_beast(monster &mon, short HD, vector<int> beast_facets);

void radiate_pain_bond(const monster& mon, int damage,
                       const monster* original_target);
void throw_monster_bits(const monster& mon);

void set_ancestor_spells(monster &ancestor, bool notify = false);

typedef function<bool (monster& mon)> monster_func;
bool apply_monsters_around_square(monster_func mf, const coord_def& where,
                                  int radius = 1);
bool apply_visible_monsters(monster_func mf,
                            const coord_def& center = you.pos(),
                            los_type los = LOS_NO_TRANS);

int derived_undead_avg_hp(monster_type mtype, int hd, int scale = 10);

int touch_of_beogh_hp_mult(const monster& mon);

bool shoot_through_actor(const actor* agent, const actor* target, bool announce = false);
bool could_harm(const actor* agent, const actor* target, bool announce_important = false,
                                                         bool announce_mundane = false);
bool could_harm_enemy(const actor* agent, const actor* target, bool announce_important = false,
                                                               bool announce_mundane = false);
