/***************************************************************************************************
 * Copyright 2021 NVIDIA Corporation. All rights reserved.
 **************************************************************************************************/
/// \file
/// \brief  Bridge client

#ifndef MI_NEURAYLIB_IBRIDGE_CLIENT_H
#define MI_NEURAYLIB_IBRIDGE_CLIENT_H

#include <mi/base/enums.h>
#include <mi/base/interface_implement.h>
#include <mi/neuraylib/iserializer.h>

namespace mi {

class IMap;
class IString;

namespace base { class ILogger; }

namespace neuraylib {
class IDeserializer;
class ITransaction;
class IScope;
class IProgress_callback;
class ITag_set;
}

/// Namespace for the Bridge functionality of the \neurayApiName.
/// \ingroup mi_neuray
namespace bridge {

class IClient_video_context;

/** \if IRAY_API \defgroup mi_neuray_bridge Iray Bridge
    \else \defgroup mi_neuray_bridge Bridge
    \endif
    \ingroup mi_neuray

    The Bridge is a feature of \NeurayProductName that allows simple and efficient use of the
    resources of a remote cluster with minimal integration effort. Data is transferred to the remote
    cluster in a way suitable for comparably low-bandwidth, high-latency connections such as an
    Internet connection, where normal \NeurayProductName clustering would not work.

    The Bridge supports server-side caching of data and incremental changes to minimize the amount
    of data sent, as well as low-latency, hardware H.264 video to efficiently use the available
    bandwidth.

    \if IRAY_API Rendering using Iray Bridge is exposed as a render plugin with the same interface
    as local renderers. This means that an application will require minimal configuration to use the
    resources of a remote cluster. Iray Bridge can also be used to efficiently transfer scene data
    to a remote cluster to perform other tasks such as off-line batch rendering. \endif

    \see \ref mi_neuray_bridge_client, \ref mi_neuray_bridge_server
*/

/** \defgroup mi_neuray_bridge_client Bridge client
    \ingroup mi_neuray_bridge

    The Bridge client is integrated in the application that wishes to use the resources of the
    remote cluster. The client connects to a single Bridge server. The client does not form a
    cluster with the Bridge server. The server can be part of a normal cluster of any size, which
    automatically makes the resources of the entire remote cluster available to the client. The
    client can be part of a cluster of any size, but the resources of the client-side cluster are
    used only for local jobs, not when executing jobs using a remote cluster via the Bridge.

    \if IRAY_API The API consists of two layers: a low-level API, called \em Bridge \em API, and a
    high-level API, called \em Iray \em Bridge \em API. The low-level Bridge API is generic and
    independent of domain-specific tasks like rendering. It exposes the full flexibility of the
    Bridge. The high-level Iray Bridge API is specific to rendering. It encapsulates the details of
    the Bridge API and exposes them in a simpler way sufficient for rendering. Using the high-level
    API is strongly recommended unless there are specific needs which require the use of the
    low-level API. See the API component #mi::bridge::IBridge_client for the Bridge API, and
    #mi::bridge::IIray_bridge_client for the Iray Bridge API. \else See the API component
    #mi::bridge::IBridge_client for a starting point. \endif

    \see \ref mi_neuray_bridge, \ref mi_neuray_bridge_server
*/

/** \addtogroup mi_neuray_bridge_client
@{
*/

/// Job states of Bridge jobs.
///
/// Execution of Bridge jobs often involves uploads to the server which can take a considerable
/// time. Each Bridge job goes through a sequence of states defined by this enum, starting in the
/// state #CLIENT_JOB_DETECTING_CHANGES and then progressing to the next state when some conditions
/// are met until the job execution is done and the job ends up in the state #CLIENT_JOB_DONE.
/// States will always progress to the next state in the defined order, but some states might be
/// skipped if there is no work that needs to be done in that state. Note that a lot of work is
/// done in parallel so while the job is still determining which elements that needs to be
/// updated, it will also query the cache status and start uploading data for cache misses, etc.
///
/// \see mi::bridge::IClient_job_progress::get_state()
enum Client_job_state
{
    /// Detecting which database elements needs to be updated before executing the job.
    CLIENT_JOB_DETECTING_CHANGES = 0,

    /// Calculates hashes for the elements that will be updated and to some extent serialize data
    /// that will be sent inline.
    CLIENT_JOB_PREPARING = 1,

    /// Determining how much data needs to be uploaded before executing this job.
    ///
    /// The elements that need to be updated is now known, but there are still pending cache status
    /// requests. The job might not enter this state if no cache status requests were needed, or if
    /// all requests were already answered while in state #CLIENT_JOB_PREPARING.
    CLIENT_JOB_QUERYING_CACHE_STATUS = 2,

    /// Uploading data.
    ///
    /// There are no pending cache status requests left and it is known how much data needs to be
    /// uploaded before executing the job. The job does not enter this state if there were no cache
    /// misses or if all data was already uploaded in previous states.
    CLIENT_JOB_UPLOADING = 3,

    /// Waiting for execution of the job to finish.
    ///
    /// All data needed by the job is now uploaded and the job is either executing on the server or
    /// waiting for all previous jobs to finish before it can start executing. To get progress
    /// feedback in this state it is possible for the server-side job to send partial results back
    /// to the client.
    CLIENT_JOB_PENDING = 4,

    /// The result has been received and deserialized by the client-side job and the job is done.
    CLIENT_JOB_DONE = 5,

    CLIENT_JOB_FORCE_32_BIT = 0xffffffffU
};

mi_static_assert( sizeof( Client_job_state) == sizeof( Uint32));

/// Provides detailed information about the progress of a Bridge job.
class IClient_job_progress : public
    mi::base::Interface_declare<0x7f51a745,0xfcf0,0x4b2d,0x92,0x93,0x2b,0x84,0xd7,0x8c,0xc0,0xe6>
{
public:
    /// Returns the state of the job.
    ///
    /// See #Client_job_state.
    virtual Client_job_state get_state() const = 0;

    /// Returns the number of elements that need to be updated before executing this job.
    ///
    /// In state #CLIENT_JOB_DETECTING_CHANGES this is the number of elements for which changes
    /// have been detected so far. In all other states it is the final number of elements that
    /// needs to be updated.
    virtual Size get_updated_element_count() const = 0;

    /// Returns the number of elements for which hashes needs to be calculated.
    ///
    /// Can be larger than 0 in states up to and including #CLIENT_JOB_PREPARING
    /// and is 0 in all the other states.
    virtual Size get_pending_hash_calculation_count() const = 0;

    /// Returns the number of cache status requests that the server has not yet replied to.
    ///
    /// Can be larger than 0 in states up to and including #CLIENT_JOB_QUERYING_CACHE_STATUS
    /// and is 0 in all the other states.
    virtual Size get_pending_cache_status_count() const = 0;

    /// Returns the total number of cache misses for which data needs to be uploaded.
    ///
    /// The final value is known in the state #CLIENT_JOB_UPLOADING and later.
    virtual Size get_cache_miss_count() const = 0;

    /// Returns the total amount of bytes to upload for all cache misses.
    ///
    /// The final value is known in the state #CLIENT_JOB_UPLOADING and later.
    virtual Size get_cache_miss_bytes() const = 0;

    /// Returns the number of cache misses for which data has been uploaded so far.
    ///
    /// Will be equal to #get_cache_miss_count() in states #CLIENT_JOB_PENDING and later.
    virtual Size get_uploaded_cache_miss_count() const = 0;

    /// Returns the number of bytes that has been uploaded for the cache misses so far.
    ///
    /// Will be equal to #get_cache_miss_bytes() in states #CLIENT_JOB_PENDING and later.
    virtual Size get_uploaded_cache_miss_bytes() const = 0;

    /// Returns the number of elements that have been queued up for serialization. The data
    /// needs to be serialized before it can be sent.
    ///
    /// Can be larger than 0 in states up to and including #CLIENT_JOB_UPLOADING
    /// and is 0 in all later states.
    virtual Size get_pending_data_serialization_count() const = 0;

    /// Returns the name of the currently uploaded element or \c NULL if no element is
    /// currently being uploaded.
    virtual const char* get_currently_uploaded_element_name() const = 0;

    /// Returns the size in bytes of the currently uploaded element or 0 if no element is
    /// currently being uploaded.
    virtual Size get_currently_uploaded_element_size() const = 0;

    /// Returns the number of bytes uploaded for the currently uploaded element or 0 if
    /// no element is currently being uploaded.
    virtual Size get_currently_uploaded_element_uploaded_bytes() const = 0;
};

/// Used to specify a set of elements by name.
///
/// \see #mi::bridge::IClient_job::get_references()
class IElement_set : public
    mi::base::Interface_declare<0x2242471d,0x96f3,0x4962,0x84,0x7e,0xd1,0x20,0xa9,0x6b,0xb6,0x98>
{
public:
    /// Returns the number of elements in the set.
    ///
    /// \see #get_element()
    virtual Size get_length() const = 0;

    /// Returns the \p index -th element of the element set.
    ///
    /// \see #get_length()
    virtual const char* get_element( Size index) const = 0;

    /// Adds \p element to the tag set.
    virtual void add_element( const char* element_name) = 0;
};

/// Represents the client-side part of a job that can be executed by the Bridge server.
///
/// The corresponding server-side part of the job must return the same ID from
/// #mi::neuraylib::ISerializable::get_class_id() and be registered as a Bridge job on the
/// server.
///
/// The client-side part must implement #mi::neuraylib::ISerializable::serialize() to serialize the
/// data that is received by #mi::bridge::IServer_job::deserialize(), and
/// #mi::bridge::IClient_job::receive_remote_result() to deserialize the result of the job which is
/// produced by #mi::bridge::IServer_job::execute().
///
/// It is recommended to derived your implementation from #mi::bridge::Client_job and overriding
/// only the required methods.
///
/// \see #mi::bridge::IClient_session::execute()
class IClient_job : public
    mi::base::Interface_declare<0xe02e4aeb,0x6edd,0x4e40,0xbb,0xe0,0x6c,0xc5,0xe7,0x69,0xa6,0xe,
    neuraylib::ISerializable>
{
public:
    /// Returns the database elements that will be used by this job on the server, identified by
    /// a set of tags.
    ///
    /// The Bridge will guarantee that any elements returned by this method and any elements
    /// indirectly referenced by them will be updated in the server-side cache and database before
    /// this Bridge job executes on the server.
    ///
    /// \note The elements identified by the overload #get_references(IElement_set*)const will also
    ///       be updated.
    ///
    /// \see #upload_only()
    ///
    /// \param[out] result   The set of tags referenced by the Bridge job. The passed-in tag set is
    ///                      empty.
    virtual void get_references( neuraylib::ITag_set* result) const = 0;

    /// Returns the database elements that will be used by this job on the server, identified by
    /// a set of names.
    ///
    /// The Bridge will guarantee that any elements returned by this method and any elements
    /// indirectly referenced by them will be updated in the server-side cache and database before
    /// this Bridge job executes on the server.
    ///
    /// \note The elements identified by the overload #get_references(neuraylib::ITag_set*)const
    ///       will also be updated.
    ///
    /// \see #upload_only()
    ///
    /// \param[out] result   The set of element names referenced by the Bridge job. The passed-in
    ///                      element set is empty.
    virtual void get_references( IElement_set* result) const = 0;

    /// Indicates whether the referenced elements should really be updated in the server-side
    /// database.
    ///
    /// Usually, this method returns \c false, meaning that referenced elements are updated in the
    /// server-side cache and database. In some circumstances it is not necessary to update the
    /// server-side database because only the cache is needed, e.g., when saving snapshots. In this
    /// case, one can optimize the update process by returning \c true, meaning that only the server
    /// side cache but not the database are updated before executing the job.
    ///
    /// \see #get_references()
    virtual bool upload_only() const = 0;

    /// Deserializes the job result from the execution of the job on the server.
    ///
    /// The execution of the job might produce intermediate results by flushing the data serialized
    /// so far. Each call to #mi::neuraylib::ISerializer::flush() on the server will result in a
    /// single call to #receive_remote_result() with the \c last_result flag set to \c false, even
    /// if no data was flushed. When the execution on the server finishes this method will be called
    /// one last time with the \c last_result flag set to \c true with the remaining data.
    ///
    /// See #mi::bridge::IServer_job::execute() for more information and a couple of example use
    /// cases.
    ///
    /// \param deserializer   The deserializer from which to read the (partial) result.
    /// \param last_result    \c true if this is the last chunk of result data the job will produce,
    ///                       \c false otherwise.
    virtual void receive_remote_result(
        neuraylib::IDeserializer* deserializer, bool last_result) = 0;

    /// A callback that provides progress information.
    ///
    /// This method is called whenever the state of the job changes and also periodically if the job
    /// stays in a state for a long time. During execution of the job, custom progress information
    /// can be encoded into chunks of result data.
    ///
    /// \see #Client_job_state, #receive_remote_result(), IServer_job::execute()
    ///
    /// \param job_progress   An instance that contains all progress information. The instance may
    ///                       only be used from within this callback.
    virtual void progress_callback( IClient_job_progress* job_progress) = 0;

    /// A callback that indicates an error during job execution or if the job was canceled.
    ///
    /// \param error_code
    ///                     -     0: The job was explicitly canceled or the transaction was aborted.
    ///                     -    -1: Network error.
    ///                     -    -2: Corresponding server side job does not exist.
    ///                     - <= -3: Unspecified error.
    /// \param msg          A short explanation of the error.
    virtual void error_callback( Sint32 error_code, const char* msg) = 0;
};

/// This mixin class provides a default implementation for some of the methods needed by
/// #mi::bridge::IClient_job.
///
/// It is recommended to derive from this class rather than from #mi::bridge::IClient_job directly.
template <Uint32 i_id1, Uint16 i_id2, Uint16 i_id3
, Uint8 i_id4, Uint8 i_id5, Uint8 i_id6, Uint8 i_id7
, Uint8 i_id8, Uint8 i_id9, Uint8 i_id10, Uint8 i_id11
, class I = IClient_job>
class Client_job : public base::Interface_implement<I>
{
public:
    /// Own type.
    typedef Client_job<i_id1,i_id2,i_id3,i_id4,i_id5,i_id6,i_id7,i_id8,i_id9,i_id10,i_id11,I> Self;

    /// Declares the interface ID
    typedef base::Uuid_t<i_id1,i_id2,i_id3,i_id4,i_id5,i_id6,i_id7,i_id8,i_id9,i_id10,i_id11> IID;

    /// Compares the interface ID \p iid against the interface ID of this interface and its
    /// ancestors.
    ///
    /// \return \c true if \p iid == \c IID() or is equal to one of the interface IDs of
    /// its ancestors, and \c false otherwise.
    static bool compare_iid( const base::Uuid& iid)
    {
        if( iid == IID())
            return true;
        return I::compare_iid( iid);
    }

    /// Acquires a const interface.
    ///
    /// If this interface is derived from or is the interface with the passed
    /// \p interface_id, then return a non-\c NULL \c const #mi::base::IInterface* that
    /// can be casted via \c static_cast to an interface pointer of the interface type
    /// corresponding to the passed \p interface_id. Otherwise return \c NULL.
    ///
    /// In the case of a non-\c NULL return value, the caller receives ownership of the new
    /// interface pointer, whose reference count has been retained once. The caller must
    /// release the returned interface pointer at the end to prevent a memory leak.
    virtual const base::IInterface* get_interface( const base::Uuid& interface_id) const
    {
        if( interface_id == IID()) {
            const Self* self = static_cast<const Self*>( this);
            self->retain();
            return self;
        }
        return I::get_interface_static( this, interface_id);
    }

    /// Acquires a mutable interface.
    ///
    /// If this interface is derived from or is the interface with the passed
    /// \p interface_id, then return a non-\c NULL #mi::base::IInterface* that
    /// can be casted via \c static_cast to an interface pointer of the interface type
    /// corresponding to the passed \p interface_id. Otherwise return \c NULL.
    ///
    /// In the case of a non-\c NULL return value, the caller receives ownership of the new
    /// interface pointer, whose reference count has been retained once. The caller must
    /// release the returned interface pointer at the end to prevent a memory leak.
    virtual base::IInterface* get_interface( const base::Uuid& interface_id)
    {
        if( interface_id == IID()) {
            Self* self = static_cast<Self*>( this);
            self->retain();
            return self;
        }
        return I::get_interface_static( this, interface_id);
    }

    using base::Interface_implement<I>::get_interface;

    /// Returns the class ID corresponding to the template parameters of this mixin class.
    virtual base::Uuid get_class_id() const
    {
        return IID();
    }

    /// Empty body. This method from the base class is not needed for client jobs.
    virtual void deserialize( neuraylib::IDeserializer* deserializer)
    {
        // avoid warnings
        (void) deserializer;
    }

    /// Empty body. The default job references no elements.
    virtual void get_references( neuraylib::ITag_set* result) const
    {
        // avoid warnings
        (void) result;
    }

    /// Empty body. The default job references no elements.
    virtual void get_references( IElement_set* result) const
    {
        // avoid warnings
        (void) result;
    }

    /// Returns \c false in the default implementation.
    virtual bool upload_only() const { return false; }
};

/// Base class for Bridge jobs that only update elements without executing anything.
///
/// Derived jobs with the interface ID of this class do not need a server-side counterpart. This job
/// will work the same way as a normal Bridge job with a corresponding server-side job
/// implementation that does not execute anything and sends back an empty result.
class Update_job : public mi::bridge::Client_job<
    0xf9b3c8e2,0x7688,0x4bf8,0x91,0x36,0xcd,0x3a,0x3f,0x51,0x30,0x7a>
{
    void serialize( neuraylib::ISerializer* serializer) const
    {
        // avoid warnings
        (void) serializer;
    }

    void receive_remote_result( neuraylib::IDeserializer* deserializer, bool last_result)
    {
        // avoid warnings
        (void) deserializer;
        (void) last_result;
    }
};


/// The different states a client session can be in.
///
/// \see #mi::bridge::IClient_session::get_state()
enum Client_session_state
{
    /// The session is trying to establish a connection to the remote Bridge application for the
    /// first time.
    CLIENT_SESSION_CONNECTING,

    /// The session has successfully established a connection to the Bridge server and is ready for
    /// use.
    CLIENT_SESSION_CONNECTED,

    /// The session was disconnected unexpectedly.
    ///
    /// The session cannot be used in this state, but will automatically attempt to reconnect and
    /// resume the session. Session resume is only possible if the corresponding server-side session
    /// is still available when reconnecting, if not the session will be closed.
    ///
    /// Note that session resume is currently not supported so disconnected sessions will always
    /// be closed.
    CLIENT_SESSION_PENDING,

    /// The session has been closed in an orderly fashion, or the session has been pending but the
    /// session could not be resumed.
    ///
    /// Once a session has been closed it can not be used anymore. To attempt a reconnection a new
    /// session must be opened.
    CLIENT_SESSION_CLOSED
};

mi_static_assert( sizeof( Client_session_state) == sizeof( Uint32));

/// Abstract interface for callbacks for session state changes.
///
/// \see #mi::bridge::IClient_session::add_session_state_callback(),
///      #mi::bridge::IClient_session::remove_session_state_callback()
class IClient_session_state_callback : public
    mi::base::Interface_declare<0x9ea6d3b3,0x3d0b,0x4d10,0x89,0xc8,0x89,0xf1,0x20,0x49,0xc6,0xe1>
{
public:
    /// This method is called whenever the session changes its state.
    virtual void session_state_callback( Client_session_state state) = 0;
};

/// Abstract interface for bandwidth measurement events.
///
/// \see #mi::bridge::IClient_session::measure_bandwidth()
class IClient_measure_bandwidth_callback : public
    mi::base::Interface_declare<0x80cf9612,0x3fd4,0x4ed0,0xb1,0x63,0xc0,0xd9,0x84,0xe2,0x6,0xaa>
{
public:
    /// This method is called when the bandwidth measurement starts and then when there is progress.
    ///
    /// \param bytes_done    The number of bytes sent/received so far.
    /// \param total_bytes   The total number of bytes to send/receive.
    virtual void progress_callback( Size bytes_done, Size total_bytes) = 0;

    /// This method is called when the bandwidth measurement is done.
    ///
    /// \param total_bytes   Total number of bytes sent/received.
    /// \param total_time    The time that elapsed on the receiving side between receiving the
    ///                      beginning of the first packet and the end of the last packet.
    virtual void done_callback( Size total_bytes, Float64 total_time) = 0;

    /// This method is called if there is an error preventing the test from finishing.
    ///
    /// \param error_code
    ///                      -    -1: Network error.
    ///                      - <= -2: Unspecified error.
    virtual void error_callback( Sint32 error_code) = 0;
};

/// Represents the client side of a Bridge session.
///
/// A session can be opened by #mi::bridge::IBridge_client::get_session(). Note that repeated calls
/// of that method with the same arguments return the same session, i.e., sessions are shared
/// between different callers. Sharing the same session is vital for features such as incremental
/// changes, and to share other resources that would otherwise be duplicated. The session will be
/// destroyed and client- and server-side resources will be released when all references to the
/// session has been released.
///
/// The session will automatically connect to the server. Use #add_session_state_callback() for
/// callbacks about session state changes. A closed session can not be used anymore. Use
/// #mi::bridge::IBridge_client::get_session() to attempt to reconnect.
///
/// \see #mi::bridge::IBridge_client::get_session()
class IClient_session : public
    mi::base::Interface_declare<0x70bb8712,0x1305,0x4c76,0xb9,0x38,0xad,0x27,0x91,0xea,0xbd,0x78>
{
public:
    /// Returns the state of the session.
    /// \see #mi::bridge::Client_session_state
    virtual Client_session_state get_state() = 0;

    /// Adds a session state callback.
    ///
    /// When adding a callback it will be called immediately once with the current state, and then
    /// every time the session state changes.
    ///
    /// \see #remove_session_state_callback()
    ///
    /// \param callback    The callback to be added.
    virtual void add_session_state_callback( IClient_session_state_callback* callback) = 0;

    /// Removes a previously added session state callback.
    ///
    /// \see #add_session_state_callback()
    ///
    /// \param callback    The callback to be removed.
    virtual void remove_session_state_callback( IClient_session_state_callback* callback) = 0;

    /// Schedules the provided job for execution on the server and returns immediately.
    ///
    /// Jobs are executed asynchronously, but sequentially (per transaction) on the server in the
    /// same order as execution is requested by this method. A job is only executed after its
    /// referenced database elements have been updated from the Bridge client to the Bridge server.
    /// 
    /// Note that the same IClient_job instance must not be executed several times in parallel. 
    /// This is true both within a single transaction and in multiple transactions.
    ///
    /// \param job   The job to execute.
    /// \param transaction The transaction in which context the job will be executed.
    /// \return
    ///              -     0: Success.
    ///              -    -1: Invalid arguments (\p job or transaction is \c NULL).
    ///              -    -2: Invalid transaction state (already committed or aborted).
    ///              -    -3: Network error.
    ///              -    -4: The job reference elements that can't be accessed.
    ///              - <= -5: Unspecified error.
    virtual Sint32 execute(IClient_job* job, neuraylib::ITransaction* transaction) = 0;

    /// Marks the provided job to be canceled and returns immediately. A job progress callback
    /// will be made to inform when the job is canceled or completed. Note that it is not always 
    /// possible to cancel a job depending on the timing so a job can still be completed 
    /// even after successfully calling this method.
    ///
    /// Known limitation: Canceling jobs doing uploads doesn't cancel the upload itself and 
    /// even if the job is canceled successfully some or all uploaded elements might be
    /// stored in the database. To make sure that elements from a job is not persisted in 
    /// the server side database the transaction must be aborted. This will automatically 
    /// cancel all Bridge jobs running in that transaction.
    /// 
    /// \param job   The job to cancel.
    /// \return
    ///              -     0: Job canceled.
    ///              -     1: Job not executing or already canceled.
    ///              -    -1: Invalid arguments (\p job is \c NULL).
    ///              -    -3: Network error.
    ///              - <= -4: Unspecified error.
    virtual Sint32 cancel(IClient_job* job) = 0;

    /// Creates a new video context.
    ///
    /// The video context will be assigned an id which needs to be transferred to the server, along
    /// with any other data required to set up the video source properly. The server use this id to
    /// get the corresponding server-side video context which can be used to stream video frames to
    /// the client. The recommended way of transferring the id to the server is by executing a
    /// Bridge job.
    ///
    /// \return The created video context, or \c NULL in case of failure.
    virtual IClient_video_context* create_video_context() = 0;

    /// Measures the bandwidth to the Bridge server.
    ///
    /// The bandwidth is measured by sending a specified number of packages with a specified size in
    /// bytes. This method is asynchronous, the provided callback interface will be called when
    /// there is progress and when the measurement is done.
    ///
    /// \note For meaningful results the method should only be called when the session is not used
    /// to upload or download other real data at the same time.
    ///
    /// \param progress_callback   A callback interface that will be called with progress
    ///                            information.
    /// \param upload              \c true for measuring upload bandwidth, \c false for measuring
    ///                            download bandwidth.
    /// \param package_size        The size in bytes of a single package.
    /// \param package_count       The number of packages to upload or download.
    /// \return                    0 if the measurement was successfully started, -1 otherwise.
    virtual Sint32 measure_bandwidth(
        IClient_measure_bandwidth_callback* progress_callback,
        bool upload,
        Uint32 package_size,
        Uint32 package_count = 1) = 0;

    /// Sets the receiving logger for log messages forwarded from the Bridge server.
    ///
    /// If the provided logger is \c NULL then forwarded log entries will be written to the general
    /// configured logger as if they were generated locally, but with a prefix identifying the
    /// server that they originated from (default behavior).
    ///
    /// \see #set_receiving_logger()
    ///
    /// \param logger      The receiving logger. It is valid to pass \c NULL in which case logging
    ///                    is reset to be done to the general configured logger.
    virtual void set_receiving_logger( base::ILogger* logger) = 0;

    /// Returns the receiving logger for log messages forwarded from the Bridge server.
    ///
    /// Note that if no receiving logger has been set, this method returns \c NULL and logging is
    /// done using the currently general configured logger.
    ///
    /// \see #set_receiving_logger()
    virtual base::ILogger* get_receiving_logger() const = 0;

    /// Sets the log level of messages that are forwarded from the Bridge server.
    ///
    /// Note that this does not affect the log level of the server, nor the log level of the client.
    /// It only filters the messages forwarded by the server to the client. Also note that the
    /// server will never send out log messages more verbose than the configured limit for the
    /// bridge application the session is connected to.
    ///
    /// \see #get_forwarding_log_level()
    virtual Sint32 set_forwarding_log_level( base::Message_severity level) = 0;

    /// Returns the currently set log level for messages forwarded from the server.
    ///
    /// \see #set_forwarding_log_level
    virtual base::Message_severity get_forwarding_log_level() const = 0;

    /// Returns the number of bytes written to the server so far.
    ///
    /// \see #get_bytes_read()
    virtual Size get_bytes_written() const = 0;

    /// Returns the number of bytes read from the server so far.
    ///
    /// \see #get_bytes_written()
    virtual Size get_bytes_read() const = 0;

    /// Allows the data for specific elements to be overridden by specifying the hash of the 
    /// data to use regardless of the actual data stored in the db. The replacements will only be
    /// done when executing a bridge job that returns the provided top_level_element in its 
    /// get_references() method and only when executed in the same scope as the scope of the 
    /// provided transaction. When an edited element is detected for the job the overrides
    /// map will be consulted to see if an override for the element with that name exists. If 
    /// it does the element won't be serialized and the override hash from the map will
    /// be used instead. The server then use the hash when loading the data from the cache, meaning
    /// that it can be different than the actual data on the client. For this to work, the data
    /// for the hash must be guaranteed to already be in the server side cache. This can be 
    /// achieved by storing snapshots in the server side cache with all required data before 
    /// rendering the scene containing hash overrides.
    ///
    /// \param overrides An IMap with the name of the element to override as key and the hash
    ///                  as string value. The map must be of type Map\<String\>. Call this 
    ///                  method with a \c NULL override map to release it or replace it with a 
    ///                  new one. The map instance is retained by bridge to avoid expensive copying
    ///                  and must not be modified after this call.
    /// \param transaction The hash overrides will be applied only for jobs executing in the same
    ///                    scope as this transaction. The top level element must be visible in 
    ///                    this transaction.
    /// \param top_level_element The top level element. The overrides applies to this element 
    ///                          and all elements it reference when executing a job that returns
    ///                          the specified top level element in its get_references() method.
    /// \return
    ///              -     0: Success.
    ///              -    -1: Invalid arguments (\p job or transaction is \c NULL).
    ///              -    -2: Invalid transaction state (already committed or aborted).
    ///              - <= -2: Unspecified error.
    virtual Sint32 set_hash_overrides(
        const IMap* overrides, 
        const char* top_level_element,
        neuraylib::ITransaction* transaction) = 0;
    
};

/// API component that serves as entry point for the client-side Bridge API.
///
/// Can be used to create sessions to a Bridge application.
class IBridge_client : public
    mi::base::Interface_declare<0xbe270827,0xad68,0x4044,0x94,0x6e,0x9d,0x41,0x4d,0xf,0x75,0x65>
{
public:
    /// Returns a session to the provided Bridge application running on a Bridge server.
    ///
    /// Bridge supports session sharing, so subsequent calls to this method with the same
    /// URL and security token will return an already existing valid session. The session will
    /// be considered the same if the URL and security token strings match exactly, otherwise a
    /// new session will be created. If a session is closed then calling get_session() with the
    /// same URL and security token will create a new session and attempt a new connection to the
    /// server.
    ///
    /// The session will connect to the specified URL automatically. To find out if the session is
    /// connected or if the connection fails add a session state callback by calling
    /// #mi::bridge::IClient_session::add_session_state_callback(). This callback will be called
    /// immediately with the current state, and then when the state changes.
    ///
    /// \see #mi::bridge::IBridge_server::create_application()
    ///
    /// \param application_url  The WebSocket URL to the server-side Bridge application. If
    ///                         the HTTP server listens on host \c "somehost" and port 80, and the
    ///                         application path on the server side is set to \c "/myapp", then the
    ///                         client will connect using the URL \c "ws://somehost:80/myapp". If
    ///                         the connection is encrypted using the SSL protocol, then the prefix
    ///                         \c "wss" needs to be used instead of \c "ws". Note that the port
    ///                         must be specified. The standard port for WebSockets is 80 and 443
    ///                         for secure WebSockets.
    /// \param security_token   An optional security token that can be inspected by the server-side
    ///                         application to decide whether the session should be accepted or
    ///                         rejected.
    virtual IClient_session* get_session(
        const char* application_url, const char* security_token = 0) = 0;

    /// Returns the Bridge protocol version.
    virtual const char* get_bridge_protocol_version() const = 0;
};

/*@}*/ // end group mi_neuray_bridge_client

} // namespace bridge

} // namespace mi

#endif // MI_NEURAYLIB_IBRIDGE_CLIENT_H
