/**
 * @file
 * @brief Functions used when building new levels.
**/

#pragma once

#include <algorithm>
#include <set>
#include <stdexcept>
#include <vector>

#include "env.h"
#include "mapdef.h"
#include "tag-version.h"

COMPILE_CHECK(sizeof(feature_property_type) <= sizeof(terrain_property_t));

#define BUILD_METHOD_KEY "build_method_key"
#define LAYOUT_TYPE_KEY  "layout_type_key"

// See _build_overflow_temples() in dungeon.cc for details on overflow
// temples.
#define TEMPLE_GODS_KEY      "temple_gods_key"
#define OVERFLOW_TEMPLES_KEY "overflow_temples_key"
#define TEMPLE_MAP_KEY       "temple_map_key"
#define TEMPLE_SIZE_KEY      "temple_size_key"

#if TAG_MAJOR_VERSION == 34
#define TOMB_STONE_STAIRS_KEY "tomb_stone_stairs_key"
#endif
#define HATCH_NAME_PROP "_hatch_name"
#define HATCH_DEST_NAME_PROP "_hatch_dest_name"

#define TRANSPORTER_NAME_PROP "_transporter_name"
#define TRANSPORTER_DEST_NAME_PROP "_transporter_dest_name"

#define DESCENT_STAIRS_KEY "descent_stairs_gone"

const unsigned short INVALID_MAP_INDEX = 10000;

// Should be the larger of GXM/GYM
#define MAP_SIDE ((GXM) > (GYM) ? (GXM) : (GYM))

// Map mask constants.

// Be sure to change flag_list in keyed_mapspec::set_mask to match!
enum map_mask_type
{
    MMT_NONE            = 0x0,
    MMT_VAULT           = 0x01,  // This is a square in a vault.
    MMT_NO_ITEM         = 0x02,  // Random items should not be placed here.
    MMT_NO_MONS         = 0x04,  // Random monsters should not be placed here.
    MMT_NO_POOL         = 0x08,  // Pool fixup should not be applied here.
                       // 0x10,  // Unused
    MMT_NO_WALL         = 0x20,  // Wall fixup should not be applied here.
    MMT_OPAQUE          = 0x40,  // Vault may impede connectivity.
    MMT_NO_TRAP         = 0x80,  // No trap generation
    MMT_MIMIC           = 0x100, // Feature mimics
    MMT_NO_MIMIC        = 0x200, // Feature shouldn't be turned into a mimic.
#if TAG_MAJOR_VERSION == 34
    MMT_WAS_DOOR_MIMIC  = 0x400, // There was a door mimic there.
#endif
    MMT_TURNED_TO_FLOOR = 0x800, // This feature was dug, deconstructed or such.
    MMT_PASSABLE        = 0x1000 // Passable for purposes of determining level connectivity
};

class dgn_region;
typedef vector<dgn_region> dgn_region_list;

struct dgn_veto_exception : public runtime_error
{
    dgn_veto_exception(const string& msg) : runtime_error(msg) {}
    dgn_veto_exception(const char *msg) : runtime_error(msg) {}
};

class dgn_region
{
public:
    // pos is top-left corner.
    coord_def pos, size;

    dgn_region(int left, int top, int width, int height)
        : pos(left, top), size(width, height)
    {
    }

    dgn_region(const coord_def &_pos, const coord_def &_size)
        : pos(_pos), size(_size)
    {
    }

    dgn_region() : pos(-1, -1), size()
    {
    }

    coord_def end() const
    {
        return pos + size - coord_def(1, 1);
    }

    coord_def random_edge_point() const;
    coord_def random_point() const;

    static dgn_region absolute(int left, int top, int right, int bottom)
    {
        return dgn_region(left, top, right - left + 1, bottom - top + 1);
    }

    static dgn_region absolute(const coord_def &c1, const coord_def &c2)
    {
        return dgn_region(c1.x, c1.y, c2.x, c2.y);
    }

    static bool between(int val, int low, int high)
    {
        return val >= low && val <= high;
    }

    bool contains(const coord_def &p) const
    {
        return contains(p.x, p.y);
    }

    bool contains(int xp, int yp) const
    {
        return xp >= pos.x && xp < pos.x + size.x
               && yp >= pos.y && yp < pos.y + size.y;
    }

    bool fully_contains(const coord_def &p) const
    {
        return p.x > pos.x && p.x < pos.x + size.x - 1
               && p.y > pos.y && p.y < pos.y + size.y - 1;
    }

    bool overlaps(const dgn_region &other) const;
    bool overlaps_any(const dgn_region_list &others) const;
    bool overlaps(const dgn_region_list &others,
                  const map_mask &dgn_map_mask) const;
    bool overlaps(const map_mask &dgn_map_mask) const;
};

struct vault_placement
{
public:
    coord_def pos;
    coord_def size;

    map_section_type orient;
    map_def map;
    vector<coord_def> exits;

    // The PC has seen at least one square of this vault.
    bool seen;

public:
    vault_placement();
    void reset();
    void apply_grid();
    void draw_at(const coord_def &c);
    int connect(bool spotty = false) const;
    string map_name_at(const coord_def &c) const;
    dungeon_feature_type feature_at(const coord_def &c);
    bool is_exit(const coord_def &c) const;
    bool is_space(const coord_def &c) const;
};

class vault_place_iterator
{
public:
    vault_place_iterator(const vault_placement &vp);
    operator bool () const;
    coord_def operator * () const;
    coord_def vault_pos() const;
    const coord_def *operator -> () const;
    vault_place_iterator &operator ++ ();
    vault_place_iterator operator ++ (int);
private:
    const vault_placement &vault_place;
    coord_def pos;
    coord_def tl, br;
};

class unwind_vault_placement_mask
{
public:
    unwind_vault_placement_mask(const map_bitmask *mask);
    ~unwind_vault_placement_mask();
private:
    const map_bitmask *oldmask;
};

extern vector<vault_placement> Temp_Vaults;

extern const map_bitmask *Vault_Placement_Mask;
extern FixedVector<string_set, NUM_BRANCHES> branch_uniq_map_tags;

set<string> &get_uniq_map_tags();
set<string> &get_uniq_map_names();

void init_level_connectivity();
void read_level_connectivity(reader &th);
void write_level_connectivity(writer &th);

bool builder(bool enable_random_maps = true);

int dgn_builder_x();
int dgn_builder_y();

void dgn_clear_vault_placements();
void dgn_erase_unused_vault_placements();
void dgn_reset_player_data();

double dgn_degrees_to_radians(int degrees);
bool dgn_has_adjacent_feat(coord_def c, dungeon_feature_type feat);
coord_def dgn_random_point_in_margin(int margin);
coord_def dgn_random_point_from(const coord_def &c, int radius, int margin = 1);
coord_def dgn_find_feature_marker(dungeon_feature_type feat);

// Generate 3 stone stairs in both directions.
void dgn_place_stone_stairs(bool maybe_place_hatches = false);

void dgn_set_grid_colour_at(const coord_def &c, int colour);

const vault_placement *dgn_place_map(const map_def *map,
                                     bool check_collision,
                                     bool make_no_exits,
                                     const coord_def &pos = INVALID_COORD);

const vault_placement *dgn_safe_place_map(const map_def *map,
                                          bool check_collision,
                                          bool make_no_exits,
                                          const coord_def &pos = INVALID_COORD);

void dgn_record_veto(const dgn_veto_exception &e);

void level_clear_vault_memory();
void run_map_epilogues();

void place_specific_trap(const coord_def& where, trap_type trap_spec, int charges = 0);

struct shop_spec;
void place_spec_shop(const coord_def& where, shop_type force_type);
void place_spec_shop(const coord_def& where, shop_spec &spec, int shop_level = 0);
int greed_for_shop_type(shop_type shop, int level_number);
object_class_type item_in_shop(shop_type shop_type);
bool seen_destroy_feat(dungeon_feature_type old_feat);
bool map_masked(const coord_def &c, unsigned mask);
coord_def dgn_find_nearby_stair(dungeon_feature_type stair_to_find,
                                coord_def base_pos, bool find_closest,
                                string hatch_name = "");

class mons_spec;
monster *dgn_place_monster(mons_spec &mspec, coord_def where,
                           bool force_pos = false, bool generate_awake = false,
                           bool patrolling = false);
int dgn_place_item(const item_spec &spec,
                   const coord_def &where,
                   int level = INVALID_ABSDEPTH);

class item_list;
void dgn_place_multiple_items(item_list &list,
                              const coord_def& where);

void dgn_set_branch_epilogue(branch_type br, string callback_name);

void dgn_reset_level(bool enable_random_maps = true);

const vault_placement *dgn_register_place(const vault_placement &place,
                                          bool register_vault);

int dgn_count_disconnected_zones(
    bool choose_stairless,
    dungeon_feature_type fill = DNGN_UNSEEN);

int dgn_count_tele_zones(bool choose_stairless);

void dgn_replace_area(const coord_def& p1, const coord_def& p2,
                      dungeon_feature_type replace,
                      dungeon_feature_type feature,
                      unsigned mmask = 0, bool needs_update = false);
void dgn_replace_area(int sx, int sy, int ex, int ey,
                      dungeon_feature_type replace,
                      dungeon_feature_type feature,
                      unsigned mmask = 0, bool needs_update = false);

vault_placement *dgn_vault_at(coord_def gp);
void dgn_seen_vault_at(coord_def gp);
vault_placement *dgn_find_layout();

string dump_vault_maps();

bool dgn_square_travel_ok(const coord_def &c);

// Resets travel_point_distance!
vector<coord_def> dgn_join_the_dots_pathfind(const coord_def &from,
                                             const coord_def &to,
                                             uint32_t mapmask);

bool join_the_dots(const coord_def &from, const coord_def &to,
                   unsigned mmask,
                   bool (*overwritable)(dungeon_feature_type) = nullptr);
int count_feature_in_box(int x0, int y0, int x1, int y1,
                         dungeon_feature_type feat);
bool door_vetoed(const coord_def pos);

void fixup_misplaced_items();

void dgn_place_transporter(const coord_def &pos, const coord_def &dest);
bool dgn_make_transporters_from_markers();

int starting_absdepth();
bool in_descent_parent(branch_type branch);

int concretize_item_level(int spec_level, int dgn_level = INVALID_ABSDEPTH);
