#include "Ship/Ship.h"

#include <Core/PrintVisitor.h>
#include <Core/Time.h>

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

Ship::Ship(Time* time, std::unique_ptr<DifficultLvlStrategy<Ship, bool>> strategy, const std::string& name, int capacity, int crew, std::unique_ptr<PrintVisitor>&& visitor)
    : _time(time), _strategy(std::move(strategy)), _name(name), _capacity(capacity), _crew(crew), _visitor(std::move(visitor)) {
    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() {
    if (!_strategy->handle(*this)) {
        rebel();
    }
}

void Ship::printCargo() const {
    _visitor->visit(*this);
}

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 std::string& cargoName, int amount, StatusCode& code) noexcept {
    if (!unloadCargo(cargoName, amount)) {
        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 std::string& cargoName, int amount) {
    if (!unloadCargo(cargoName, amount)) {
        throw std::runtime_error("Missing cargo");
    }
}

void Ship::takeDamage(int damage) {
    _armor -= damage;
    if (_armor >= 0) {
        return;
    }

    _durability += _armor;
    _armor = 0;
    if (_durability <= 0) {
        std::cout << "Ship: " << _name << " was destroyed!\n";
    }
}

const std::vector<std::unique_ptr<Cargo>>& Ship::cargoes() const {
    return _cargoes;
}

bool Ship::unloadCargo(const std::string& cargoName, int amount) {
    while (amount != 0) {
        auto it = std::ranges::find_if(_cargoes, [&cargoName](const auto& cargo) { return cargo->name() == cargoName; });
        if (it != _cargoes.end()) {
            if ((*it)->amount > amount) {
                (*it)->amount -= amount;
                return true;
            }
            amount -= (*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!");
    }
}