#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;

    Cargo(size_t amount)
        : amount(amount) {}
    virtual ~Cargo() = default;
    Cargo(const Cargo&) = default;
    Cargo(Cargo&&) = default;
    Cargo& operator=(const Cargo&) = default;
    Cargo& operator=(Cargo&&) = default;

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

    virtual size_t getPrice() const = 0;
    virtual const std::string& name() const = 0;
};

struct Fruit : public Cargo, public TimeObserver {
    static constexpr int MAX_TIME = 20;
    static constexpr int BASE_PRICE = 10;
    int rottenCounter;
    Time* time;

    Fruit(Time* time, size_t amount, int rottenCounter)
        : Cargo(amount), time(time), rottenCounter(rottenCounter) {
        time->attach(this);
    }

    // Rule of 5
    ~Fruit() {
        time->detach(this);
    }

    size_t getPrice() const override {
        return (rottenCounter * BASE_PRICE) / MAX_TIME;
    }

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

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

struct Item : public Cargo, public TimeObserver {
    enum class Type { Common = 100,
                      Rare = 300,
                      Epic = 1000,
                      Legendary = 2500 };
    static constexpr int MAX_DURABILITY = 100;
    Time* time;
    Type type;
    int durability{MAX_DURABILITY};

    Item(Time* time, size_t amount, Type type)
        : Cargo(amount), time(time), type(type) {
        time->attach(this);
    }

    // Rule of 5
    ~Item() {
        time->detach(this);
    }

    size_t getPrice() const override {
        return (durability * static_cast<int>(type)) / MAX_DURABILITY;
    }

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

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

struct Alcohol : public Cargo {
    enum class Type { White = 100,
                      Spiced = 200,
                      Dark = 300,
                      Seasoned = 500 };
    static constexpr int MAX_POWER = 96;
    int power;
    Type type;

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

    size_t getPrice() const override {
        return (power * static_cast<int>(type)) / MAX_POWER;
    }

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

int main() {
    Time time;
    std::unique_ptr<Cargo> cargo = std::make_unique<Item>(&time, 10, Item::Type::Epic);

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