#ifndef CANVAS_SIGNAL_HPP
#define CANVAS_SIGNAL_HPP

#include "callable.hpp"
#include <cstdio>
#include <vector>

namespace ce {
/*! Observer-listener implementation
 */
template <class... Args>
class Signal {
    std::vector<Callable<void, Args...>> listeners{};
public:
    void connect(Callable<void, Args...> callable);
    void disconnect(Callable<void, Args...> callable);
    void invoke(Args...);
};

template <class... Args> void Signal<Args...>::connect(Callable<void, Args...> callable) {
    this->listeners.push_back(callable);
}

template <class... Args> void Signal<Args...>::disconnect(Callable<void, Args...> callable) {
    std::erase_if(this->listeners,
    [&callable](Callable<void, Args...> &listener) -> bool {
        return listener == callable;
    });
}

template <class... Args> void Signal<Args...>::invoke(Args... args) {
    for(Callable<void, Args...> &listener : this->listeners) {
        listener.call(args...);
    }
}

#ifdef DEBUG
static inline
void TEST_signals() {
    struct A {inline void f(int val) {
        std::printf("A: %d\n", val);
    }};
    struct B { inline void f(int val) {
        std::printf("B: %d\n", val);
    }};

    A a_object;
    B b_object;

    Signal<int> signal;

    Callable<void, int> a_callable{Callable<void, int>::make(&a_object, &A::f)};
    Callable<void, int> b_callable{Callable<void, int>::make(&b_object, &B::f)};

    signal.connect(a_callable);
    signal.invoke(5);
    signal.connect(b_callable);
    signal.invoke(12);
    signal.disconnect(a_callable);
    signal.invoke(10);
}
#endif
}

#endif // !CANVAS_SIGNAL_HPP