/*
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 "Broker.hpp"
#include "core-types.hpp"

#include <chrono>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>

namespace helics {
/**
 * Factory for building Core API instances.
 */
namespace BrokerFactory {

    class BrokerBuilder {
      public:
        /** build a new broker of the builder type*/
        virtual std::shared_ptr<Broker> build(const std::string& name) = 0;
    };

    /** template for making a Broker builder*/
    template<class BrokerTYPE>
    class BrokerTypeBuilder final: public BrokerBuilder {
      public:
        static_assert(std::is_base_of<Broker, BrokerTYPE>::value,
                      "Type does not inherit from helics::Core");

        using broker_build_type = BrokerTYPE;
        virtual std::shared_ptr<Broker> build(const std::string& name) override
        {
            return std::make_shared<BrokerTYPE>(name);
        }
    };

    /** define a new Broker Builder from the builder give a name and build code*/
    void defineBrokerBuilder(std::shared_ptr<BrokerBuilder> cb, const std::string& name, int code);

    /** template function to create a builder and link it into the library*/
    template<class BrokerTYPE>
    std::shared_ptr<BrokerBuilder> addBrokerType(const std::string& brokerTypeName, int code)
    {
        auto bld = std::make_shared<BrokerTypeBuilder<BrokerTYPE>>();
        std::shared_ptr<BrokerBuilder> bbld = std::static_pointer_cast<BrokerBuilder>(bld);
        defineBrokerBuilder(bbld, brokerTypeName, code);
        return bbld;
    }
    /**
     * Creates a Broker object of the specified type.
     *
     * Invokes initialize() on the instantiated Core object.
     */
    std::shared_ptr<Broker> create(core_type type, const std::string& configureString);
    /** Create a broker from command line arguments*/
    std::shared_ptr<Broker> create(core_type type, int argc, char* argv[]);
    /** Create a broker from command line arguments in a vector*/
    std::shared_ptr<Broker> create(core_type type, std::vector<std::string> args);

    std::shared_ptr<Broker>
        create(core_type type, const std::string& brokerName, const std::string& configureString);

    std::shared_ptr<Broker>
        create(core_type type, const std::string& brokerName, int argc, char* argv[]);

    /** Create a broker from command line arguments in a vector*/
    std::shared_ptr<Broker>
        create(core_type type, const std::string& brokerName, std::vector<std::string> args);

    /** locate a coreBroker by name
@param brokerName the name of the broker
@return a shared_ptr to the Broker*/
    std::shared_ptr<Broker> findBroker(const std::string& brokerName);

    /** try to find a joinable broker of a specific type*/
    std::shared_ptr<Broker> findJoinableBrokerOfType(core_type type);

    /** get all available brokers*/
    std::vector<std::shared_ptr<Broker>> getAllBrokers();

    /** check if there are any active Brokers*/
    bool brokersActive();

    /** register a coreBroker so it can be found by others
@details also cleans up any leftover brokers that were previously unregistered this can be
controlled by calling cleanUpBrokers earlier if desired
@param broker a pointer to a Broker object that should be able to be found globally
@param type the core type associated with a broker
@return true if the registration was successful false otherwise*/
    bool registerBroker(const std::shared_ptr<Broker>& broker, core_type type);
    /** remove a broker from the registry
@param name the name of the broker to unregister
*/
    void unregisterBroker(const std::string& name);

    /** add a type associated with a broker*/
    void addAssociatedBrokerType(const std::string& name, core_type type);

    /** clean up unused brokers
@details when brokers are unregistered they get put in a holding area that gets cleaned up when a
new broker is registered or when the clean up function is called this prevents some odd threading
issues
@return the number of brokers still operating
*/
    size_t cleanUpBrokers();

    /** clean up unused brokers
@details when brokers are unregistered they get put in a holding area that gets cleaned up when a
new broker is registered or when the clean up function is called this prevents some odd threading
issues
@param delay the number of milliseconds to wait to ensure stuff is cleaned up
@return the number of brokers still operating
*/
    size_t cleanUpBrokers(std::chrono::milliseconds delay);

    /** make a copy of the broker pointer to allow access to the new name
@return true if successful
 */
    bool copyBrokerIdentifier(const std::string& copyFromName, const std::string& copyToName);

    /** display the help listing for a particular core_type*/
    void displayHelp(core_type type = core_type::UNRECOGNIZED);

    /** terminate all running Brokers*/
    void terminateAllBrokers();
}  // namespace BrokerFactory
}  // namespace helics
