#include "cc3k.h"

#include <iostream>
#include <fstream>
#include "constants.h"

CC3K::CC3K(const feature enabled_features,
           input *in, output *out, RNG *rng, std::ifstream &data):
        enabled_features{enabled_features},
        in{in}, out{out}, rng{rng} {
        curr_menu = std::make_unique<menu>(enabled_features);
        out->clear();
        curr_menu->print(out, rng->get_init_seed());
        out->render();
        gresult.status = game_status::MAIN_MENU;

        if (enabled_features & FEATURE_READ_MAP)
                proc_lvls(data);
}

game_status CC3K::run() {
        switch (gresult.status) {
        case MAIN_MENU: {
                auto tmp = curr_menu->run(in);

                if (tmp == -2) {
                        gresult.status = TERMINATED;
                        return TERMINATED;
                }

                if (tmp != -1) {
                        curr_menu.reset();
                        curr_game =
                                std::make_unique<game>(static_cast<race>(tmp),
                                                       enabled_features,
                                                       in, out, rng,
                                                       lvls);
                        out->clear();
                        curr_game->print();
                        out->render();
                        gresult.status = IN_GAME;
                        return IN_GAME;
                }

                out->clear();
                curr_menu->print(out, rng->get_init_seed());
                out->render();
                return MAIN_MENU;
        }

        case IN_GAME: {
                gresult = curr_game->run();

                if (gresult.status == RESTART) {
                        curr_game.reset();
                        curr_menu = std::make_unique<menu>(enabled_features);
                        out->clear();
                        curr_menu->print(out, rng->get_init_seed());
                        out->render();
                        gresult.status = MAIN_MENU;
                        return MAIN_MENU;
                } else if (gresult.status == IN_GAME) {
                        out->clear();
                        curr_game->print();
                        out->render();
                        return IN_GAME;
                } else if (gresult.status == TERMINATED) {
                        return TERMINATED;
                }

                out->clear();
                gresult.print(out);
                out->render();
                return gresult.status;
        }

        case DEAD:
        case WON:
        case ESCAPED:
                curr_game.reset();
                gresult.run(in);

                if (gresult.status == RESTART) {
                        curr_menu = std::make_unique<menu>(enabled_features);
                        out->clear();
                        curr_menu->print(out, rng->get_init_seed());
                        out->render();
                        gresult.status = MAIN_MENU;
                        return MAIN_MENU;
                }

                return TERMINATED;

        default:
                break;
        }

        return TERMINATED;
}

void CC3K::proc_lvls(std::ifstream &data) {
        lvls.reserve(DEFAULT_LVL_CNT);

        for (int i = 0; i < DEFAULT_LVL_CNT; ++i) {
                level_data tmp = {POS_NIL, {}, {}, {}, {}, POS_NIL};
                std::string buffer;

                for (int y = 0; y < MAP_HEIGHT; ++y) {
                        getline(data, buffer);

                        for (int x = 0; x < MAP_WIDTH; ++x)
                                push_tile(buffer[x], {x, y}, tmp);
                }

                number_rooms(tmp);

                lvls.push_back(tmp);
        }
}

size_t CC3K::remap_pos(const position &pos) {
        return pos.x + pos.y * MAP_WIDTH;
}

void CC3K::number_rooms(level_data &lvl) {
        int curr_room_cnt = 0;

        for (int y = 0; y < MAP_HEIGHT; ++y)
                for (int x = 0; x < MAP_WIDTH; ++x)
                        if (lvl.map_data[remap_pos({x, y})] == '.') {
                                number_block(lvl.map_data, {x, y}, curr_room_cnt);
                                ++curr_room_cnt;
                        }
}

bool CC3K::in_bounds(const position &pos) {
        return pos.x >= 0 && pos.x < MAP_WIDTH &&
               pos.y >= 0 && pos.y < MAP_HEIGHT;
}

void CC3K::number_block(std::string &map_data, const position &pos, int num) {
        if (map_data[remap_pos(pos)] != '.')
                return;

        map_data[remap_pos(pos)] = num + '0';

        for (int i = 0; i < DIRECTION_CNT; ++i)
                if (in_bounds(pos + MOVE[i]) &&
                    map_data[remap_pos(pos + MOVE[i])] == '.')
                        number_block(map_data, pos + MOVE[i], num);
}

void CC3K::push_tile(const char ch, const position &pos, level_data &lvl) {
        switch (ch) {
        case '-':
        case '|':
        case ' ':
        case '+':
        case '#':
                lvl.map_data += ch;
                return;

        case '@':
                lvl.pc_pos = pos;
                break;

        case '\\':
                lvl.stairs = pos;
                break;

        case '0':
                lvl.potions.push_back({RESTORE_HEALTH, pos});
                break;

        case '1':
                lvl.potions.push_back({BOOST_ATK, pos});
                break;

        case '2':
                lvl.potions.push_back({BOOST_DEF, pos});
                break;

        case '3':
                lvl.potions.push_back({POISON_HEALTH, pos});
                break;

        case '4':
                lvl.potions.push_back({WOUND_ATK, pos});
                break;

        case '5':
                lvl.potions.push_back({WOUND_DEF, pos});
                break;

        case '6':
                lvl.gold_piles.push_back({pos, GOLD_NORMAL});
                break;

        case '7':
                lvl.gold_piles.push_back({pos, GOLD_SMALL});
                break;

        case '8':
                lvl.gold_piles.push_back({pos, GOLD_MERCHANT});
                break;

        case '9':
                lvl.gold_piles.push_back({pos, GOLD_DRAGON});
                break;

        case 'H':
                lvl.enemies.push_back({HUMAN, pos});
                break;

        case 'W':
                lvl.enemies.push_back({DWARF, pos});
                break;

        case 'E':
                lvl.enemies.push_back({ELF, pos});
                break;

        case 'O':
                lvl.enemies.push_back({ORC, pos});
                break;

        case 'M':
                lvl.enemies.push_back({MERCHANT, pos});
                break;

        case 'D':
                lvl.enemies.push_back({DRAGON, pos});
                break;

        case 'L':
                lvl.enemies.push_back({HALFLING, pos});
                break;

        default:
                break;
        }

        lvl.map_data += '.';
}
