// Boost::Signals2
//  Michal Bloch 17/05/25

// Uproszczony Wstep:
//  Biblioteka Boost Signals2 udostepnia implementacje wzorca obserwatora.
//  Za pomoca obiektow funkcyjnych, ktore sa nazywane slot'ami, sygnaly wywoluja
//  funkcje, ktore je subskrybuja. https://www.boost.org/library/latest/signals2/

// Dokladniejszy Opis:
//  Poza samym faktem bycia implementacja wzorca obserwatora (w zasadzie to sygnalow), Signals2 zapewnia
//  thread-safety, automatyczne odlaczanie slot'ow, ktore przestaja istniec,
//  oraz agregacje wartosci zwrotnych za pomoca Combiner'ow. Dostepne jest rowniez "szeregowanie"
//  slot'ow, czyli okreslanie kolejnosci, w ktorej zostana one wywolane.

//  Jedyna z najwazniejszych (moim zdaniem) zalet uzywania Signals2 jest decoupling
//  naszych klas, ktore podczas implementacji nie maja bezposredniej zaleznosci z innymi klasami,
//  ktore taki sygnal moga otrzymac lub tez nadac.

// Przyklad:
//  Ponizej zaimplementowany przyklad urzadzen IoT - czujnika temperatury, ktory raz
//  na jakis czas sprawdza temperature i informuje podlaczone do niego urzadzenia o jej zmianie.
//  Przyklad prezentuje przykladowa implementacje automatycznie zarzadzanych slot'ow przy uzyciu fabryk.

// Skompilowano przy uzyciu GNU 13.3.0 CXX standard c++17 -pthreads -Wall -Werror -Wextra

#include <thread>
#include <chrono>
#include <random>
#include <iostream>

#include <boost/signals2.hpp>
#include <boost/smart_ptr.hpp>

#define DEMO_ITERATIONS 10

using Temperature = float;

class TemperatureSensor {
    // definicja typu naszego sygnalu
    //  brak typu zwracanego, przyjmowany jeden argument typu Temperature
    typedef boost::signals2::signal<void (Temperature)> signal_t;

public:
    // definicja typu slot'a
    typedef signal_t::slot_type TemperatureSlot;

public:
    TemperatureSensor() {}
    
    // podlaczanie slot'ow do naszego sygnalu
    boost::signals2::connection connect(const TemperatureSlot &subscriber)
    {
        // domyslnie slot'y sa dodawane na sam koniec listy
        return sig_.connect(subscriber);

        // istnieje rowniez mozliwosc dodawania na konkretne miejsce
        // return sig_.connect(pozycja, subscriber);

        // lub na poczatek listy slot'ow
        // return sig_.connect(subscriber, boost::signals2::at_front);

        // Zmienia to kolejnosc, w ktorej slot'y zostana wywolane.
    }

    // glowna funkcja naszego czujnika
    void operator()() {
        thread_local std::mt19937 engine{};
        std::normal_distribution<float> d{23, 5};

        for (auto x = 0; x < DEMO_ITERATIONS; ++x){
            // powiadomienie kazdego subskrybenta
            sig_(d(engine));
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }

private:
    signal_t sig_;
};


class Logger {
public:
    // statyczna fabryka, ktora tworzy obiekt i subskrybuje wzgledem czujnika temperatury
    static boost::shared_ptr<Logger> create(TemperatureSensor& sensor)
    {
        // utworzenie faktycznego obiektu, kontrolowanego przez sprytny wskaznik
        boost::shared_ptr<Logger> new_logger(new Logger{});
        {
            using namespace boost::placeholders;
            // utworzenie nowego slot'a z binding'em argumentow
            TemperatureSensor::TemperatureSlot slot(boost::bind(&Logger::log, new_logger.get(), _1));
            
            // przekazanie slot'a, ktory sledzi (tworzy weak_ptr) nasz nowy obiekt
            // UWAGA! nie dziala z smart pointerami z biblioteki standardowej
            sensor.connect(slot.track(new_logger));
        }
        return new_logger;
    }
    
    void log(Temperature t) const {
        std::cout<< "[LOG] Current temperature: "<<t<<"°C\n";
    }
};

class AlertSystem {
public:
    // statyczna fabryka, ktora tworzy obiekt i subskrybuje wzgledem czujnika temperatury
    static boost::shared_ptr<AlertSystem> create(TemperatureSensor& sensor)
    {
        // utworzenie faktycznego obiektu, kontrolowanego przez sprytny wskaznik
        boost::shared_ptr<AlertSystem> new_alertSys(new AlertSystem{});
        {
            using namespace boost::placeholders;

            // utworzenie nowego slot'a z binding'em argumentow
            TemperatureSensor::TemperatureSlot slot(boost::bind(&AlertSystem::alert, new_alertSys.get(), _1));
            
            // przekazanie slot'a, ktory sledzi (tworzy weak_ptr) nasz nowy obiekt
            // UWAGA! nie dziala z smart pointerami z biblioteki standardowej
            sensor.connect(slot.track(new_alertSys));
        }
        return new_alertSys;
    }

    void alert(Temperature t) const {
        if (t >= 25.f){
            std::cout<< "[ALERT] High temperature: "<<t<<"°C\n";
        }    
    }
};


int main(){
    // utworzenie czujnika
    TemperatureSensor sensor{};

    // utworzenie innych urzadzen za pomoca fabryk statycznych
    auto logger = Logger::create(sensor);
    auto alertSys = AlertSystem::create(sensor);

    // uruchomienie osobnego watku z czujnikiem
    std::thread t(std::ref(sensor));

    // po 3 sekundach logger powinien zostac automatycznie odlaczony,
    // poniewaz przestal istniec, przez co nie wyswietlaja sie juz jego nowe logi

    std::this_thread::sleep_for(std::chrono::seconds(3));

    // usuniecie obiektu logger'a, poprzez zresetowanie sprytnego wskaznika
    logger.reset();

    t.join();

    return 0;
}
