/*

BROKER MODULE

Copyright (C) 2017-2019 by Xose PĂ©rez <xose dot perez at gmail dot com>

*/

#pragma once

#include <functional>
#include <utility>
#include <vector>
#include <tuple>

// Example usage:
//
// module.h
// BrokerDeclare(CustomBroker, void(int));
//
// module.cpp
// BrokerBind(CustomBroker);
//
// other.cpp
// #include "module.h"
// void func() {
//     CustomBroker::Register([](int arg) { Serial.println(arg); }
//     CustomBroker::Publish(12345);
// }

template <typename Func>
struct TBroker {};

template <typename R, typename ...Args>
struct TBroker<R(Args...)> {

    using TArgs = typename std::tuple<Args...>;
    using TCallback = std::function<R(Args...)>;
    using TCallbacks = std::vector<TCallback>;

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

    TBroker() = default;

    // TODO: https://source.chromium.org/chromium/chromium/src/+/master:base/callback_list.h
    // Consider giving out 'subscription' / 'token', so that the caller can remove callback later

    void Register(TCallback callback) {
        callbacks.push_back(callback);
    }

    void Publish(Args... args) {
        for (auto& callback : callbacks) {
            callback(args...);
        }
    }

    protected:

    TCallbacks callbacks;

};

// TODO: since 1.14.0 we intoduced static syntax for Brokers, ::Register & ::Publish.
// Preserve it (up to a point) when creating module-level objects.
// Provide a helper namespace with Register & Publish, instance and 
// To help out VS Code with argument discovery, put TArgs as the first template parameter.

#define BrokerDeclare(Name, Signature) \
namespace Name { \
using type = TBroker<Signature>; \
extern type Instance; \
template<typename S = type::TArgs, typename ...Args> \
inline void Register(Args&&... args) { \
    Instance.Register(std::forward<Args>(args)...); \
}\
\
template<typename S = type::TArgs, typename ...Args> \
inline void Publish(Args&&... args) { \
    Instance.Publish(std::forward<Args>(args)...); \
}\
}

#define BrokerBind(Name) \
namespace Name { \
Name::type Instance; \
}