#include "Ship/Ship.h"

#include <Core/Time.h>

#include <algorithm>
#include <cassert>
#include <exception>
#include <iomanip>
#include <iostream>

Ship::Ship(Time* time, std::unique_ptr<ShipStrategy<Ship, bool>> strategy, const std::string& name, int capacity, int crew)
    : _time(time), _strategy(std::move(strategy)), _name(name), _capacity(capacity), _crew(crew) {
    if (_capacity < 0) {
        throw std::runtime_error("Capacity can't be negative value!");
    }
    assert(time);
    _time->attach(this);
}

Ship::~Ship() {
    _time->detach(this);
}

void Ship::nextDay() {
    bool shouldRebel = !consume("Rum");
    shouldRebel |= !consume("Banana");
    if (shouldRebel) {
        rebel();
    }
}

void Ship::printCargo() const {
    std::cout << "|----------------|----------------|\n";
    std::cout << "|      NAME      "
              << "|     AMOUNT     |" << '\n';
    std::cout << "|----------------|----------------|\n";
    for (const auto& cargo : _cargoes) {
        std::cout << "|" << std::setw(15) << std::left << cargo->name() << " | " << std::setw(15) << cargo->amount << "|\n";
    }
    std::cout << "|----------------|----------------|\n";
}

Cargo* Ship::load(std::unique_ptr<Cargo>&& cargo, StatusCode& code) noexcept {
    if (_capacity > cargo->amount) {
        _cargoes.push_back(std::move(cargo));
        code = StatusCode::LackOfSpace;
        return _cargoes.back().get();
    }

    code = StatusCode::OK;
    return nullptr;
}

void Ship::unload(const Cargo& cargo, StatusCode& code) noexcept {
    if (auto it = std::find_if(_cargoes.begin(), _cargoes.end(), [&cargo](const auto& item) { return *item == cargo; }); it != _cargoes.end()) {
        _cargoes.erase(it);
        code = StatusCode::MissingCargo;
        return;
    }

    code = StatusCode::OK;
}

Cargo* Ship::load(std::unique_ptr<Cargo>&& cargo) {
    if (_capacity > cargo->amount) {
        _cargoes.push_back(std::move(cargo));
        return _cargoes.back().get();
    }

    throw std::runtime_error("Lack of space");
}

void Ship::unload(const Cargo& cargo) {
    if (auto it = std::find_if(_cargoes.begin(), _cargoes.end(), [&cargo](const auto& item) { return *item == cargo; }); it != _cargoes.end()) {
        _cargoes.erase(it);
        throw std::runtime_error("Missing cargo");
    }
}

void Ship::takeDamage(int damage) {
    _durability -= damage;
    if (_durability <= 0) {
        std::cout << "Ship: " << _name << " was destroyed!\n";
    }
}

bool Ship::consume(const std::string& cargoName) {
    int left = _crew;

    while (left != 0) {
        auto it = std::ranges::find_if(_cargoes, [&cargoName](const auto& cargo) { return cargo->name() == cargoName; });
        if (it != _cargoes.end()) {
            if ((*it)->amount > left) {
                (*it)->amount -= left;
                return true;
            }
            left -= (*it)->amount;
            _cargoes.erase(it);
        } else {
            return false;
        }
    }

    return true;
}

void Ship::rebel() {
    std::cout << "\n********** Crew rebel **********\n";
    _crew -= 5;

    if (_crew < 5) {
        throw std::runtime_error("There is not enought crew! Game over!");
    }
}

template <typename T, typename Res>
bool ShipEasyStrategy::handle(std::string &t) {
    int left = _crew;

    while (left != 0) {
        auto it = std::ranges::find_if(_cargoes, [&cargoName](const auto& cargo) { return cargo->name() == cargoName; });
        if (it != _cargoes.end()) {
            if ((*it)->amount > left) {
                (*it)->amount -= left;
                return true;
            }
            left -= (*it)->amount;
            _cargoes.erase(it);
        } else {
            return false;
        }
    }

    return true;
}
