#include "level.h"

#include "constants.h"

level::level(character *player, RNG *rng, const feature enabled_features):
        enabled_features{enabled_features}, map{player, rng, enabled_features},
        player{player} {
        auto tiles = map.get_room_list();

        for (size_t i = 0; i < tiles.size(); ++i)
                remove_from_list(tiles[i], player->get_pos());

        for (size_t i = 0; i < tiles.size(); ++i)
                remove_from_list(tiles[i], map.get_down_stairs());

        if (enabled_features & FEATURE_REVISIT)
                for (size_t i = 0; i < tiles.size(); ++i)
                        remove_from_list(tiles[i], map.get_up_stairs());

        gen_potions(rng, tiles);
        gen_gold(rng, tiles);
        gen_enemies({}, rng, tiles);
}

level::level(const level_data &lvl, character *player, RNG *rng,
             const feature enabled_features):
        enabled_features{enabled_features},
        map{lvl, player, rng, enabled_features}, player{player} {
        auto tiles = map.get_room_list();

        for (size_t i = 0; i < tiles.size(); ++i)
                remove_from_list(tiles[i], player->get_pos());

        for (size_t i = 0; i < tiles.size(); ++i)
                remove_from_list(tiles[i], map.get_down_stairs());

        if (enabled_features & FEATURE_REVISIT)
                for (size_t i = 0; i < tiles.size(); ++i)
                        remove_from_list(tiles[i], map.get_up_stairs());

        if (enabled_features & FEATURE_READ_MAP)
                fill(lvl, tiles, rng);

        if (enabled_features & FEATURE_READ_MAP &&
            (lvl.enemies.size() ||
             lvl.potions.size() ||
             lvl.gold_piles.size()))
                return;

        gen_potions(rng, tiles);
        gen_gold(rng, tiles);
        gen_enemies(lvl, rng, tiles);
}

void level::fill(const level_data &lvl, std::vector<position_list> &tiles,
                 RNG *rng) {
        for (auto [type, pos] : lvl.potions) {
                for (size_t i = 0; i < tiles.size(); ++i)
                        remove_from_list(tiles[i], pos);

                pplist.push_back(new_potion(type, pos));
                plist.push_back(pplist[pplist.size() - 1].get());
        }

        for (auto g : lvl.gold_piles) {
                for (size_t i = 0; i < tiles.size(); ++i)
                        remove_from_list(tiles[i], g.get_pos());

                glist.push_back(g);
        }

        for (auto [type, pos] : lvl.enemies) {
                for (size_t i = 0; i < tiles.size(); ++i)
                        remove_from_list(tiles[i], pos);

                if (type != DRAGON)
                        pelist.push_back(new_enemy(type, pos, enabled_features,
                                                   map.which_room(pos), rng));
                else {
                        gold guards{POS_NIL, 0};

                        for (size_t i = 0; i < glist.size(); ++i)
                                if (glist[i].get_amount() == GOLD_DRAGON &&
                                    is_adjacent(glist[i].get_pos(), pos))
                                        guards = glist[i];

                        pelist.push_back(new_enemy(type, pos, enabled_features,
                                                   map.which_room(pos), rng,
                                                   guards.get_pos()));
                }

                elist.push_back(pelist[pelist.size() - 1].get());
        }
}

gold_list level::dragon_hoard(const level_data &lvl) {
        gold_list result;

        for (auto g : glist)
                if (g.get_amount() == GOLD_DRAGON &&
                    !is_in_glist(g, lvl.gold_piles))
                        result.push_back(g);

        return result;
}

bool level::is_in_glist(gold &g, const gold_list &gl) {
        for (auto tmp : gl)
                if (tmp.get_pos() == g.get_pos())
                        return true;

        return false;
}

void level::gen_enemies(const level_data &lvl, RNG *rng,
                        std::vector<position_list> &tiles) {
        auto dhoard = dragon_hoard(lvl);
        int cnt = (enabled_features & FEATURE_EXTRA_STUFF ?
                   rng->rand_between(MIN_ENEMIE_CNT, MAX_ENEMIE_CNT + 1) :
                   MIN_ENEMIE_CNT) + dhoard.size();

        elist.reserve(cnt);
        pelist.reserve(cnt);

        for (size_t i = 0; i < dhoard.size(); ++i) {
                position_list spots;

                for (int dir = 0; dir < DIRECTION_CNT; ++dir) {
                        position tmp = dhoard[i].get_pos() + MOVE[dir];

                        if (map.which_room(tmp) != -1 && tmp != player->get_pos())
                                spots.push_back(dhoard[i].get_pos() + MOVE[dir]);
                }

                auto pos = spots.size() ? rng->get_rand_in_vector(spots) :
                           dhoard[i].get_pos();
                pelist.push_back(new_dragon(rng, pos, dhoard[i].get_pos(),
                                            enabled_features,
                                            map.which_room(dhoard[i].get_pos()),
                                            dhoard[i].get_pos()));
                int room = map.which_room(pos);
                remove_from_list(tiles[room], pos);

                if (!tiles[room].size())
                        tiles.erase(tiles.begin() + room);

                elist.push_back(pelist[i].get());
        }


        for (int i = elist.size(); i < cnt; ++i) {
                auto p = get_rand_pos(rng, tiles);
                pelist.push_back(new_enemy(rng, p, enabled_features,
                                           map.which_room(p)));
                elist.push_back(pelist[i].get());
        }
}

position level::get_rand_pos(RNG *rng, std::vector<position_list> &tiles) {
        int room;
        position pos;
        room = rng->rand_under(tiles.size());

        pos = rng->get_rand_in_vector(tiles[room]);
        remove_from_list(tiles[room], pos);

        if (!tiles[room].size())
                tiles.erase(tiles.begin() + room);

        return pos;
}

void level::add_gold(gold g) {
        glist.push_back(g);
}

void level::gen_gold(RNG *rng, std::vector<position_list> &tiles) {
        glist.reserve(GOLD_CNT);

        for (int i = glist.size(); i < GOLD_CNT; ++i)
                glist.push_back(gold{get_rand_pos(rng, tiles), rand_gold_pile(rng)});
}

void level::gen_potions(RNG *rng, std::vector<position_list> &tiles) {
        int cnt;
        int max_type;

        if (enabled_features & FEATURE_EXTRA_STUFF) {
                cnt = rng->rand_between(MIN_POTION_CNT, MAX_POTION_CNT + 1);
                max_type = POTION_TYPE_CNT;
        } else {
                cnt = MIN_POTION_CNT;
                max_type = DEFAULT_POTION_TYPE_CNT;
        }

        plist.reserve(cnt);
        pplist.reserve(cnt);

        for (int i = pplist.size(); i < cnt; ++i) {
                pplist.push_back(new_potion(static_cast<potion_type>
                                            (rng->rand_under(max_type)),
                                            get_rand_pos(rng, tiles)));
                plist.push_back(pplist[i].get());
        }
}

void level::print(output *out) const {
        map.print(out);

        for (size_t i = 0; i < plist.size(); ++i)
                plist[i]->print(out);

        for (size_t i = 0; i < glist.size(); ++i)
                glist[i].print(out);

        for (size_t i = 0; i < elist.size(); ++i)
                elist[i]->print(out);
}

bool level::is_available(const position &pos, bool is_player) const {
        if (!map.is_available(pos))
                return false;

        if (player->get_pos() == pos)
                return false;

        for (size_t i = 0; i < elist.size(); ++i)
                if (pos == elist[i]->get_pos())
                        return false;

        if (!(enabled_features & FEATURE_WALK_OVER))
                for (size_t i = 0; i < plist.size(); ++i)
                        if (pos == plist[i]->get_pos())
                                return false;

        if (!(enabled_features & FEATURE_WALK_OVER) && !is_player)
                for (size_t i = 0; i < glist.size(); ++i)
                        if (pos == glist[i].get_pos())
                                return false;

        return true;
}

bool level::is_available_all(const position &pos) const {
        if (!map.is_available(pos))
                return false;

        if (!(enabled_features & FEATURE_DOORS) &&
            map.which_room(pos) == -1)
                return false;

        if (player->get_pos() == pos)
                return false;

        for (size_t i = 0; i < elist.size(); ++i)
                if (pos == elist[i]->get_pos())
                        return false;

        for (size_t i = 0; i < plist.size(); ++i)
                if (pos == plist[i]->get_pos())
                        return false;

        for (size_t i = 0; i < glist.size(); ++i)
                if (pos == glist[i].get_pos())
                        return false;

        return true;
}

int level::get_room(const position &pos) const {
        return map.which_room(pos);
}

position_list level::get_available_around(const position &pos) const {
        position_list result;

        for (int i = 0; i < DIRECTION_CNT; ++i)
                if (is_available(pos + MOVE[i]))
                        result.push_back(pos + MOVE[i]);

        return result;
}

position_list level::get_available_around_all(const position &pos) const {
        position_list result;

        for (int i = 0; i < DIRECTION_CNT; ++i)
                if (is_available_all(pos + MOVE[i]))
                        result.push_back(pos + MOVE[i]);

        return result;
}

position level::get_up_stairs() const {
        return map.get_up_stairs();
}

position level::get_down_stairs() const {
        return map.get_down_stairs();
}

const enemy_list &level::get_elist() {
        return elist;
}

const potion_list &level::get_plist() {
        return plist;
}

gold_list &level::get_glist() {
        return glist;
}

std::unique_ptr<potion> level::detach_potion(size_t idx) {
        if (idx >= plist.size())
                return nullptr;

        auto tmp = std::move(pplist[idx]);
        plist.erase(plist.begin() + idx);
        pplist.erase(pplist.begin() + idx);
        return tmp;
}

void level::erase_enemy(character *ch) {
        if (ch == nullptr)
                return;

        for (size_t i = 0; i < elist.size(); ++i)
                if (elist[i] == ch) {
                        elist.erase(elist.begin() + i);
                        pelist.erase(pelist.begin() + i);
                }
}

size_t level::what_is_at(const position &pos) const {
        if (!map.is_available(pos))
                return WHAT_WALL;

        for (size_t i = 0; i < elist.size(); ++i)
                if (elist[i]->get_pos() == pos)
                        return WHAT_ENEMY | i;

        return WHAT_SPACE;
}
