/*
Copyright (c) 2017-2020,
Battelle Memorial Institute; Lawrence Livermore National Security, LLC; Alliance for Sustainable Energy, LLC.  See
the top-level NOTICE for additional details. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once

#include "Federate.hpp"
#include "data_view.hpp"

#include <functional>

namespace helics {
class MessageFederateManager;
class Endpoint;
/** class defining the block communication based interface */
class HELICS_CXX_EXPORT MessageFederate:
    public virtual Federate // using virtual inheritance to allow combination federate
{
  public:
    /**constructor taking a federate information structure and using the default core
    @param fedName the name of the messageFederate, can be left empty to use a default or one from fi
    @param fi  a federate information structure
    */
    MessageFederate(const std::string& fedName, const FederateInfo& fi);
    /**constructor taking a core and a federate information structure, sore information in fi is ignored
    @param fedName the name of the messageFederate, can be left empty to use a default or one from fi
    @param core a shared ptr to a core to join
    @param fi  a federate information structure
    */
    MessageFederate(
        const std::string& fedName,
        const std::shared_ptr<Core>& core,
        const FederateInfo& fi);
    /**constructor taking a string with the required information
    @param configString can be either a JSON file, TOML file or a string containing JSON code
    */
    explicit MessageFederate(const std::string& configString);
    /**constructor taking a string with the required information
    @param name the name of the federate, can be empty to get name from config
    @param configString can be either a JSON file, TOML file or a string containing JSON code
    */
    MessageFederate(const std::string& name, const std::string& configString);
    /** move constructor*/
    MessageFederate(MessageFederate&& mFed) noexcept;
    /** delete copy constructor*/
    MessageFederate(const MessageFederate& mFed) = delete;
    /** default constructor*/
    MessageFederate();
    /** special constructor should only be used by child classes in constructor due to virtual inheritance*/
    explicit MessageFederate(bool res);
    // copy constructor and copy assignment are disabled
    /** destructor */
    virtual ~MessageFederate();
    /** move assignment*/
    MessageFederate& operator=(MessageFederate&& mFed) noexcept;
    /** delete copy assignment*/
    MessageFederate& operator=(const MessageFederate& mFed) = delete;

  protected:
    virtual void startupToInitializeStateTransition() override;
    virtual void initializeToExecuteStateTransition() override;
    virtual void updateTime(Time newTime, Time oldTime) override;
    virtual std::string localQuery(const std::string& queryStr) const override;

  public:
    /** register an endpoint
    @details call is only valid in startup mode
    @param eptName the name of the endpoint
    @param type the defined type of the interface for endpoint checking if requested
    */
    Endpoint& registerEndpoint(
        const std::string& eptName = std::string(),
        const std::string& type = std::string());

    /** register an endpoint directly without prepending the federate name
    @details call is only valid in startup mode
    @param eptName the name of the endpoint
    @param type the defined type of the interface for endpoint checking if requested
    @return a Reference to an Endpoint Object
    */
    Endpoint&
        registerGlobalEndpoint(const std::string& eptName, const std::string& type = std::string());

    /** register an indexed Endpoint
    @details register a global endpoint as part of a 1D array of endpoints
    @param eptName the name of the endpoint array
    @param index1 the index into a 1 dimensional array of endpoints
    @param type the optional type on the endpoint
    */
    Endpoint& registerIndexedEndpoint(
        const std::string& eptName,
        int index1,
        const std::string& type = std::string())
    {
        return registerGlobalEndpoint(eptName + '_' + std::to_string(index1), type);
    }

    virtual void registerInterfaces(const std::string& configString) override;

    /** register a set Message interfaces
    @details call is only valid in startup mode it is a protected call to add an
    @param configString  the location of the file(TOML or JSON) or JSON String to load to generate the
    interfaces
    */
    void registerMessageInterfaces(const std::string& configString);

  private:
    /** register a set Message interfaces from JSON
 @details call is only valid in startup mode it is a protected call to add an
 @param jsonString  the location of the file or JSON String to load to generate the interfaces
 */
    void registerMessageInterfacesJson(const std::string& jsonString);

    /** register a set Message interfaces using a toml file
  @details call is only valid in startup mode it is a protected call to add an
  @param tomlString  the location of the TOML to load to generate the interfaces
  */
    void registerMessageInterfacesToml(const std::string& tomlString);

  public:
    /** give the core a hint for known communication paths
    @details the function will generate an error in the core if a communication path is not present once the
    simulation is initialized
    @param localEndpoint the local endpoint of a known communication pair
    @param remoteEndpoint of a communication pair
    */
    void registerKnownCommunicationPath(
        const Endpoint& localEndpoint,
        const std::string& remoteEndpoint);
    /** subscribe to valueFederate publication to be delivered as Messages to the given endpoint
    @param ept the specified endpoint to deliver the values
    @param key the name of the publication to subscribe
    */
    void subscribe(const Endpoint& ept, const std::string& key);
    /** check if the federate has any outstanding messages*/
    bool hasMessage() const;
    /* check if a given endpoint has any unread messages*/
    bool hasMessage(const Endpoint& ept) const;

    /**
     * Returns the number of pending receives for the specified destination endpoint.
     */
    uint64_t pendingMessages(const Endpoint& ept) const;
    /**
     * Returns the number of pending receives for all endpoints.
     */
    uint64_t pendingMessages() const;
    /** receive a packet from a particular endpoint
    @param ept the identifier for the endpoint
    @return a message object*/
    std::unique_ptr<Message> getMessage(const Endpoint& ept);
    /** receive a communication message for any endpoint in the federate
    @details the return order will be in order of endpoint creation then order of arrival
    all messages for the first endpoint, then all for the second, and so on
    @return a unique_ptr to a Message object containing the message data*/
    std::unique_ptr<Message> getMessage();

    /** send a message
    @details send a message to a specific destination
    @param source the source endpoint
    @param dest a string naming the destination
    @param data a buffer containing the data
    @param dataLength the length of the data buffer
    */
    void sendMessage(
        const Endpoint& source,
        const std::string& dest,
        const char* data,
        size_t dataLength)
    {
        sendMessage(source, dest, data_view(data, dataLength));
    }
    /** send a message
    @details send a message to a specific destination
    @param source the source endpoint
    @param dest a string naming the destination
    @param message a data_view of the message
    */
    void sendMessage(const Endpoint& source, const std::string& dest, const data_view& message);
    /** send an event message at a particular time
    @details send a message to a specific destination
    @param source the source endpoint
    @param dest a string naming the destination
    @param data a buffer containing the data
    @param dataLength the length of the data buffer
    @param sendTime the time the message should be sent
    */
    void sendMessage(
        const Endpoint& source,
        const std::string& dest,
        const char* data,
        size_t dataLength,
        Time sendTime)
    {
        sendMessage(source, dest, data_view(data, dataLength), sendTime);
    }
    /** send an event message at a particular time
    @details send a message to a specific destination
    @param source the source endpoint
    @param dest a string naming the destination
    @param message a data_view of the message data to send
    @param sendTime the time the message should be sent
    */
    void sendMessage(
        const Endpoint& source,
        const std::string& dest,
        const data_view& message,
        Time sendTime);
    /** send an event message at a particular time
    @details send a message to a specific destination
    @param source the source endpoint
    @param message a pointer to the message
    */
    void sendMessage(const Endpoint& source, std::unique_ptr<Message> message);

    /** send an event message at a particular time
    @details send a message to a specific destination
    @param source the source endpoint
    @param message a message object
    */
    void sendMessage(const Endpoint& source, const Message& message);

    /** get an endpoint by its name
    @param name the Endpoint
    @return an Endpoint*/
    Endpoint& getEndpoint(const std::string& name) const;

    /** get an Endpoint from an index
    @param index the index of the endpoint to retrieve index is 0 based
    @return an Endpoint*/
    Endpoint& getEndpoint(int index) const;

    /** register a callback for all endpoints
    @param callback the function to execute upon receipt of a message for any endpoint
    */
    void setMessageNotificationCallback(const std::function<void(Endpoint&, Time)>& callback);
    /** register a callback for a specific endpoint
    @param ept the endpoint to associate with the specified callback
    @param callback the function to execute upon receipt of a message for the given endpoint
    */
    void setMessageNotificationCallback(
        const Endpoint& ept,
        const std::function<void(Endpoint&, Time)>& callback);

    /** add a named filter to an endpoint for all message coming from the endpoint*/
    void addSourceFilter(const Endpoint& ept, const std::string& filterName);
    /** add a named filter to an endpoint for all message going to the endpoint*/
    void addDestinationFilter(const Endpoint& ept, const std::string& filterName);

    virtual void disconnect() override;

    /**get the number of registered endpoints*/
    int getEndpointCount() const;

  private:
    /** @brief PIMPL design pattern with the implementation details for the MessageFederate*/
    std::unique_ptr<MessageFederateManager> mfManager;
};
} // namespace helics
