#include "world.hpp"


const tile_t tile_defaults[] = {
    { tile_t::TILE_NONE,   0, false, false, false, tile_t::TILE_MODEL_FLAT,     tile_t::TILE_ACTION_NONE, {0} },
    { tile_t::TILE_FLOOR,  1, false, false, false, tile_t::TILE_MODEL_FLAT,     tile_t::TILE_ACTION_NONE, {0} },
    { tile_t::TILE_WATER,  2, true,  false, false, tile_t::TILE_MODEL_FLAT,     tile_t::TILE_ACTION_NONE, {0} },
    { tile_t::TILE_BOX,    1, true,  false, true,  tile_t::TILE_MODEL_CUBE_LOW, tile_t::TILE_ACTION_NONE, {0} },
    { tile_t::TILE_ROCK,   3, true,  false, false, tile_t::TILE_MODEL_SPIKE,    tile_t::TILE_ACTION_NONE, {0} },
    { tile_t::TILE_DOOR,   1, false, false, false, tile_t::TILE_MODEL_FLAT,     tile_t::TILE_ACTION_NONE, {0} },
    { tile_t::TILE_GOAL,   1, false, false, false, tile_t::TILE_MODEL_FLAT,     tile_t::TILE_ACTION_NONE, {0} },
    { tile_t::TILE_PLAYER, 0, true,  false, false, tile_t::TILE_MODEL_CUBE_LOW, tile_t::TILE_ACTION_NONE, {0} }
};


unsigned Layout::id_cnt = 0;


Layout::Layout(unsigned ww, unsigned hh): border_buf((unsigned*)calloc(ww*hh, sizeof(unsigned))), buf((tile_t*)calloc(ww*hh,sizeof(tile_t))), bottom_buf((tile_t*)calloc(ww*hh,sizeof(tile_t))), w(ww), h(hh), id(++id_cnt) {
    assert(w>0 && h>0);
    for (unsigned i=0; i<w*h; ++i) { // XXX: needed?
        bottom_buf[i] = tile_defaults[tile_t::TILE_NONE];
    }
}


Layout::~Layout() {
    free(border_buf);
    free(buf);
    free(bottom_buf);
}


void Layout::find_borders() {
    for (unsigned y=0; y<h; ++y) {
        for (unsigned x=0; x<w; ++x) {
            const unsigned t = at(x, y).tile_border_type;
            if (t != 1) continue; // only for "floors"
            unsigned rv = 0;
            if (x == w-1 || y == h-1 || at(x+1, y).tile_border_type != t || at(x, y+1).tile_border_type != t || at(x+1, y+1).tile_border_type != t) rv |= 1u; // tr
            if (x == w-1 || y == 0   || at(x+1, y).tile_border_type != t || at(x, y-1).tile_border_type != t || at(x+1, y-1).tile_border_type != t) rv |= 2u; // br
            if (x == 0   || y == 0   || at(x-1, y).tile_border_type != t || at(x, y-1).tile_border_type != t || at(x-1, y-1).tile_border_type != t) rv |= 4u; // bl
            if (x == 0   || y == h-1 || at(x-1, y).tile_border_type != t || at(x, y+1).tile_border_type != t || at(x-1, y+1).tile_border_type != t) rv |= 8u; // tl
            border_buf[(y*w)+x] = rv;
        }
    }
}


World::World(Layout* l): world(l), w(l->w), h(l->h), x(player_x), y(player_y) {
    for (unsigned x=0; x<w; ++x) {
        for (unsigned y=0; y<h; ++y) {
            if (world->at(x, y).start) {
                player_x = x;
                player_y = y;
                return;
            }
        }
    }
    assert(false);
}


World::~World() {
    delete world;
}


const Layout* World::world_get() const {
    return world;
}


bool World::player_step(int xdir, int ydir) {
    if (!xdir && !ydir) return false;
    assert(!xdir != !ydir);
    assert(xdir >= -1 && xdir <= 1);
    assert(ydir >= -1 && ydir <= 1);
    int new_x = (int)player_x + xdir;
    int new_y = (int)player_y + ydir;
    if (new_x < 0 || new_y < 0) return false;
    return player_goto((unsigned)new_x, (unsigned)new_y);
}


bool World::player_push(int xdir, int ydir) {
    if (!xdir && !ydir) return false;
    assert(!xdir != !ydir);
    assert(xdir >= -1 && xdir <= 1);
    assert(ydir >= -1 && ydir <= 1);

    int new_x = (int)player_x + xdir;
    int new_y = (int)player_y + ydir;
    if (new_x < 1 || new_y < 1 || new_x >= (int)w-1 || new_y >= (int)h-1) return false;
    if (!world->at(new_x, new_y).movable) return false;

    int new_move_x = new_x + xdir;
    int new_move_y = new_y + ydir;
    if (world->at(new_move_x, new_move_y).clip) return false;
    assert(world->at(new_move_x, new_move_y).tile != tile_t::TILE_NONE);
    if (world->below(new_move_x, new_move_y).tile != tile_t::TILE_NONE) return false;

    world->below(new_move_x, new_move_y) = world->at(new_move_x, new_move_y); // backup
    world->at(new_move_x, new_move_y) = world->at(new_x, new_y); // move
    if (world->below(new_x, new_y).tile != tile_t::TILE_NONE) { // restore
        assert(!world->below(new_x, new_y).clip);
        world->at(new_x, new_y) = world->below(new_x, new_y);
        world->below(new_x, new_y) = tile_defaults[tile_t::TILE_NONE];
    } else { // default
        world->at(new_x, new_y) = tile_defaults[tile_t::TILE_FLOOR];
    }

    player_x = new_x;
    player_y = new_y;
    return true;
}


bool World::player_goto(unsigned x, unsigned y) {
    if (x >= w || y >= h) return false;
    if (x == player_x && y == player_y) return false;
    if (world->at(x, y).clip) return false;
    player_x = x;
    player_y = y;
    return true;
}


const tile_t& World::at(unsigned x, unsigned y, unsigned& len, unsigned& borders) const {
    len = 1;
    borders = world->borders(x, y);
    const tile_t& rv = world->at(x, y);
    while (x<w-1 && world->at(++x, y).tile == rv.tile && world->at(x, y).model == rv.model && world->borders(x, y) == borders) {
        len++;
    }
    return rv;
}


Worlds::Worlds(): world(NULL) {
}


Worlds::Worlds(unsigned i, World* w): world(w) {
    push(i, w);
}


void Worlds::push(unsigned id, World* w) {
    assert(id != 0);
    assert(get(id) == NULL);
    worlds.insert(std::pair<unsigned, World*>(id, w));
}


World* Worlds::get(unsigned id) {
    std::map<unsigned, World*>::iterator it = worlds.find(id);
    return (it == worlds.end())? NULL: it->second;
}


bool Worlds::set(unsigned id) {
    World* w = get(id);
    if (w) {
        world = w;
        return true;
    } else {
        return false;
    }
}


bool Worlds::all_done() const {
    for (std::map<unsigned, World*>::const_iterator it = worlds.begin(); it != worlds.end(); it++) {
        if (!it->second->world_get()->is_done()) return false;
    }
    return true;
}


Worlds::~Worlds() {
    while (!worlds.empty()) {
        delete worlds.begin()->second;
        worlds.erase(worlds.begin());
    }
}