#include "board.h"
#include <cstdlib>
#include <iostream>

Board::Board() :
    selection_index(0),
    solved_tiles(0),
    on_selection(false),
    is_cursor_visible(true)
{
    std::size_t rect_size = 5;
    rect_selection = sf::VertexArray(sf::LinesStrip, rect_size);

    for (std::size_t i = 0; i < rect_size; ++i)
        rect_selection[i].color = sf::Color::Red;

    rect_filling.setFillColor(sf::Color(255, 0, 0, 128));
}

Board::~Board()
{
    for (Cell *cell : vec_field)
    {
        delete cell->sprite;
        delete cell;
    }
}

bool Board::init(const std::string& path, int splitting, const sf::RenderWindow &window)
{
    std::cout << path << '\n';
    if (!global_texture.loadFromFile(path) )
        return false;

    calculateBoardProperties(splitting);
    splitImageIntoTiles(Cell::side_length);
    scaleImageToWindow(window);
    shuffleTiles();
    setSelectionVertex(selection_index);

    return true;
}

void Board::calculateBoardProperties(int splitting)
{
    const int width  = global_texture.getSize().x;
    const int height = global_texture.getSize().y;

    Cell::side_length = (width < height) ? width / splitting : height / splitting;
    cells_on_height = height / Cell::side_length;
    cells_on_width  = width  / Cell::side_length;
}

void Board::splitImageIntoTiles(int tile_length)
{
    const int width  = global_texture.getSize().x;
    const int height = global_texture.getSize().y;

    Cells::size_type index = 0;
    for (int x = 0; x < height; x += tile_length)
    {
        if ((height - x) >= tile_length)
        {
            for (int y = 0; y < width; y += tile_length)
            {
                if ((width - y) >= tile_length)
                {
                    sf::Sprite* sp = new sf::Sprite(global_texture, sf::IntRect(y, x, tile_length, tile_length));
                    sp->setPosition(static_cast<float>(y), static_cast<float>(x));

                    vec_field.push_back(new Cell({index, index, sp}));
                    ++index;
                }
            }
        }
    }
}

void Board::scaleImageToWindow(const sf::RenderWindow &window)
{
    float scaling = calculateScalingToWindow(window);
    scaleTiles(scaling);
}

float Board::calculateScalingToWindow(const sf::RenderWindow &window) const
{
    int texture_width  = global_texture.getSize().x;
    int texture_height = global_texture.getSize().y;

    float scaling = 0.;
    if (texture_width >= texture_height && texture_width > static_cast<int>(window.getSize().x))
        scaling = static_cast<float>(window.getSize().x) / static_cast<float>(texture_width);
    if (texture_height >= texture_width && texture_height > static_cast<int>(window.getSize().y))
        scaling = static_cast<float>(window.getSize().y) / static_cast<float>(texture_height);

    return scaling;
}

void Board::scaleTiles(float scaling)
{
    if (scaling == 0.)
        return;

    int old_side_length = Cell::side_length;
    Cell::side_length = static_cast<int>(static_cast<float>(Cell::side_length) * scaling);
    int shift = Cell::side_length - old_side_length;

    for (Cells::size_type i = 0; i < vec_field.size(); ++i)
    {
        vec_field[i]->sprite->scale(scaling, scaling);

        const auto shift_vector = calculateTileShiftVector(i, shift);
        vec_field[i]->sprite->move(shift_vector.first, shift_vector.second);
    }
}

std::pair<float, float> Board::calculateTileShiftVector(Cells::size_type tile_index, int shift) const
{
    float move_x = 0.f, move_y = 0.f;

    // The first column isn't allowed to move by x
    if (!(((tile_index % cells_on_width == 0) && (tile_index >= cells_on_width))))
        move_x = static_cast<float>(shift) * static_cast<float>((tile_index < cells_on_width) ? tile_index : tile_index % cells_on_width);

    // The first row isn't allowed to move by y
    if (tile_index >= cells_on_width)
        move_y = static_cast<float>(shift) * static_cast<float>(tile_index / cells_on_width);

    return {move_x, move_y};
}

void Board::shuffleTiles()
{
    solved_tiles = vec_field.size(); // all tiles are solved for now

    srand(static_cast<unsigned int>(time(nullptr)));
    for (Cells::size_type curr_i = 0; curr_i < vec_field.size(); ++curr_i)
    {
        Cells::size_type swap_i;
        do
        {   // find two different tiles
           swap_i = rand() & (vec_field.size() - 1);
        } while (curr_i == swap_i);

        swapCells(curr_i, swap_i);
    }
}

void Board::draw(sf::RenderWindow& window)
{
    for (const Cell *cell : vec_field)
        window.draw(*cell->sprite);

    if (on_selection)
        window.draw(rect_filling);

    if (is_cursor_visible)
        window.draw(rect_selection);
}

bool Board::tryProcessDirection(const DIRECTION &direction)
{
    if (on_selection)
        return swapOnSelection(direction);

    return moveSelection(direction);
}

bool Board::moveSelection(const DIRECTION &direction)
{
    switch (direction) {
    case DIRECTION::UP:
        if (selection_index < cells_on_width) // if upper row
            return false;
        selection_index -= cells_on_width;
        setSelectionVertex(selection_index);
        break;

    case DIRECTION::DOWN:
        if (selection_index >= (cells_on_width * (cells_on_height - 1))) // if bottom row
            return false;
        selection_index += cells_on_width;
        setSelectionVertex(selection_index);
        break;

    case DIRECTION::RIGHT:
        ++selection_index;
        if (selection_index == vec_field.size()) // if the last cell of right bottom corner
            selection_index = 0; // move to the first cell of upper left corner
        setSelectionVertex(selection_index);
        break;

    case DIRECTION::LEFT:
        if (selection_index == 0) // if the first cell of of upper left corner
            selection_index = vec_field.size() - 1; // move to the last cell of right bottom corner
        else
            --selection_index;
        setSelectionVertex(selection_index);
        break;

    default:
        return false;
        break;
    }

    return true;
}

bool Board::swapOnSelection(const DIRECTION &direction)
{
    switch (direction) {
    case DIRECTION::UP:
        if (selection_index < cells_on_width) // if upper row
            return false;
        swapCells(selection_index, selection_index - cells_on_width);
        selection_index -= cells_on_width;
        setSelectionVertex(selection_index);
        break;

    case DIRECTION::DOWN:
        if (selection_index > (cells_on_width * (cells_on_height - 1))) // if bottom row
            return false;
        swapCells(selection_index, selection_index + cells_on_width);
        selection_index += cells_on_width;
        setSelectionVertex(selection_index);
        break;

    case DIRECTION::RIGHT:
        if ((selection_index + 1) % cells_on_width == 0)
            return false;
        swapCells(selection_index, selection_index + 1);
        ++selection_index;
        setSelectionVertex(selection_index);
        break;

    case DIRECTION::LEFT:
        if (((selection_index % cells_on_width == 0) && (selection_index > cells_on_width)) || selection_index == 0)
            return false;
        swapCells(selection_index, selection_index - 1);
        --selection_index;
        setSelectionVertex(selection_index);
        break;

    default:
        return false;
        break;
    }

    on_selection = false;
    return true;
}

void Board::onSelectionMode()
{
    on_selection = !on_selection;

    if (on_selection)
    {
        rect_filling.setPosition(rect_selection[0].position);
        rect_filling.setSize(sf::Vector2f(static_cast<float>(rect_selection[1].position.x - rect_selection[0].position.x),
                                          static_cast<float>(rect_selection[2].position.y - rect_selection[1].position.y)));
    }
}

void Board::setSelectionVertex(Cells::size_type index)
{
    const auto& cell = vec_field[index];
    const auto& pos = cell->sprite->getPosition();
    const auto& x = pos.x;
    const auto& y = pos.y;
    const float length = static_cast<float>(Cell::side_length);

    rect_selection[0].position = pos;
    rect_selection[1].position = sf::Vector2f(x + length, y);
    rect_selection[2].position = sf::Vector2f(x + length, y + length);
    rect_selection[3].position = sf::Vector2f(x, y + length);
    rect_selection[4].position = pos;
}

void Board::swapCells(Cells::size_type curr_index, Cells::size_type swap_index)
{
    // Check if the pair of cells for swapping was initially solved
    bool curr_solved = (vec_field[curr_index]->inital_index == vec_field[curr_index]->current_index);
    bool swap_solved = (vec_field[swap_index]->inital_index == vec_field[swap_index]->current_index);

    Cell *curr_cell = vec_field[curr_index];
    Cell *swap_cell = vec_field[swap_index];
    const sf::Vector2f temp_pos = curr_cell->sprite->getPosition();
    const Cells::size_type temp_cell_index = curr_cell->current_index;

    curr_cell->sprite->setPosition(swap_cell->sprite->getPosition());
    curr_cell->current_index = swap_cell->current_index;

    swap_cell->sprite->setPosition(temp_pos);
    swap_cell->current_index = temp_cell_index;

    Cell *temp = vec_field[curr_index];
    vec_field[curr_index] = vec_field[swap_index];
    vec_field[swap_index] = temp;

    if ((vec_field[curr_index]->inital_index == vec_field[curr_index]->current_index) && !curr_solved)
        // Wasn't solved and NOW is solved
        ++solved_tiles;

    if ((vec_field[curr_index]->inital_index != vec_field[curr_index]->current_index) && curr_solved)
        // Was solved and NOW is unsolved
        --solved_tiles;

    if ((vec_field[swap_index]->inital_index == vec_field[swap_index]->current_index) && !swap_solved)
        // Wasn't solved and NOW is solved
        ++solved_tiles;

    if ((vec_field[swap_index]->inital_index != vec_field[swap_index]->current_index) && swap_solved)
        // Was solved and NOW is unsolved
        --solved_tiles;

}

bool Board::isWinCondition() const
{
    return (solved_tiles == vec_field.size());
}

void Board::setCursorVisibility(bool visible)
{
    is_cursor_visible = visible;
}

int Board::Cell::side_length = 1;