/*
Discovery service for finding available service instances.
*/

#pragma once

#include <string>
#include <vector>
#include <memory>
#include <functional>
#include <mutex>
#include <stinger/utils/iconnection.hpp>
#include <stinger/mqtt/properties.hpp>
#include "structs.hpp"
#include "enums.hpp"
#include "property_structs.hpp"

{%for ns in config.cpp.namespace%}
namespace {{ns}} {
{%endfor-%}
namespace {{stinger.name|snake_case}} {

{%if stinger.properties | length > 0 %}
struct InitialPropertyValues {
public:
    {%for prop_name, prop in stinger.properties.items()%}
    std::optional<{{prop.cpp.property_struct_name}}> {{prop_name | lowerCamelCase}};
    int {{prop_name | lowerCamelCase}}Version = 0;
    {%endfor%}

    bool isComplete() const;
};
{%endif%}

struct InstanceInfo {
public:
    std::optional<std::string> serviceId;
    {%-for topic_param in config.topics.params %}
    std::optional<std::string> {{topic_param|lowerCamelCase}};
    {%-endfor%}
    {%if stinger.properties | length > 0 %}InitialPropertyValues initial_property_values; // Not included in (de-)serialization.{%endif%}

    void UpdateFromRapidJsonObject(const rapidjson::Value& jsonObj);
    void AddToRapidJsonObject(rapidjson::Value& parent, rapidjson::Document::AllocatorType& allocator) const;

    bool isComplete() const;
};

class {{stinger.name|UpperCamelCase}}Discovery
{
public:
    // Constructor taking a broker connection and service_id
    {{stinger.name|UpperCamelCase}}Discovery(std::shared_ptr<stinger::utils::IConnection> broker);

    virtual ~{{stinger.name|UpperCamelCase}}Discovery();

    // Set a callback to be invoked when a new service instance is discovered
    void SetDiscoveryCallback(const std::function<void(const InstanceInfo&)>& cb);

    // Get a singleton instance ID. Returns immediately if one is available,
    // otherwise waits until one is discovered.
    std::future<InstanceInfo> GetSingleton();

    // Get all discovered instance IDs
    std::vector<InstanceInfo> GetInstances() const;

private:
    void _onMessage(const stinger::mqtt::Message& msg);
    int _discoverySubscriptionId = -1;
    int _allPropertySubscriptionId = -1;
    stinger::utils::CallbackHandleType _brokerMessageCallbackHandle = 0;
    std::shared_ptr<stinger::utils::IConnection> _broker;
    std::map<uint64_t, InstanceInfo> _discoveredInstances; // Keyed by a hash of service_id, {%for topic_param in config.topics.params %}{%if loop.last%}and {%endif%}{{topic_param}}{%if not loop.last%}, {%endif%}{% endfor%}
    std::function<void(const InstanceInfo&)> _discovery_callback;
    
    mutable std::mutex _mutex;
    std::vector<std::promise<InstanceInfo>> _pending_promises;
};

} // namespace {{stinger.name|snake_case}}
{%for ns in config.cpp.namespace|reverse%}
} // namespace {{ns}}
{%endfor-%}
