#include "level.h"

#include <fstream>
#include <algorithm>
#include <iostream>

template<typename D, typename B>  // [D]erived - [B]ase
std::unique_ptr<D> static_unique_pointer_cast(std::unique_ptr<B>&& old)
{
    return std::unique_ptr<D>{static_cast<D *>(old.release())};
}

void Level::Map::init(const std::string &path)
{
    prepareDefaultCells();

    std::ifstream file;
    file.open(path);

    std::string cur_line;
    std::istringstream sstr;
    SECTION cur_section = SECTION::NONE;
    while (getline(file, cur_line))
    {
        if (map_section.find(cur_line) != map_section.end())
        {
            cur_section = map_section[cur_line];
            continue;
        }

        sstr.clear();
        sstr.str(cur_line);
        switch (cur_section)
        {
        case SECTION::SIZE:
            readMapSize(sstr);
            break;

        case SECTION::MAP:
            readMapRow(sstr);
            break;

        case SECTION::TELEPORT:
        case SECTION::CHARGE:
        case SECTION::TRIGGER:
            readCellSection(sstr, cur_section);
            break;

        default:
            break;
        }
    }

    file.close();
}

void Level::Map::prepareDefaultCells()
{
    default_cells[PASSABLE_CELL] = std::make_unique<PassableCell>();
    default_cells[WATER_CELL] = std::make_unique<WaterCell>();
    default_cells[WALL_CELL] = std::make_unique<WallCell>();
    default_cells[CHARGE_CELL] = std::make_unique<ChargeCell>();
    default_cells[EXIT_CELL] = std::make_unique<ExitCell>();
    default_cells[TELEPORT_CELL] = std::make_unique<TeleportCell>();
    default_cells[TRIGGER_CELL] = std::make_unique<TriggerCell>();
}

void Level::Map::readMapSize(std::istringstream &sstr)
{
    sstr >> rows >> cols;
    data.reserve(rows * cols);
}

void Level::Map::readMapRow(std::istringstream &sstr)
{
    data.emplace_back(Row());
    int cell_type;
    while (sstr >> cell_type)
    {
        data.back().emplace_back(default_cells[cell_type]->clone());
        data.back().back()->setPosition(data.size()-1, data.back().size()-1);
    }
}

void Level::Map::readCellSection(std::istringstream &sstr, const SECTION &section)
{
    coordinate src_row, src_col;
    sstr >> src_row >> src_col;

    switch (section) {
    case SECTION::TELEPORT:
        data[src_row][src_col] = readTeleport(sstr, static_unique_pointer_cast<TeleportCell>(std::move(data[src_row][src_col])));
        break;

    case SECTION::CHARGE:
        data[src_row][src_col] = readCharge(sstr, static_unique_pointer_cast<ChargeCell>(std::move(data[src_row][src_col])));
        break;

    case SECTION::TRIGGER:
        data[src_row][src_col] = readTrigger(sstr, static_unique_pointer_cast<TriggerCell>(std::move(data[src_row][src_col])));
        break;

    default:
        break;
    }
}

std::unique_ptr<TeleportCell> &&Level::Map::readTeleport(std::istringstream &sstr, std::unique_ptr<TeleportCell> &&cell)
{
    coordinate dest_row, dest_col;

    sstr >> dest_row >> dest_col;
    cell->setDestination(dest_row, dest_col);
    return std::move(cell);
}

std::unique_ptr<ChargeCell> &&Level::Map::readCharge(std::istringstream &sstr, std::unique_ptr<ChargeCell> &&cell)
{
    int num_charges;

    sstr >> num_charges;
    cell->setCharges(num_charges);
    return std::move(cell);
}

std::unique_ptr<TriggerCell> &&Level::Map::readTrigger(std::istringstream &sstr, std::unique_ptr<TriggerCell> &&cell)
{
    coordinate dest_row, dest_col;
    int cell_type;

    sstr >> dest_row >> dest_col >> cell_type;

    auto dest_cell = default_cells[cell_type]->clone();
    dest_cell->setPosition(dest_row, dest_col);

    switch (cell_type) {
    case TELEPORT_CELL:
        dest_cell = readTeleport(sstr, static_unique_pointer_cast<TeleportCell>(std::move(dest_cell)));
        break;

    case CHARGE_CELL:
        dest_cell = readCharge(sstr, static_unique_pointer_cast<ChargeCell>(std::move(dest_cell)));
        break;

    case TRIGGER_CELL:
        dest_cell = readTrigger(sstr, static_unique_pointer_cast<TriggerCell>(std::move(dest_cell)));
        break;

    default:
        break;
    }

    cell->addTarget(std::move(dest_cell));
    return std::move(cell);
}

Level::Level(const std::string &path)
{
    map.init(path);
}

size_t Level::rows() const
{
    return map.rows;
}

size_t Level::cols() const
{
    return map.cols;
}

CellPtr &Level::getCellAt(coordinate row, coordinate col)
{
    return map.data[row][col];
}

void Level::placeBridge(coordinate row, coordinate col)
{
    map.data[row][col] = std::make_unique<PassableCell>(row, col, palette::Black);
}

void Level::removeCharge(coordinate row, coordinate col)
{
    map.data[row][col] = std::make_unique<PassableCell>(row, col, color_ground);
}

sf::Color Level::defaultGroundColor()
{
    return color_ground;
}

void Level::setDefaultGroundColor(const sf::Color &color)
{
    color_ground = color;
}
