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

class Observer;

class IObserverNotifier
{
public:
    virtual ~IObserverNotifier() = default;
    virtual void UnRegister(Observer&) = 0;
};

class Observer
{
public:
    explicit Observer() = default;
    virtual ~Observer() {
        for (auto* abstractObserverNotifier : mAbstractObserverNotifiers)
            abstractObserverNotifier->UnRegister(*this);   
    }

    Observer(const Observer&) = delete;
    Observer(Observer&&) = delete;
    Observer& operator=(const Observer&) = delete;
    Observer& operator=(Observer&&) = delete;

    void AddObserverNotifier(IObserverNotifier& observerNotifier)
    {
        mAbstractObserverNotifiers.insert(&observerNotifier);
    }

    void RemoveObserverNotifier(IObserverNotifier& observerNotifier)
    {
        mAbstractObserverNotifiers.erase(&observerNotifier);
    }

private:
    std::set<IObserverNotifier*> mAbstractObserverNotifiers;
};

template<typename ... Params>
class ObserverNotifier : private IObserverNotifier
{
public:
    ObserverNotifier() = default;
    ~ObserverNotifier() {
        for (const auto& p : mObserverCallbacks) {
            p.first->RemoveObserverNotifier(*this);
        }
    }

    ObserverNotifier(const ObserverNotifier&) = delete;
    ObserverNotifier(ObserverNotifier&&) = delete;

    ObserverNotifier& operator=(const ObserverNotifier&) = delete;
    ObserverNotifier& operator=(ObserverNotifier&&) = delete;

    void Register(Observer& observer, std::function<void(Params...)> f) {
        mObserverCallbacks.emplace_back(&observer, f);
        observer.AddObserverNotifier(*this);
    }

    void NotifyObservers(Params... args) const
    {
        for (const auto& p : mObserverCallbacks)
        {
            const auto& callback = p.second;

            callback(args...);
        }
    }

private:
    void UnRegister(Observer& observer) override
    {
        mObserverCallbacks.erase(std::remove_if(mObserverCallbacks.begin(),
                                                mObserverCallbacks.end(),
                                                [&](const auto& p) { return p.first == &observer;}),
                                 mObserverCallbacks.end());
    }

private:
    std::vector<std::pair<Observer*, std::function<void(Params...)>>> mObserverCallbacks;
};


class Sensor
{
public:
    void ChangeTime() {
        ++mTime;
        mOnTimeChange.NotifyObservers(mTime);
    }

    void ChangeTemperature(double delta) {
        mTemperature += delta;
        mOnTemperatureChange.NotifyObservers(mTemperature);
    }

    void RegisterTimeChange(Observer& observer, std::function<void(double)> f) { mOnTimeChange.Register(observer, f); }
    void RegisterTemperatureChange(Observer& observer, std::function<void(double)> f) { mOnTemperatureChange.Register(observer, f); }

private:
    ObserverNotifier<int> mOnTimeChange;
    ObserverNotifier<double> mOnTemperatureChange;
    int mTime = 0;
    double mTemperature = 0;
};

class Ice : public Observer {
public:
        
    void OnTimeChanged(int time) {
        mVolume -= mLose;
        mOnVolumeChange.NotifyObservers(mVolume);
    }

    void OnTemperatureChanged(double t) {
        if (t <= 0) {
            mLose = 0;
        } else if (t < 15) {
            mLose = 5;
        } else {
            mLose = 21;
        }
    }

    void RegisterVolumeChange(Observer& observer, std::function<void(double)> f) { mOnVolumeChange.Register(observer, f); }


private:
    ObserverNotifier<double> mOnVolumeChange;
    double mVolume = 42;
    double mLose = 0;
};

class MyObserver : public Observer {
public:
    static void OnTimeChange(int t) {
        std::cout << "observer says time is " << t << std::endl;
    }

    static void OnTemperatureChange(double temperature) {
        std::cout << "observer says temperature is " << temperature << std::endl;
    }

    static void OnIceChange(double volume) {
        std::cout << "observer says Ice volume is " << volume << std::endl;
    }

};

int main()
{
    Sensor sensor;
    Ice ice;
    MyObserver observer;

    sensor.RegisterTimeChange(observer, &MyObserver::OnTimeChange);
    sensor.RegisterTemperatureChange(observer, &MyObserver::OnTemperatureChange);
    ice.RegisterVolumeChange(observer, &MyObserver::OnIceChange);
    sensor.RegisterTimeChange(ice, [&](int t){ice.OnTimeChanged(t);});
    sensor.RegisterTemperatureChange(ice, [&](double t){ice.OnTemperatureChanged(t);});

    sensor.ChangeTemperature(0);
    sensor.ChangeTime();
    sensor.ChangeTemperature(10.3);
    sensor.ChangeTime();
    sensor.ChangeTime();
    sensor.ChangeTemperature(42.1);
    sensor.ChangeTime();
}
