/**
 * @file
 * @brief Functions related to ranged attacks.
**/

#pragma once

#include <vector>

#include "ac-type.h"
#include "beam-type.h"
#include "enchant-type.h"
#include "externs.h"
#include "killer-type.h"
#include "mon-ai-action.h"
#include "mon-attitude-type.h"
#include "random.h"
#include "ray.h"
#include "spl-cast.h"
#include "zap-type.h"

using std::string;

// The mid of the source for a pending hostile teleport.
// (Can be the player, if caused by teleportitis.)
#define TELEPORTITIS_SOURCE "TELEPORTITIS_SOURCE"

using std::vector;

#define BEAM_STOP       1000        // all beams stopped by subtracting this
                                    // from remaining range

class monster;
class ranged_attack;

enum mon_resist_type
{
    MON_RESIST,                 // monster resisted
    MON_UNAFFECTED,             // monster unaffected
    MON_AFFECTED,               // monster was affected
    MON_OTHER,                  // monster unaffected, but for other reasons
};

class dist;

typedef FixedArray<int, 19, 19> explosion_map;

struct tracer_info
{
    int count;                         // # of times something "hit"
    int power;                         // total levels/hit dice affected

    tracer_info();
    void reset();
};

struct bolt;

struct beam_tracer
{
    // Is this tracer collecting warnings to prompt the player with?
    virtual bool is_collecting_warnings() noexcept { return false; }
    // Has the tracer hit an enemy worth caring about? Only needs to be
    // accurate when firing if `is_collecting_warnings` returns true.
    virtual bool has_hit_foe() noexcept { return false; }
    // Should the beam stop at the target if there is a friendly monster
    // after it?
    virtual bool should_stop() noexcept { return false; }
    // How many times the player was hit by this tracer.
    virtual int player_hit_count() noexcept { return 0; }

    // Called when a meaningful target is affected
    virtual void actor_affected(bool friendly_fire, int power)
    {
        UNUSED(friendly_fire, power);
    }

    virtual void player_hit(bool was_friendly = true)
    {
        UNUSED(was_friendly);
    }
    virtual void monster_hit(const bolt& bolt, const monster& mon)
    {
        UNUSED(bolt, mon);
    }
    virtual void blocked(string message)
    {
        UNUSED(message);
    }
};

// Used when casting a spell to check if the spell should be aborted
struct player_beam_tracer : beam_tracer
{
    struct bad_target
    {
        const monster* mon;
        string adj;
        string suffix;
        bool penance;
    };
    vector<bad_target> bad_attack_targets;
    const monster* bad_charm_target = nullptr;
    const monster* god_hated_target = nullptr;
    int hit_self_count = 0;
    int foe_count = 0;
    string blocked_message;
    int blocked_count = 0;

    player_beam_tracer() {}

    bool is_collecting_warnings() noexcept override
    {
        return true;
    }

    bool has_hit_foe() noexcept override;
    bool should_stop() noexcept override;
    void actor_affected(bool friendly_fire, int power) noexcept override;
    void player_hit(bool was_friendly) noexcept override;
    void monster_hit(const bolt& bolt, const monster& mon) override;
    void blocked(string message) noexcept override;
    bool has_any_warnings() noexcept;
};

// Used to check if casting a spell might be useful
struct targeting_tracer : beam_tracer
{
    tracer_info foe_info;
    tracer_info friend_info;
    bool abort_for_player = false;
    int hurt_player_count = 0;

    targeting_tracer() {}

    bool has_hit_foe() noexcept override;
    void actor_affected(bool friendly_fire, int power) noexcept override;
    void player_hit(bool was_friendly) noexcept override;
    int player_hit_count() noexcept override;

    ai_action::goodness good_to_fire(int foe_ratio) const;
};

struct bolt
{
    bolt();

    // INPUT parameters set by caller
    spell_type  origin_spell = SPELL_NO_SPELL; // may remain SPELL_NO_SPELL for
                                               // non-spell beams.
    int         range = -2;
    char32_t    glyph = '*';           // missile gfx
    colour_t    colour = BLACK;
    beam_type   flavour = BEAM_MAGIC;
    beam_type   real_flavour = BEAM_MAGIC; // for random and chaos beams this
                                           // will remain the same while flavour
                                           // changes
    bool        drop_item = false;     // should drop an item when done
    coord_def   source = {0,0};           // beam origin
    coord_def   target = {0,0};           // intended target
    dice_def    damage = dice_def(0,0);
    int         ench_power = 0, hit = 0;
    killer_type thrower = KILL_NON_ACTOR;   // what kind of thing threw this?
    int         ex_size = 0;           // explosion radius (0==none)

    mid_t       source_id = MID_NOBODY;// The mid of the source (remains
                                       // MID_NOBODY if not coming from a player
                                       // or a monster).
    string source_name = "";      // The name of the source, should it
                                  // be different from agent->name(),
                                  // or if the actor dies prematurely.
    string name = "";
    string short_name = "";
    string hit_verb = "";         // The verb to use when this beam hits
                                  // something. If not set, will use
                                  // "engulfs" if an explosion or cloud
                                  // and "hits" otherwise.
    int    loudness = 0;          // Noise level on hitting or exploding.
    string hit_noise_msg = "";    // Message to give player for each hit
                                  // monster that isn't in view.
    string explode_noise_msg = "";  // Message to give player if the explosion
                                    // isn't in view.
    bool   pierce = false;        // Can the beam pass through a target and
                                  // hit another target behind the first?
    bool   is_explosion = false;
    bool   is_death_effect = false; // effect of e.g. ballistomycete spore
    bool   aimed_at_spot = false; // aimed at (x, y), should not cross
    bool   stop_at_allies = false; // Should beam automatically stop before reaching allies
    bool   safe_to_user = false;  //
    string aux_source = "";       // source of KILL_NON_ACTOR beams

    bool   affects_nothing = false; // should not hit monsters or features

    bool   effect_known = true;   // did we _know_ this would happen?
    bool   effect_wanton = false; // could we have guessed it would happen?

    bool   no_saving_throw = false;   // whether to ignore any saving throw
                                      // this beam might otherwise have
    int    draw_delay = 15;       // delay used when drawing beam.
    int    explode_delay = 50;    // delay when drawing explosions.
    bool   redraw_per_cell = true; // whether to force a redraw after every cell
                                   // drawn during an animation. Not for
                                   // explosions.
                                   // TODO: why can't this behavior follow
                                   // from draw_delay == 0?

    bolt*  special_explosion = nullptr; // For exploding with a different
                                        // flavour/damage/etc than the beam
                                        // itself.
    // Do we draw animations?
    bool   animate;
    ac_type ac_rule = ac_type::normal;   // How defender's AC affects damage.
#ifdef DEBUG_DIAGNOSTICS
    bool   quiet_debug = false;    // Disable any debug spam.
#endif

    // OUTPUT parameters (tracing, ID)
    bool obvious_effect = false; // is this a non-enchantment, or did it already
                                 // show some effect or message? (Otherwise, we'll
                                 // print the canned 'nothing happened.)

    bool seen = false;          // Has player seen the beam?
    bool heard = false;         // Has the player heard the beam?

    vector<coord_def> path_taken = {}; // Path beam took.

    // INTERNAL use - should not usually be set outside of beam.cc
    int  extra_range_used = 0;
    bool aimed_at_feet = false;   // this was aimed at self!
    bool msg_generated = false;   // an appropriate msg was already mpr'd
    bool noise_generated = false; // a noise has already been generated at this pos
    bool passed_target = false;   // Beam progressed beyond target.
    bool in_explosion_phase = false; // explosion phase (as opposed to beam phase)
    bool enchant_chaining_done = false; // wheather we have already chained to nearby actors
    mon_attitude_type attitude = ATT_HOSTILE; // attitude of whoever fired the bolt
    int foe_ratio = 0;           // 100* foe ratio (see mons_should_fire())
    map<mid_t, int> hit_count;   // how many times targets were affected
    int foes_hurt = 0;               // number of foes actually hurt
    int foes_helped = 0;             // number of foes actually helped
    int friends_hurt = 0;            // number of friends actually hurt
    int friends_helped = 0;          // number of friends actually helped

    // Ranged attack to perform on each actor reached (for ranged attack beams)
    ranged_attack* ranged_atk = nullptr;

    beam_tracer* tracer = nullptr;

    bool chose_ray = false;       // do we want a specific ray?
    bool overshoot_prompt = true; // warn when an ally is past the target
    bool friendly_past_target = false; // we fired and found something past the target

    int       bounces = 0;        // # times beam bounced off walls
    coord_def bounce_pos = {0,0}; // position of latest wall bounce,
                                  // reset if a reflection happens

    coord_def last_affected_actor_pos = {0,0};   // position of the last actor affected by this beam

    int   reflections = 0;        // # times beam reflected off shields
    mid_t reflector = MID_NOBODY; // latest thing to reflect beam

    bool use_target_as_pos = false; // pos() should return ::target()

    ray_def     ray;             // shoot on this specific ray

    // only used if USE_TILE is defined
    tileidx_t tile_beam = 0;
    tileidx_t tile_explode = 0;

private:
    bool can_see_invis = false;
    bool nightvision = false;

    bool can_trigger_bullseye = false;

public:
    bool is_enchantment() const; // no block/dodge, use willpower
    void set_target(const dist &targ);
    void set_agent(const actor *agent);
    void setup_retrace();
    void precalc_agent_properties();

    // Returns YOU_KILL or MON_KILL, depending on the source of the beam.
    killer_type  killer() const;

    kill_category whose_kill() const;

    actor* agent(bool ignore_reflections = false) const;

    void fire();
    void fire(beam_tracer& tracer);
    void fire_as_ranged_attack(ranged_attack& atk);

    // Returns member short_name if set, otherwise some reasonable string
    // for a short name, most likely the name of the beam's flavour.
    string get_short_name() const;
    string get_source_name() const;

    bool visible() const;

    bool can_affect_actor(const actor *act) const;
    bool can_affect_wall(const coord_def& p, bool map_knowledge = false) const;
    bool harmless_to_player() const;
    bool ignores_monster(const monster* mon) const;
    bool ignores_player() const;
    bool can_knockback(int dam = -1) const;
    bool can_pull(const actor &act, int dam = -1) const;
    bool god_cares() const; // Will the god be unforgiving about this beam?
    bool is_harmless(const monster* mon) const;
    bool nasty_to(const monster* mon) const;
    bool nice_to(const monster_info& mi) const;
    bool has_saving_throw() const;

    void draw(const coord_def& p, bool force_refresh=true);

    // Various explosion-related stuff.
    bool explode(bool show_more = true, bool hole_in_the_middle = false);
    bool explode(beam_tracer& tracer, bool show_more = true,
                 bool hole_in_the_middle = false);
    void refine_for_explosion(const string& explode_msg = "");
    bool explosion_draw_cell(const coord_def& p);
    void explosion_affect_cell(const coord_def& p);
    void determine_affected_cells(explosion_map& m, const coord_def& delta,
                                  int count, int r,
                                  bool stop_at_statues, bool stop_at_walls);
    void special_explode();

    bool self_targeted() const;

    coord_def get_last_affected_pos(int step = 0);

    // Setup.
    void fake_flavour();
private:
    void do_fire();
    void initialise_fire();

    // Lots of properties of the beam.
    coord_def pos() const;
    coord_def leg_source() const;
    cloud_type get_cloud_type() const;
    int get_cloud_pow() const;
    int get_cloud_size(bool min = false, bool max = false) const;
    bool is_blockable() const;
    bool is_omnireflectable() const;
    bool is_fiery() const;
    bool can_burn_trees() const;
    bool is_bouncy(dungeon_feature_type feat) const;
    bool stop_at_target() const;
    bool is_reflectable(const actor &whom) const;
    bool found_player() const;
    bool need_regress() const;
    bool is_big_cloud() const; // expands into big_cloud at endpoint
    int range_used_on_hit() const;
    bool bush_immune(const monster &mons) const;
    bool at_blocking_monster() const;
    int apply_lighting(int base_hit, const actor &target) const;

    void do_ranged_attack(actor& target);

    set<string> message_cache;
    void emit_message(const char* msg);

    int apply_AC(const actor* victim, int hurted);
    bool determine_damage(monster* mon, int& preac, int& postac, int& final);

    // Functions which handle actually affecting things. They all
    // operate on the beam's current position (i.e., whatever pos()
    // returns.)
    void step();
public:
    void affect_cell();
    void affect_endpoint();
    void affect_endpoint(beam_tracer& tracer);
private:
    void affect_wall();
    void digging_wall_effect();
    void sporangium_wall_effect();
    void burn_wall_effect();
    void affect_ground();
    void affect_place_clouds();
    void affect_place_explosion_clouds();
    int range_used(bool leg_only = false) const;
    void finish_beam();
    void drop_object();

    // These methods make the beam affect a specific actor, not
    // necessarily what's at pos().
public:
    void affect_actor(actor *act);
private:
    // for monsters
    void affect_monster(monster* m);
    void kill_monster(monster &m);
    bool check_for_friendly_past_target(monster* mon);
    bool attempt_block(monster* mon);
    void update_hurt_or_helped(monster* mon);
    void enchantment_affect_monster(monster* mon);
public:
    mon_resist_type try_enchant_monster(monster* mon, int &res_margin);
    mon_resist_type apply_enchantment_to_monster(monster* mon);
    void apply_beam_conducts();
private:
    void apply_bolt_paralysis(monster* mons);
    void apply_bolt_petrify(monster* mons);
    void handle_enchant_chaining(coord_def centre);
    void monster_post_hit(monster* mon, int dmg);
    // for players
    void affect_player();
    bool misses_player();
public:
    void affect_player_enchantment(bool resistible = true);
private:
    void internal_ouch(int dam);
    // for both
    void knockback_actor(actor *act, int dam);
    void pull_actor(actor *act, int dam);

    // tracers
    void tracer_affect_player();
    void tracer_affect_monster(monster* mon);
    void tracer_enchantment_affect_monster(monster* mon);
    void tracer_nonenchantment_affect_monster(monster* mon);
    bool has_relevant_side_effect(monster* mon);

    // methods to change the path
    void bounce();
    void reflect();
public:
    void choose_ray();

    bool is_tracer() const noexcept { return tracer != nullptr; }
    void set_is_tracer(bool value) noexcept;
};

int mons_adjust_flavoured(monster* mons, bolt &pbolt, int hurted,
                          bool doFlavouredEffects = true);

// Return whether the effect was visible.
bool enchant_actor_with_flavour(actor* victim, const actor *atk,
                                beam_type flavour, int powc = 0);

bool enchant_monster_invisible(monster* mon, const string &how);

bool ench_flavour_affects_monster(actor *agent, beam_type flavour,
                                  const monster* mon, bool intrinsic_only = false);
spret mass_enchantment(enchant_type wh_enchant, int pow,
                            bool fail = false);
int ench_power_stepdown(int pow);

bool poison_monster(monster* mons, const actor* who, int levels = 1,
                    bool force = false, bool verbose = true);
bool miasma_monster(monster* mons, const actor* who);
bool sticky_flame_monster(monster* mons, const actor* who, int dur,
                    bool verbose = true);
bool curare_actor(actor* source, actor* target, string name,
                  string source_name, int bonus_poison = 0);
int silver_damages_victim(actor* victim, int damage, string &dmg_msg);
void fire_tracer(const monster* mons, targeting_tracer& tracer,
                 bolt &pbolt, bool explode_only = false,
                 bool explosion_hole = false);
spret zapping(zap_type ztype, int power, bolt &pbolt,
                   bool needs_tracer = false, const char* msg = nullptr,
                   bool fail = false);
void fire_partial_player_tracer(zap_type ztype, int power,
                                player_beam_tracer& tracer,
                                bolt &pbolt, int range = 0);
bool player_tracer(zap_type ztype, int power, bolt &pbolt, int range = 0);

set<coord_def> create_feat_splash(coord_def center, int radius, int num, int dur,
                                  dungeon_feature_type new_feat = DNGN_SHALLOW_WATER);

void init_zap_index();
void clear_zap_info_on_exit();

bool zap_explodes(zap_type ztype);
bool zap_is_enchantment(zap_type ztype);
int zap_ench_power(zap_type z_type, int pow, bool is_monster);
int zap_to_hit(zap_type z_type, int power, bool is_monster);
dice_def zap_damage(zap_type z_type, int power, bool is_monster, bool random = true);
colour_t zap_colour(zap_type z_type);

dice_def combustion_breath_damage(int pow, bool allow_random = true);

void zappy(zap_type z_type, int power, bool is_monster, bolt &pbolt);
void bolt_parent_init(const bolt &parent, bolt &child);

int explosion_noise(int rad);

int omnireflect_chance_denom(int SH);

void glaciate_freeze(monster* mon, killer_type englaciator,
                             int kindex);

void fill_chain_targets(const bolt& beam, coord_def centre,
                        vector<coord_def> &targs, bool random);

bolt setup_targeting_beam(const monster &mons);

bool cancel_beam_prompt(const bolt& beam, const player_beam_tracer& tracer,
                        int beams_fired = 1);

int apply_willpower_bypass(const actor& source, int willpower);
int apply_willpower_bypass(const monster_info& source, int willpower);
int guile_will_reduction(bool max = false);
