#include <algorithm>
#include <cassert>
#include <functional>
#include <iostream>
#include <memory>
#include <set>
#include <vector>

struct TimeObserver {
    virtual ~TimeObserver() = default;
    virtual void nextDay() = 0;
};

class Time {
public:
    enum class State {
        Closing,
        FocusChanged
    };

    void attach(TimeObserver* observer);
    void detach(TimeObserver* observer);
    Time& operator++();
    size_t day() const { return _day; }

private:
    void notify() const;

    size_t _day{0};
    std::set<TimeObserver*> _observers;
};

void Time::attach(TimeObserver* observer) {
    _observers.emplace(observer);
}

void Time::detach(TimeObserver* observer) {
    _observers.erase(observer);
}

Time& Time::operator++() {
    ++_day;
    notify();
    return *this;
}

void Time::notify() const {
    std::ranges::for_each(_observers, std::mem_fn(&TimeObserver::nextDay));
}

struct Cargo {
    size_t amount;

    auto operator<=>(const Cargo&) const = default;

protected:
    // Only derived class can create it
    Cargo(size_t amount)
        : amount(amount) {}
    Cargo() = default;
};

template <typename Neasted>
class Vulnerable : public TimeObserver {
public:
    Vulnerable(Neasted neasted, Time* time, int durability, int maxDurability)
        : _neasted(neasted), _time(time), _durability(durability), _maxDurability(maxDurability) {
        assert(_time);
        time->attach(this);
    }

    // Rule of 5
    ~Vulnerable() {
        _time->detach(this);
    }

    void nextDay() override {
        _durability = std::max(0, _durability - 1);
    }

    size_t getPrice() const {
        return (_durability * _neasted.getPrice()) / _maxDurability;
    }

    const std::string& name() const {
        return _neasted.name();
    }

private:
    Neasted _neasted;
    Time* _time;
    int _durability;
    int _maxDurability;
};

template <typename Neasted, typename ValueType>
class Valuable {
public:
    Valuable(Neasted neasted, ValueType value)
        : _neasted(neasted), _value(value) {
    }

    size_t getPrice() const {
        return _neasted.getPrice() * static_cast<int>(_value);
    }

    const std::string& name() const {
        return _neasted.name();
    }

private:
    Neasted _neasted;
    ValueType _value;
};

struct Fruit : public Cargo {
    static constexpr int BASE_PRICE = 10;

    Fruit(size_t amount)
        : Cargo(amount) {}

    size_t getPrice() const {
        return BASE_PRICE;
    }

    const std::string& name() const {
        static const std::string name = "Banana";
        return name;
    }
};

enum class ItemType { Common = 1,
                      Rare = 3,
                      Epic = 10,
                      Legendary = 25 };

struct Item : public Cargo {
    static constexpr int BASE_PRICE = 100;

    Item(size_t amount)
        : Cargo(amount) {}

    size_t getPrice() const {
        return BASE_PRICE;
    }

    const std::string& name() const {
        static const std::string name = "Item";
        return name;
    }
};

enum class AlcoholType { White = 1,
                         Spiced = 2,
                         Dark = 3,
                         Seasoned = 5 };

struct Alcohol : public Cargo {
    static constexpr int MAX_POWER = 96;
    static constexpr int BASE_PRICE = 100;
    int power;

    Alcohol(size_t amount, int power)
        : Cargo(amount), power(power) {}

    size_t getPrice() const {
        return (power * BASE_PRICE) / MAX_POWER;
    }

    const std::string& name() const {
        static const std::string name = "Rum";
        return name;
    }
};

int main() {
    Time time;
    auto cargo = Vulnerable(Valuable(Item(10), ItemType::Epic), &time, 100, 100);
    auto cargo2 = Valuable(Item(10), ItemType::Epic);

    for (int i = 1; i <= 15; ++i) {
        std::cout << "DAY: " << i << " | Name: " << cargo.name() << " | Price: " << cargo.getPrice();
        std::cout << " |-| Name: " << cargo2.name() << " | Price: " << cargo2.getPrice() << '\n';
        ++time;
    }
}