#include <QFile>
#include <QString>
#include <QObject>
#include <QCloseEvent>
#include <QMessageBox>
#include <queue>

#include "gameinstance.h"
#include "gamewindow.h"

using namespace std;

const QString map_path = ":/resources/maps/maps.txt";

GameInstance::GameInstance(int _level, int _min_step):
    game_gui(new GameWindow(nullptr)),
    used_step(0),
    min_step(_min_step),
    level(_level),
    result(-1),
    finished(false),
    leakage(false),
    animation_timer(new QTimer())
{
    game_gui -> show();
    game_gui -> set_lcd(GameWindow::USED_STEP_LCD, 0);
    game_gui -> set_lcd(GameWindow::MIN_STEP_LCD, _min_step == -1 ? 999 : _min_step);
    game_gui -> set_lcd(GameWindow::LEVEL_LCD, _level);
    animation_timer->setInterval(200);
    load_map(_level);
    connect(game_gui -> get_done_button(), SIGNAL(clicked()), this, SLOT(on_done_button_clicked()));
    connect(game_gui, SIGNAL(closed()), this, SLOT(quit()));
    connect(animation_timer, SIGNAL(timeout()), this, SLOT(animation_proceed()));
}

void GameInstance::init_block(int _type, int _orientation, int _y, int _x)
{
    Block* ret = new Block(game_gui, _y, _x, this, _type, _orientation);
    connect(ret, SIGNAL(clicked()), ret, SLOT(pressed()));
    blocks[_y][_x] = ret;
}

void GameInstance::quit()
{
    emit game_over();
}

void GameInstance::load_map(int dest_level)
{
    QFile map_file(::map_path);
    map_file.open(QIODevice::ReadOnly | QIODevice::Text);
    QString str = map_file.readAll();
    string s = str.toStdString();
    int type = 0;
    int orient = 0;
    int cnt = 0;
    int level_count = 0;
    while (level_count != dest_level) {
        if (s[cnt] == '[') {
            level_count ++;
        }
        cnt ++;
    }
    for (int i = 0; i < MAP_SIZE; i++) {
        for (int j = 0; j < MAP_SIZE; j++) {
            while (s[cnt] < '0' || s[cnt] > '9') {
                cnt ++;
            }
            type = s[cnt ++] - '0';
            while (s[cnt] < '0' || s[cnt] > '9') {
                cnt ++;
            }
            orient = s[cnt ++] - '0';
            init_block(type, orient, i, j);
        }
     }
    map_file.close();
}

GameInstance::~GameInstance()
{
    if (game_gui) {
        delete game_gui;
    }
    delete animation_timer;
}

void GameInstance::block_pressed(int y, int x)
{
    if (!finished) {
            if (x >= 0 && x < MAP_SIZE && y >= 0 && y < MAP_SIZE) {
                blocks[y][x] -> rotate();
                used_step ++;
            }
            refresh_ui();
        }
}

int GameInstance::get_result()
{
    return result;
}

bool GameInstance::check_connection()
{
    // the function will return whether the water can flow from the source to the outlet
    // WITHOUT ANY LEAKAGE

    // connection[x][y][z]:
    //   whether the z side of block of type x and orientation y has an outlet
    // z:
    //   0 left 1 up 2 right 3 down

    static queue<pair<int, int> > q;
    static int vis[MAP_SIZE][MAP_SIZE];
    static int counter = 0;
    static const bool connection[5][4][4] = {
        {
            {0, 1, 1, 1},
            {1, 0, 1, 1},
            {1, 1, 0, 1},
            {1, 1, 1, 0}
        },
        {
            {0, 1, 1, 0},
            {0, 0, 1, 1},
            {1, 0, 0, 1},
            {1, 1, 0, 0}
        },
        {
            {1, 0, 1, 0},
            {0, 1, 0, 1},
            {1, 0, 1, 0},
            {0, 1, 0, 1}
        },
        {
            {1, 1, 1, 1},
            {1, 1, 1, 1},
            {1, 1, 1, 1},
            {1, 1, 1, 1}
        },
        {
            {0, 0, 0, 0},
            {0, 0, 0, 0},
            {0, 0, 0, 0},
            {0, 0, 0, 0}
        }
    };
    static const int det_x[4] = {-1, 0, 1, 0};
    static const int det_y[4] = {0, -1, 0, 1};

    Block* cnt;
    Block* tmp;

    path.clear();
    while (q.size()) {
        q.pop();
    }
    finished = false;
    leakage = false;
    counter ++;

    cnt = blocks[0][0];
    path.push_back(make_pair(0, 0));
    if (connection[cnt -> get_type()][cnt -> get_orientation()][0]) {
        q.push(make_pair(0, 0));
        vis[0][0] = counter;
    }

    while (q.size()) {
        int size = q.size();
        for (int i = 1; i <= size; i++) {
            int cx = q.front().first;
            int cy = q.front().second;
            cnt = blocks[cy][cx];
            for (int k = 0; k < 4; k++) {
                if (cx == 0 && cy == 0 && k == 0) continue;
                if (cx == MAP_SIZE - 1 && cy == MAP_SIZE - 1 && k == 2) continue;
                if (connection[cnt -> get_type()][cnt -> get_orientation()][k]) {
                    int tx = cx + det_x[k];
                    int ty = cy + det_y[k];
                    // check leakage
                    if (tx < 0 || tx >= MAP_SIZE || ty < 0 || ty >= MAP_SIZE) {
                        leakage = true;
                        return false;
                    }
                    tmp = blocks[ty][tx];
                    if (!connection[tmp -> get_type()][tmp -> get_orientation()][(k + 2) % 4]) {
                        leakage = true;
                        return false;
                    }
                    if (vis[ty][tx] != counter) {
                        q.push(make_pair(tx, ty));
                        vis[ty][tx] = counter;
                        path.push_back(make_pair(ty, tx));
                    }
                }
            }
            q.pop();
        }
    }

    cnt = blocks[MAP_SIZE - 1][MAP_SIZE - 1];
    if (vis[MAP_SIZE - 1][MAP_SIZE - 1] == counter
            && connection[cnt -> get_type()][cnt -> get_orientation()][2]) {
        finished = true;
    } else {
        finished = false;
    }

    return finished;
}

void GameInstance::on_done_button_clicked()
{
    if (check_connection()) {
        result = used_step;
        play_animation();
    } else {
        if (leakage) {
            QMessageBox::information(nullptr, "", "There's leakage in the maze.\nGame Over!");
            game_gui -> close();
        } else {
            QMessageBox::information(nullptr, "", "It seems that water can not flow into the outlet.\nGame Over!");
            game_gui -> close();
        }
    }
}

void GameInstance::play_animation()
{
    for (int i = 0; i < MAP_SIZE; i++) {
        for (int j = 0; j < MAP_SIZE; j++) {
            blocks[i][j] -> set_highlighted(false);
        }
    }
    animation_proceed(true);
    animation_timer -> start();
}

void GameInstance::animation_proceed(bool reset)
{
    static int cnt = 0;
    if (reset) {
        cnt = 0;
    } else {
        if (cnt == (int)path.size()) {
            animation_timer -> stop();
            QMessageBox::information(nullptr, "", "Congratulations!");
            game_gui -> close();
        } else {
            blocks[path[cnt].first][path[cnt].second] -> set_highlighted(true);
            refresh_ui();
            if (path[cnt].first == MAP_SIZE - 1 && path[cnt].second == MAP_SIZE - 1) {
                game_gui -> set_outlet(true);
            }
            cnt ++;
        }
    }
}

void GameInstance::refresh_ui()
{
    for (int i = 0; i < MAP_SIZE; i++) {
        for (int j = 0; j < MAP_SIZE; j++) {
            if (blocks[i][j]) {
                blocks[i][j] -> set_image(blocks[i][j] -> get_path());
            }
        }
    }
    game_gui -> set_lcd(GameWindow::USED_STEP_LCD, used_step);
}

