#pragma once

#include <future>
#include <string>
#include <map>

// common.h
#define OPENFIN_ADAPTER_API __declspec(dllimport) 

namespace OpenFinAdapterApi {
	using namespace std;
	// EndpointId.h
		/// \brief A structure that identifies a Channels endpoint
	struct EndpointId
	{
	public:
		std::string uuid;
		std::string name;
		std::string channelName;
		std::string channelId;
		std::string endpointId;
	};

	// MessageChannelClient.h
	/// \brief Base class for a channel client and channel provider
	class OPENFIN_ADAPTER_API MessageChannelClient
	{
	public:
		/// \brief Registers a listener of a topic
		/// \param topic The topic
		/// \param listener A listener
		void registerTopic(const string& topic, const function<shared_ptr<string>(shared_ptr<string>, EndpointId)>& listener);

		/// \brief Unregisters a listener of a topic
		/// \param topic The topic
		void unregisterTopic(const string& topic);
	};

	// ChannelClient.h
	/// \brief Represents a Channel Client that can connect to a Channel Provider
	class OPENFIN_ADAPTER_API ChannelClient : public MessageChannelClient
	{
	public:
		/// \brief Connects the client to a channel
		/// \param wait whether to wait for the provider to open if not already open
		/// \return A shared pointer to an async future which completes when the connection has been established
		shared_future<void> connectAsync(bool wait = true);

		/// \brief Disconnects the client from the channel
		/// \return A shared pointer to an async future which completes when the channel has been disconnected
		shared_future<void> disconnectAsync() const;

		/// \brief Dispatches a signal on a topic to a channel provider, with no payload
		/// \param topic The topic on which to dispatch the signal
		/// \return The response from the provider
		shared_future<shared_ptr<string>> dispatchAsync(const string& topic) const;

		/// \brief Dispatches a payload on a topic to the channel provider
		/// \param topic The topic on which to dispatch the payload
		/// \param payloadJson The payload of the message in JSON format
		/// \return The response from the provider
		shared_future<shared_ptr<string>> dispatchAsync(const string& topic, const string& payloadJson) const;

		/// \brief Creates a subscription to the channel disconnected event
		/// \param listener The listener function
		void subscribeDisconnected(const function<void()>& listener);

		/// \brief Un-registers a listener from the channel disconnected event
		/// \param listener The registered listener function
		void unsubscribeDisconnected(const function<void()>& listener);
	};

	// ChannelProvider.h  
	/// \brief Represents a Channel Provider that accepts connections from Channel Clients
	class OPENFIN_ADAPTER_API ChannelProvider : public MessageChannelClient
	{
	public:
		/// \brief Opens the Channel Provider, making it available for client connections
		/// \return An async future that completes when the provider is open
		shared_future<void> openAsync();

		/// \brief Registers a listener function that will be called upon client connections
		/// \param listener A listener
		void subscribeClientConnected(const function<void(const EndpointId&)>& listener);

		/// \brief Un-registers a listener from being called upon client connections
		/// \param listener The registered listener
		void unsubscribeClientConnected(const function<void(const EndpointId&)>& listener);

		/// \brief Registers a listener function that will be called upon client disconnections
		/// \param listener A listener
		void subscribeClientDisconnected(const function<void(const EndpointId&)>& listener);

		/// \brief Un-registers a listener from being called upon client disconnections
		/// \param listener The registered listener
		void unsubscribeClientDisconnected(const function<void(const EndpointId&)>& listener);

		/// \brief Dispatches an empty message to a remote endpoint on a topic
		/// \param targetChannel The target channel endpoint
		/// \param topic The topic
		/// \return The response from the remote endpoint
		shared_future<shared_ptr<string>> dispatchAsync(const EndpointId& targetChannel, const string& topic) const;

		/// \brief Dispatches a payload to a remote endpoint on a topic
		/// \param targetChannel The target channel endpoint
		/// \param topic The topic
		/// \param payloadJson The payload
		/// \return The response from the remote endpoint
		shared_future<shared_ptr<string>> dispatchAsync(const EndpointId& targetChannel, const string& topic, const string& payloadJson) const;

		/// \brief Dispatches a payload to all connected clients
		/// \param topic The topic
		/// \param payloadJson The payload
		/// \return An async future
		shared_future<shared_ptr<string>> broadcastAsync(const string& topic, const string& payloadJson) const;

		/// \brief Dispatches an empty message to all connected clients on a topic
		/// \param topic The topic
		/// \return An async future
		shared_future<shared_ptr<string>> broadcastAsync(const string& topic) const;
	};

	// Channels.h
	/// \brief Represents Channels connectivity for a Runtime connection, providing functions for creating clients and providers.
	class OPENFIN_ADAPTER_API Channels
	{
	public:
		/// \brief Creates a Channel Client for the given channel name
		/// \param channelName The name of the channel
		/// \return A pointer to the client object
		shared_ptr<ChannelClient> createClient(const string& channelName);

		/// \brief Creates a Channel Provider with the given name
		/// \param channelName The name of the channel
		/// \return A pointer to the created channel provider
		shared_ptr<ChannelProvider> createProvider(const string& channelName);
	};

	// OpenFinApiException.h
		/// \brief Represents and error thrown by the OpenFin runtime API
	class OPENFIN_ADAPTER_API OpenFinApiException : public std::exception
	{
	public:
		OpenFinApiException(const std::string& message);

		const char* what() const noexcept override;
	};

	// RvmOptions.h
		/// \brief The options required to initialise the RVM
	class OPENFIN_ADAPTER_API RvmOptions
	{

	public:
		struct RvmCacheKey {
			string productName;
			string productVersion;
			string companyName;
			string runtimeVersion;
		};

		bool noUi;
		bool disableAutoUpdates;
		bool doNotLaunch;
		string installDirectory;
		string workingDirectory;
		RvmCacheKey cacheKey;

		RvmOptions(bool no_ui,
			bool disableAutoUpdates,
			bool doNotLaunch,
			const string& installDirectory,
			const string& workingDirectory,
			const string& productName,
			const string& productVersion,
			const string& companyName,
			const string& runtimeVersion);
	};

	// RuntimeOptions.h
	/// \brief The options required for initialising the runtime
	class OPENFIN_ADAPTER_API RuntimeOptions
	{
	public:
		/// \brief Enum of the port discovery mode to use
		enum PortDiscoveryMode {
			/// \brief No port discovery. The provided port will be used.
			None,
			/// \brief Port discovery enabled using the Window class method.
			WindowClass
		};

		/// \brief Advanced settings to configure how the adapter manages and connects to a runtime instance. 
		enum class RuntimeConnectOptions : uint16_t {
			/// \brief No advanced options
			None = 0x0000,
			/// \brief Automatically disconnects when the last application closes
			NonPersistent = 0x0001,
		};

		/// \brief Connection UUID
		string uuid;

		/// \brief The per-customer or per-contract licensing identifier for the RVM
		string license_key;

		/// \brief Requested Runtime Version to connect to
		string version;

		/// \brief Requested Runtime defaults to 127.0.0.1
		string  host;

		/// \brief Requested Runtime port number; only used when PortDiscovery is disabled.
		int32_t port;

		/// \brief Determines Port discovery methods to be used. Defaults to all available.
		PortDiscoveryMode portDiscoveryMode;

		/// \brief Arguments to be passed to the Runtime
		string arguments;

		/// \brief The RVM Options to be used.
		RvmOptions* rvmOptions;

		/// \brief Security Realm
		string securityRealm;

		/// \brief Advanced settings to configure how the adapter manages and connects to a runtime instance.
		RuntimeConnectOptions runtimeConnectOptions;

		/// \brief Disables heartbeating from client to runtime
		bool disableHeartbeating;

		/// \brief Constructs a new instance of the RuntimeOptions
		/// \param uuid Connection UUID
		/// \param licenseKey The per-customer or per-contract licensing identifier for the RVM
		/// \param version Requested Runtime Version to connect to
		/// \param host Requested Runtime defaults to 127.0.0.1
		/// \param port Requested Runtime port number; only used when PortDiscovery is disabled.
		/// \param portDiscoveryMode Determines Port discovery methods to be used. Defaults to all available.
		/// \param arguments Arguments to be passed to the Runtime
		/// \param rvmOptions The RVM Options to be used.
		/// \param securityRealm Security Realm
		///	\param runtimeConnectOptions Advanced settings to configure how the adapter manages and connects to a runtime instance.
		/// \param disableHeartbeating Disables heartbeating from client to runtime
		RuntimeOptions(const string& uuid,
			const string& licenseKey,
			const string& version,
			const string& host,
			int32_t port,
			PortDiscoveryMode portDiscoveryMode,
			const string& arguments,
			const RvmOptions& rvmOptions,
			const string& securityRealm,
			const RuntimeConnectOptions runtimeConnectOptions = RuntimeConnectOptions::None,
			bool disableHeartbeating = false);

		/// \brief Constructs a new instance of the RuntimeOptions
		/// \param uuid Connection UUID
		/// \param licenseKey The per-customer or per-contract licensing identifier for the RVM
		/// \param version Requested Runtime Version to connect to
		RuntimeOptions(
			const string& uuid,
			const string& licenseKey,
			const string& version
		);

	};

	// DesktopSystem.h
	///
	/// \brief The Desktop System API
	class OPENFIN_ADAPTER_API DesktopSystem {
	public:
		shared_future<shared_ptr<string>> launchManifest(const string& manifestUrl, const shared_ptr<RvmOptions>& rvmOptions = nullptr) const;
	};

	// Runtime.h
	///
	/// \brief Represents a connection to the OpenFin runtime
	class OPENFIN_ADAPTER_API Runtime
	{
	public:
		/// \brief Gets whether the runtime is connected
		/// \return Whether the runtime is connected
		bool isConnected() const;

		/// \brief Gets the runtime ID
		string getRuntimeId();

		/// \brief Gets the Runtime options 
		RuntimeOptions getRuntimeOptions();

		/// \brief Initialises and connects to the runtime
		/// \return The async future
		future<void> connectAsync();

		/// \brief Disconnects from the runtime
		/// \return The async future
		future<void> disconnectAsync();

		/// \brief Gets the Channels object
		/// \return The Channels object
		shared_ptr<Channels> getChannels();

		/// \brief Gets the Desktop System API object
		/// \return The DesktopSystem object
		shared_ptr<DesktopSystem> getDesktopSystem();

		/// \brief Registers a listener function that will be called when the adapter disconnects
		/// \param listener A listener
		void subscribeDisconnected(const function<void()>& listener);

		/// \brief Unregisters a listener function such that it will no longer be called when the adapter disconnects
		/// \param listener The listener
		void unsubscribeDisconnected(const function<void()>& listener);
	};

	// RuntimeFactory.h
	/// \brief A factory for creating runtime instances from a set of runtime options
	class OPENFIN_ADAPTER_API RuntimeFactory
	{
	private:
		map<string, shared_ptr<Runtime>> runtimes;

	public:
		/// \brief Gets a runtime instance for the given options
		/// \param runtimeOptions The options used to create the runtime instance
		/// \return The Runtime instance
		shared_ptr<Runtime> getRuntimeInstance(const RuntimeOptions& runtimeOptions);
	};
}