#include "player.h"

#include "constants.h"
#include "enemy.h"
#include "level.h"

player_base::player_base(RNG *rng, const feature enabled_features,
                         const enum race &nrace):
        character{rng, enabled_features, nrace, {0, 0}},
gold_cnt{0}, known_potions{0}, inv{{}, 0, false}, MAX_THROW_DIST{5} {}

void player_base::print(output *out) {
        out->print_char(pos, '@', COLOR_PAIR(COLOR_BLUE));

        if (inv.enabled)
                inv.print(out, known_potions);
}

std::string player_base::get_abbrev() const {
        return "PC";
}

int player_base::get_gold() const {
        return this->gold_cnt;
}

int player_base::get_ATK() const {
        return this->ATK;
}

int player_base::get_DEF() const {
        return this->DEF;
}

int player_base::get_HP() const {
        return this->HP;
}

long_result player_base::apply(std::unique_ptr<potion> p) {
        if (p == nullptr)
                return {result::APPLIED_NOTHING,
                        "PC tried to breathe the magic in the air. "};

        std::string name = p->get_name();

        known_potions |= 1 << p->get_type();

        apply_effect(std::move(p));

        if (race == RT_800)
                return {result::APPLIED,
                        "PC gets rusty joints (-50 HP). "};

        return {result::APPLIED,
                "PC uses potion of " + name + ". "};
}

long_result player_base::attack(character *ch) {
        if (ch == nullptr)
                return {result::NOTHING,
                        "PC tried to attack thin air. "};

        return ch->get_hit(this, ATK, base_hit_rate);
}

long_result player_base::move(level *lvl,
                              const position &p) {
        if (enabled_features & FEATURE_NCURSES) {
                enemy_base *tmp = nullptr;

                if (lvl->is_available(p, true)) {
                        pos = p;
                        return {result::MOVED, ""};
                } else if ((tmp = get_enemy_at(p, lvl->get_elist())) != nullptr) {
                        auto res = attack(static_cast<character *>(tmp));

                        if (tmp->is_dead()) {
                                int g = tmp->dies(lvl);
                                lvl->erase_enemy(tmp);

                                if (g)
                                        res.msg += "PC gains " +
                                                   std::to_string(g) +
                                                   " pieces of gold. ";

                                gold_cnt += g;
                        }

                        return res;
                }
        } else if (lvl->get_up_stairs() == p &&
                   enabled_features & FEATURE_REVISIT) {
                return {result::GO_UP, "PC goes up the stairs. "};
        } else if (lvl->get_down_stairs() == p) {
                return {result::GO_DOWN, "PC goes down the stairs. "};
        } else if (lvl->is_available(p, true)) {
                pos = p;
                return {result::MOVED, ""};
        }

        return {result::NOTHING, "PC tried to move into non-existent space. "};
}

void player_base::pickup_gold(level *lvl, std::string &msg) {
        gold g{{0, 0}, 0};
        int gold_tmp = 0;

        while ((g = get_gold_at(pos, lvl->get_glist())).get_amount() != 0)
                gold_tmp += g.get_amount();

        if (gold_tmp)
                msg += "PC picks up " + std::to_string(gold_tmp) +
                       " pieces of gold. ";

        bool locked = false;

        for (auto g : lvl->get_glist())
                if (g.is_locked() && g.get_pos() == pos)
                        locked = true;

        if (locked)
                msg += "There's a pile of gold here that PC cannot pick up. ";

        gold_cnt += gold_tmp;

}

long_result player_base::get_hit(character *ch, const int tATK,
                                 const fraction &hit_rate) {
        if (rng->trial(hit_rate)) {
                int tmp = calc_dmg(tATK, DEF);
                tmp = tmp > HP ? HP : tmp;
                HP -= tmp;

                if (HP == 0)
                        return {result::DIED,
                                ch->get_abbrev() + " deals " +
                                std::to_string(tmp) +
                                " damage to PC. PC is slain by " +
                                ch->get_abbrev() + ". "};

                return {result::HIT, ch->get_abbrev() + " deals " +
                        std::to_string(tmp) +
                        " damage to PC. "};
        }

        return {result::MISS, ch->get_abbrev() + " tried to hit PC but missed. "};
}

void player_base::add_gold(int amount) {
        gold_cnt += amount;
}

long_result player_base::throw_potion(level *lvl, std::unique_ptr<potion> p,
                                      direction dir) {
        if (p == nullptr)
                return {NOTHING, "PC tried to throw a vial of air. "};

        position tmp{pos};

        for (int i = 0; i < MAX_THROW_DIST; ++i) {
                tmp += MOVE[dir];
                size_t flag = lvl->what_is_at(tmp);

                if (flag & WHAT_WALL)
                        return {THROWN, "The potion shatters against a wall. "};
                else if (flag & WHAT_ENEMY) {
                        std::string name{p->get_name()};
                        character *ch = lvl->get_elist()[flag ^ WHAT_ENEMY];
                        known_potions |= 1 << p->get_type();
                        ch->apply_effect(std::move(p));

                        if (ch->get_race() != BABY_DRAGON)
                                return {THROWN, "The potion of " + name +
                                        "'s contents spills on " +
                                        ch->get_abbrev() + ". "};

                        return {THROWN, "The potion of " + name +
                                " doesn't seem to affect B. "};
                }
        }

        return {THROWN, "The potion shatters against the floor. "};
}

long_result player_base::interpret_command(level *lvl, game_command cmd) {
        if (inv.enabled) {
                if (cmd == TOGGLE_INVENTORY || cmd == GAME_COMMAND_TERMINATE) {
                        inv.enabled = false;
                        return {result::INVENTORY, ""};
                } else {

                        auto res = inv.run(cmd, enabled_features);

                        if (res.second == -1) {
                                inv.enabled = false;
                                return apply(std::move(res.first));
                        } else if (res.first == nullptr) {
                                return {result::INVENTORY, ""};
                        } else if (res.second == DIRECTION_CNT) {
                                inv.enabled = false;
                                return {result::THROWN,
                                        "PC tried to throw a vial of air. "};
                        } else {
                                inv.enabled = false;
                                return throw_potion(lvl, std::move(res.first),
                                                    static_cast<direction>(res.second));
                        }
                }
        }

        if (cmd == GAME_COMMAND_TERMINATE) {
                return {result::TERMINATE, ""};
        } else if (cmd >= MOVE_NORTH && cmd <= MOVE_SOUTHWEST) {
                auto res = move(lvl, pos + MOVE[cmd - MOVE_NORTH]);

                pickup_gold(lvl, res.msg);

                if (enabled_features & FEATURE_INVENTORY) {
                        size_t idx = get_potion_at(pos, lvl->get_plist());

                        if (idx != lvl->get_plist().size() &&
                            inv.owns.size() < INV_MAX_CNT) {
                                inv.insert(lvl->detach_potion(idx));
                                res.msg += "PC picks up a potion. ";
                        } else if (idx != lvl->get_plist().size()) {
                                res.msg += "PC already has a full knapsack. ";
                        }
                }


                return res;
        } else if (cmd >= APPLY_NORTH && cmd <= APPLY_SOUTHWEST) {
                size_t idx = get_potion_at(pos + MOVE[cmd - APPLY_NORTH],
                                           lvl->get_plist());
                auto res = apply(lvl->detach_potion(idx));
                return res;
        } else if (cmd == APPLY_PANIC) {
                return {result::NOTHING,
                        "PC tried to use in some non-existent direction. "};
        } else if (cmd >= ATTACK_NORTH && cmd <= ATTACK_SOUTHWEST) {
                enemy_base *tmp = get_enemy_at(pos + MOVE[cmd - ATTACK_NORTH],
                                               lvl->get_elist());

                auto res = attack(static_cast<character *>(tmp));

                if (tmp != nullptr && tmp->is_dead()) {
                        int g = tmp->dies(lvl);
                        lvl->erase_enemy(tmp);

                        if (g)
                                res.msg += "PC gains " +
                                           std::to_string(g) +
                                           " pieces of gold. ";

                        gold_cnt += g;
                }

                pickup_gold(lvl, res.msg);

                return res;
        } else if (cmd == UP_STAIRS) {
                if (lvl->get_up_stairs() == pos &&
                    enabled_features & FEATURE_REVISIT)
                        return {GO_UP, "PC goes up the stairs. "};

                return {result::NOTHING,
                        "PC tried to fly through the ceiling. "};
        } else if (cmd == DOWN_STAIRS) {
                if (lvl->get_down_stairs() == pos)
                        return {GO_DOWN, "PC goes down the stairs. "};

                return {result::NOTHING,
                        "PC tried to dig through the floor. "};
        } else if (cmd == THE_WORLD) {
                return {TOGGLE_THE_WORLD, "PC toggles Stand: The World! "};
        } else if (cmd == STAR_PLATINUM) {
                return {TOGGLE_STAR_PLATINUM,
                        "PC toggles Stand: Star Platinum: The World! "};
        } else if (cmd == GAME_RESTART) {
                return {RESTART_GAME, ""};
        } else if (cmd == GAME_COMMAND_PASS) {
                return {result::NOTHING, ""};
        } else if (cmd == TOGGLE_INVENTORY &&
                   enabled_features & FEATURE_INVENTORY) {
                inv.start();
                return {result::INVENTORY, ""};
        }

        return {UNKNOWN, "PC tried to produce some undefined behaviour. "};
}

void player_base::inventory::start() {
        curr = 0;
        enabled = true;
}

std::pair<std::unique_ptr<potion>, int> player_base::inventory::run(
        game_command cmd, const feature enabled_features) {
        if (cmd == MOVE_NORTH) {
                if (curr != 0)
                        --curr;

                return {nullptr, 0};
        } else if (cmd == MOVE_SOUTH) {
                if (curr < owns.size() - 1)
                        ++curr;

                return {nullptr, 0};
        } else if (cmd == ENTER) {
                if (owns.size()) {
                        std::unique_ptr<potion> tmp = std::move(owns[curr]);
                        owns.erase(owns.begin() + curr);
                        return {std::move(tmp), -1};
                }

                return {nullptr, -1};
        } else if (cmd >= THROW_NORTH && cmd <= THROW_SOUTHWEST &&
                   enabled_features & FEATURE_THROW) {
                if (owns.size()) {
                        std::unique_ptr<potion> tmp = std::move(owns[curr]);
                        owns.erase(owns.begin() + curr);
                        return {std::move(tmp),
                                static_cast<direction>(cmd - THROW_NORTH)};
                }

                return {nullptr, DIRECTION_CNT};
        }

        return {nullptr, 0};
}

void player_base::inventory::insert(std::unique_ptr<potion> p) {
        owns.push_back(std::move(p));
}

void player_base::inventory::print(output *out, unsigned long known_potions) {
        if (!enabled)
                return;

        // Draw out a window on top of the existing output
        for (int y = INV_TOP; y < INV_BOTTOM; ++y)
                for (int x = INV_LEFT; x < INV_RIGHT; ++x)
                        out->print_char({x, y}, ' ');

        for (int x = INV_LEFT; x < INV_RIGHT; ++x) {
                out->print_char({x, INV_TOP}, '-', COLOR_PAIR(COLOR_MAGENTA));
                out->print_char({x, INV_BOTTOM - 1}, '-',
                                COLOR_PAIR(COLOR_MAGENTA));
        }

        for (int y = INV_TOP; y < INV_BOTTOM; ++y) {
                out->print_char({INV_LEFT, y}, '|', COLOR_PAIR(COLOR_MAGENTA));
                out->print_char({INV_RIGHT - 1, y}, '|', COLOR_PAIR(COLOR_MAGENTA));
        }

        out->print_str({INV_LEFT + 1, INV_TOP},
                       "Potions:", COLOR_PAIR(COLOR_CYAN));

        for (size_t i = 0; i < owns.size(); ++i) {
                out->print_str({INV_LEFT + INV_POTION_OFFSET,
                                static_cast<int>(i) + INV_TOP + INV_Y_OFFSET},
                               "potion of " +
                               (known_potions & (1 << owns[i]->get_type()) ?
                                owns[i]->get_name() : std::string{"??"}));
        }

        if (owns.size())
                out->print_str({INV_LEFT + INV_CURSOR_OFFSET,
                                static_cast<int>(curr) + INV_TOP + INV_Y_OFFSET},
                               "->");
}

void player_base::set_potion_known(const potion_type &type) {
        known_potions |= 1 << type;
}
