#include "StrangeFlag.hpp"
#include "StrangeFlag_Displayer.hpp"

#include <stdlib.h>
#include <string.h>

void StrangeFlag::direction_to_cord(const int x, const int y, const direction_t direction, int &new_x, int &new_y) {
    new_x = x;
    new_y = y;
    switch (direction) {
        case UP: {
            --new_x;
        } break;
        case DOWN: {
            ++new_x;
        } break;
        case LEFT: {
            --new_y;
        } break;
        case RIGHT: {
            ++new_y;
        } break;
    }

    if (new_x < 0
    ||  new_y < 0
    ||  new_x >= HEIGHT
    ||  new_y >= WIDTH) {
        throw 1;
    }
}

StrangeFlag::tile_t StrangeFlag::map(int x, int y) {
    if (x >= 0
    &&  y >= 0
    &&  x <  HEIGHT
    &&  y <  WIDTH) {
        return map_m[x][y];
    } else {
        return FULL;
    }
}

bool StrangeFlag::is_goal() {
    tile_t goal[HEIGHT][WIDTH] = {
        {RED, RED, RED},
        {FULL, BLANK, FULL},
        {BLANK, BLANK, FULL},
        {FULL, BLANK, FULL},
        {GREEN, GREEN, GREEN},
    };
    return !memcmp(map_m, goal, sizeof(tile_t) * HEIGHT * WIDTH);
}

StrangeFlag * StrangeFlag::copy() {
    StrangeFlag * r = new StrangeFlag;
    memcpy(r, this, sizeof(tile_t) * HEIGHT * WIDTH);
    return r;
}

void StrangeFlag::apply_operator(int x, int y, direction_t direction) {
    if (not is_operation(x, y, direction)) {
        throw 1;
    }

    int new_x, new_y;
    direction_to_cord(x, y, direction, new_x, new_y);
    tile_t swp = map_m[new_x][new_y];
    map_m[new_x][new_y] = map_m[x][y];
    map_m[x][y] = swp;
}

bool StrangeFlag::is_operation(int x, int y, direction_t direction) {
    if (map_m[x][y] != GREEN
    &&  map_m[x][y] != RED) {
        return false;
    }

    int new_x, new_y;
    try {
        direction_to_cord(x, y, direction, new_x, new_y);
    } catch (...) {
        return false;
    }

    return map_m[new_x][new_y] == BLANK;
}

size_t StrangeFlag::solve(size_t max, StrangeFlag_Displayer * displayer) {
    displayer->display_state(this);
    size_t steps = 1;
    for (size_t tries = 0; !is_goal() && steps <= max; tries++) {
        int x = rand() % HEIGHT;
        int y = rand() % WIDTH;
        int d = rand() % DIRECTION_END;
        try {
            apply_operator(x, y, (direction_t)d);
        } catch (...) {
            displayer->display_fail(steps, tries);
            continue;
        }
        ++steps;
        displayer->display_state(this);
    }

    bool is_success = is_goal();
    displayer->display_result(is_success, steps);
    return (is_success ? steps : 0);
}