#include "board.h"
#include "config.h"

__u8 board_need_config = true;

rect base_cell = {0};
tile null_tile = {0};

struct {
    rect    draw;                       // background rect
    rect    cell_map[BOARD_MAP_SIZE];   // background cells array
    tile    tile_map[BOARD_MAP_SIZE];   // tiles array
    __u16   empty_index[BOARD_MAP_SIZE];// empty cells indexes
    __u16   empty_count;                // empty cells count
    __u32   score;
    __u32   highscore;
} board = {0};

// Get tile index for row and column
__u16 board_index(__u16 row, __u16 column) {
    return column + row * BOARD_COUNT;
}

// Get tile position (as point with eow and column) for index
point board_position(__u16 index) {
    point p = {
        .x = index % BOARD_COUNT,
        .y = index / BOARD_COUNT
    };
    return p;
}

// Calculate cell rect for row and column
rect position2cell(point p) {
    rect c = {0};
    c.width = base_cell.width;
    c.height = base_cell.height;
    c.x = board.draw.x + BOARD_SPACING + p.x * (c.width + BOARD_SPACING);
    c.y = board.draw.y + BOARD_SPACING + p.y * (c.height + BOARD_SPACING);
    return c;
}

// Update information about empty cells
void board_update_empty_info();

// Represent tile array as pointers array
void board_to_tempboard(tile* temp[]);

// Fill tile array with tiles from pointers array
void board_from_tempboard(tile* temp[], __u8 forward);

// Move tile inside a pointer array
void tempboard_move_tile(tile* temp[], __u16 from, __u16 to);

// Merge tiles inside a pointer array
void tempboard_merge_tile(tile* temp[], __u16 from, __u16 to);

// Random number generator
__u32 random_u32(__u32 max);

void board_init(rect* r)
{
    // seed for random number generator
    srand(__menuet__getsystemclock());

    __u16 cell_size = (r->width - BOARD_SPACING * (BOARD_COUNT + 1)) / BOARD_COUNT;
    base_cell.width = cell_size;
    base_cell.height = cell_size;

    null_tile.value = 0;
    null_tile.animate = false;
    null_tile.ani_step = ANI_APPEAR_STEP;
    null_tile.merged = false;

    board.score = 0;
    board.draw = *r;

    canvas_init(r);
    canvas_fill(BOARD_BG_COLOR);

    __u16 i = 0;
    for (i = 0; i < BOARD_MAP_SIZE; i++)
    {
        board.cell_map[i] = position2cell(board_position(i));
        board.tile_map[i] = null_tile;
    }

    __u8 loaded = false;
    __u8 empty_config = true;
    if (board_need_config)
    {
        board_need_config = false;
        config_state state = {0};
        loaded = config_load(&state);
        if(loaded)
        {
            board.score = state.score;
            board.highscore = state.highscore;

            i = 0;
            for (i = 0; i < BOARD_MAP_SIZE; i++)
            {
                if (state.value_map[i])
                {
                    empty_config = false;
                    board_add_tile(state.value_map[i],i);
                }
            }
        }
    }

    if (!loaded || empty_config)
    {
        i = 0;
        for (i = 0; i < START_COUNT; i++)
        {
            board_add_random_tile();
        }
    }

    board_redraw();
}

void board_delete()
{
    config_state state = {0};
    state.score = board.score;
    state.highscore = board.highscore;
    int i = 0;
    for (i = 0; i < BOARD_MAP_SIZE; i++)
        state.value_map[i] = board.tile_map[i].value;
    config_save(&state);

    canvas_delete();
}

void board_redraw()
{
    __u16 i = 0;
    __u8 animate = false;
    __u8 last_animate = false;
    do
    {
        canvas_fill(BOARD_BG_COLOR);

        for (i = 0; i < BOARD_MAP_SIZE; i++)
        {
            canvas_draw_rect(&board.cell_map[i],CELL_COLOR);
        }

        animate = false;
        last_animate = false;
        for (i = 0; i < BOARD_MAP_SIZE; i++)
        {
            tile* t = &board.tile_map[i];
            last_animate = tile_draw(t);
            if (last_animate)
            {
                animate = last_animate;
            }
        }

        canvas_paint();

        if (animate)
        {
            __menuet__delay100(ANI_DELAY);
        }
    }
    while (animate);
}

__u8 board_up()
{
    __u8 moved = false;

    __u16 row = 0;
    __u16 column = 0;
    __u16 ind = 0;
    __u16 preind = 0;
    tile* indtile = 0;
    tile* pretile = 0;

    tile* temp_board[BOARD_MAP_SIZE] = {0};
    board_to_tempboard(temp_board);

    for (column = 0; column < BOARD_COUNT; column++)
    {
        for (row = 0; row < BOARD_COUNT; row++)
        {
            if (row > 0)
            {
                ind = board_index(row,column);
                indtile = temp_board[ind];
                if (indtile)
                {
                    preind = board_index(row - 1,column);
                    pretile = temp_board[preind];
                    if (!pretile)
                    {
                        moved = true;
                        tempboard_move_tile(temp_board,ind,preind);
                        row = 0;
                    }
                    else if (tile_mergeable(indtile,pretile))
                    {
                        moved = true;
                        board.score += indtile->value * 2;
                        if (board.score > board.highscore)
                            board.highscore = board.score;
                        tempboard_merge_tile(temp_board,ind,preind); 
                        row = 0;
                    }
                }
            }
        }
    }

    board_from_tempboard(temp_board,true);

    return moved;
}

__u8 board_down()
{
    __u8 moved = false;

    __u16 row = 0;
    __u16 column = 0;
    __u16 ind = 0;
    __u16 preind = 0;
    tile* indtile = 0;
    tile* pretile = 0;

    tile* temp_board[BOARD_MAP_SIZE] = {0};
    board_to_tempboard(temp_board);

    for (column = 0; column < BOARD_COUNT; column++)
    {
        row = BOARD_COUNT;
        while (row--)
        {
            if ((BOARD_COUNT - row) > 1)
            {
                ind = board_index(row,column);
                indtile = temp_board[ind];
                if (indtile)
                {
                    preind = board_index(row + 1,column);
                    pretile = temp_board[preind];
                    if (!pretile)
                    {
                        moved = true;
                        tempboard_move_tile(temp_board,ind,preind);
                        row = BOARD_COUNT;
                    }
                    else if (tile_mergeable(indtile,pretile))
                    {
                        moved = true;
                        board.score += indtile->value * 2;
                        if (board.score > board.highscore)
                            board.highscore = board.score;
                        tempboard_merge_tile(temp_board,ind,preind);
                        row = BOARD_COUNT;
                    }
                }
            }
        }
    }

    board_from_tempboard(temp_board,false);

    return moved;
}

__u8 board_left()
{
    __u8 moved = false;

    __u16 row = 0;
    __u16 column = 0;
    __u16 ind = 0;
    __u16 preind = 0;
    tile* indtile = 0;
    tile* pretile = 0;

    tile* temp_board[BOARD_MAP_SIZE] = {0};
    board_to_tempboard(temp_board);

    for (row = 0; row < BOARD_COUNT; row++)
    {
        for (column = 0; column < BOARD_COUNT; column++)
        {
            if (column > 0)
            {
                ind = board_index(row,column);
                indtile = temp_board[ind];
                if (indtile)
                {
                    preind = board_index(row,column - 1);
                    pretile = temp_board[preind];
                    if (!pretile)
                    {
                        moved = true;
                        tempboard_move_tile(temp_board,ind,preind);
                        column = 0;
                    }
                    else if (tile_mergeable(indtile,pretile))
                    {
                        moved = true;
                        board.score += indtile->value * 2;
                        if (board.score > board.highscore)
                            board.highscore = board.score;
                        tempboard_merge_tile(temp_board,ind,preind);
                        column = 0;
                    }
                }
            }
        }
    }

    board_from_tempboard(temp_board,true);

    return moved;
}

__u8 board_right()
{
    __u8 moved = false;

    __u16 row = 0;
    __u16 column = 0;
    __u16 ind = 0;
    __u16 preind = 0;
    tile* indtile = 0;
    tile* pretile = 0;

    tile* temp_board[BOARD_MAP_SIZE] = {0};
    board_to_tempboard(temp_board);

    for (row = 0; row < BOARD_COUNT; row++)
    {
        column = BOARD_COUNT;
        while (column--)
        {
            if ((BOARD_COUNT - column) > 1)
            {
                ind = board_index(row,column);
                indtile = temp_board[ind];
                if (indtile)
                {
                    preind = board_index(row,column + 1);
                    pretile = temp_board[preind];
                    if (!pretile)
                    {
                        moved = true;
                        tempboard_move_tile(temp_board,ind,preind);
                        column = BOARD_COUNT;
                    }
                    else if (tile_mergeable(indtile,pretile))
                    {
                        moved = true;
                        board.score += indtile->value * 2;
                        if (board.score > board.highscore)
                            board.highscore = board.score;
                        tempboard_merge_tile(temp_board,ind,preind);
                        column = BOARD_COUNT;
                    }
                }
            }
        }
    }

    board_from_tempboard(temp_board,false);

    return moved;
}

__u8 board_add_random_tile()
{
    board_update_empty_info();
    if (board.empty_count)
    {
        __u16 rnd_av = random_u32(board.empty_count);
        rnd_av = board.empty_index[rnd_av];
        __u32 rnd_value = (random_u32(10) < 9) ? 2 : 4;

        board_add_tile(rnd_value,rnd_av);
    }
    return board.empty_count;
}

void board_add_tile(__u32 value, __u16 index)
{
    tile* av_tile = &board.tile_map[index];
    av_tile->value = value;

    av_tile->animate = true;
    av_tile->ani_step = ANI_APPEAR_STEP;
    av_tile->transition = position2cell(board_position(index));
    av_tile->cell.x = av_tile->transition.x + base_cell.width / 2;
    av_tile->cell.y = av_tile->transition.y + base_cell.height / 2;
    av_tile->cell.width = 0;
    av_tile->cell.height = 0;
}

__u8 board_has_moves()
{
    __u16 ind = 0;
    __u16 next = 0;
    __u16 step = 0;
    __u16 pos = 0;
    for (step = 0; step < BOARD_COUNT; step++)
    {
        for (pos = 0; pos < BOARD_COUNT; pos++)
        {
            // check horizontal
            ind = board_index(step,pos);
            next = board_index(step,pos + 1);

            if (!board.tile_map[ind].value ||
                    (((pos + 1) < BOARD_COUNT) &&
                     (!board.tile_map[next].value ||
                      (board.tile_map[ind].value == board.tile_map[next].value)
                      )
                     )
                    )
                return true;

            // check vertical
            ind = board_index(pos,step);
            next = board_index(pos + 1,step);

            if (!board.tile_map[ind].value ||
                    (((pos + 1) < BOARD_COUNT) &&
                     (!board.tile_map[next].value ||
                      (board.tile_map[ind].value == board.tile_map[next].value)
                      )
                     )
                    )
                return true;
        }
    }
    return false;
}

__u32 board_score()
{
    return board.score;
}

__u32 board_highscore()
{
    return board.highscore;
}

void board_update_empty_info()
{
    board.empty_count = 0;

    __u16 i = 0;
    for (i = 0; i < BOARD_MAP_SIZE; i++)
    {
        if (!board.tile_map[i].value)
        {
            board.empty_index[board.empty_count] = i;
            board.empty_count++;
        }
    }
}

void board_to_tempboard(tile* temp[])
{
    __u16 ind = 0;
    for (ind = 0; ind < BOARD_MAP_SIZE; ind++)
    {
        tile* bt = &board.tile_map[ind];
        if (bt->value)
        {
            temp[ind] = bt;
        }
    }
}

void board_from_tempboard(tile *temp[], __u8 forward)
{
    __u16 ind = 0;
    if (forward)
    {
        for (ind = 0; ind < BOARD_MAP_SIZE; ind++)
        {
            tile* bt = &board.tile_map[ind];
            tile* tt = temp[ind];
            if (tt)
            {
                *bt = *tt;
                bt->transition = position2cell(board_position(ind));
            }
            else
            {
                *bt = null_tile;
            }
        }
    }
    else
    {
        ind = BOARD_MAP_SIZE;
        while (ind--)
        {
            tile* bt = &board.tile_map[ind];
            tile* tt = temp[ind];
            if (tt)
            {
                *bt = *tt;
                bt->transition = position2cell(board_position(ind));
            }
            else
            {
                *bt = null_tile;
            }
        }
    }
}

void tempboard_move_tile(tile* temp[], __u16 from, __u16 to)
{
    temp[to] = temp[from];
    temp[to]->animate = true;
    temp[to]->ani_step = ANI_MOVE_STEP;
    temp[from] = 0;
}

void tempboard_merge_tile(tile* temp[], __u16 from, __u16 to)
{
    temp[from]->merged = true;
    temp[from]->merged_rect = temp[to]->cell;
    tempboard_move_tile(temp,from,to);
}

__u32 random_u32(__u32 max)
{
    return ((rand() * 1.0) / RAND_MAX) * max;
}