#ifndef _diffusion_recoverable_update_stream_h_
#define _diffusion_recoverable_update_stream_h_ 1

/*
 * Copyright © 2023 DiffusionData Ltd., All Rights Reserved.
 *
 * Use is subject to licence terms.
 *
 * NOTICE: All information contained herein is, and remains the
 * property of DiffusionData. The intellectual and technical
 * concepts contained herein are proprietary to DiffusionData and
 * may be covered by U.S. and Foreign Patents, patents in process, and
 * are protected by trade secret or copyright law.
 */

/**
 * @file recoverable-update-stream.h
 *
 * @since 6.10
 */

#include "buf.h"
#include "datatypes/diffusion-value.h"
#include "session.h"
#include "update-stream.h"


/**
 * @brief Opaque diffusion recoverable topic update stream struct.
 */
typedef struct DIFFUSION_RECOVERABLE_UPDATE_STREAM_T DIFFUSION_RECOVERABLE_UPDATE_STREAM_T;

/**
 * @brief Structure representing the response for all DIFFUSION_RECOVERABLE_UPDATE_STREAM_T operations.
 */
typedef struct diffusion_recoverable_update_stream_callback_response_s
{
    DIFFUSION_TOPIC_CREATION_RESULT_T result;
} DIFFUSION_RECOVERABLE_UPDATE_STREAM_CALLBACK_RESPONSE_T;

/**
 * @brief Error handler callback for Recoverable update stream operation.
 */
typedef int (*RECOVERABLE_UPDATE_STREAM_CALLBACK_HANDLER_T)(
    const SESSION_T *session,
    const DIFFUSION_RECOVERABLE_UPDATE_STREAM_T *recoverable_update_stream,
    const DIFFUSION_RECOVERABLE_UPDATE_STREAM_CALLBACK_RESPONSE_T *response,
    const DIFFUSION_ERROR_T *error,
    void *context);

/**
 * @brief Structure provided when calling `diffusion_recoverable_update_stream_set` or
 * `diffusion_recoverable_update_stream_validate`.
 */
typedef struct diffusion_recoverable_update_stream_params_s
{
    /// Callback
    RECOVERABLE_UPDATE_STREAM_CALLBACK_HANDLER_T on_callback;

    /// User-supplied context returned to callbacks.
    void *context;
} DIFFUSION_RECOVERABLE_UPDATE_STREAM_PARAMS_T;

/**
 * @brief Sets the topic to a specified value.
 *
 * The `NULL` value can only be passed to the `value` parameter
 * when updating `TOPIC_TYPE_STRING`, `TOPIC_TYPE_INT64` or
 * `TOPIC_TYPE_DOUBLE` topics.
 *
 * When a `TOPIC_TYPE_STRING`, `TOPIC_TYPE_INT64` or `TOPIC_TYPE_DOUBLE`
 * topic is set to `NULL`, the topic will be updated
 * to have no value. If a previous value was present subscribers
 * will receive a notification that the new value is `NULL`.
 * New subscribers will not receive a value notification.
 *
 * This function may fail with the following errors:
 * <dl>
 *      <dt>
 *              `DIFF_ERR_INCOMPATIBLE_EXISTING_TOPIC`
 *      </dt>
 *      <dd>
 *              The existing topic specification in the server does not match
 *              the topic specification provided to the update stream.
 *              Subsequent calls may fail with a `DIFF_ERR_INVALID_UPDATE_STREAM`
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_NO_SUCH_TOPIC`
 *      </dt>
 *      <dd>
 *              The topic does not exists in the server and no topic specification
 *              was provided to the update stream to create one.
 *              Subsequent calls may fail with a `DIFF_ERR_INVALID_UPDATE_STREAM`
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_ACCESS_DENIED`
 *      </dt>
 *      <dd>
 *              Session does not have enough permissions for operation.
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_NO_SESSION`
 *      </dt>
 *      <dd>
 *              Session no longer exists.
 *      </dd>

 *      <dt>
 *              `DIFF_ERR_UPDATE_STREAM_RECOVERY_LIMIT_EXCEEDED`
 *      </dt>
 *      <dd>
 *              `DIFF_ERR_CLUSTER_ROUTING` or `DIFF_ERR_CLUSTER_REPARTITION` has occurred
 *              in the cluster where the server resides.
 *              The total amount of retries set by the User has been reached and the
 *              update stream has stopped attempting to recover.
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_UNSATISFIED_CONSTRAINT`
 *      </dt>
 *      <dd>
 *              An update constraint has been provided and it was not satisfied while
 *              processing the request on the server.
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_INVALID_UPDATE_STREAM`
 *      </dt>
 *      <dd>
 *              The update stream is currently in an invalid state and no further operations
 *              are possible.
 *      </dd>
 * </dl>
 *
 * @param session                       The session handle. If NULL, this function returns immediately.
 * @param recoverable_update_stream     The update stream to enact the update.
 * @param value                         The update value.
 * @param params                        Parameters describing a topic update stream set request and callbacks handlers
 *                                      which may be invoked in response.
 * @param error                         Populated when an error occurs
 *
 * @return                              true is operation is successful, false otherwise.
 *                                      If the operation fails, the error is populated if present.
 *
 * @since 6.10
 */
bool diffusion_recoverable_update_stream_set(
    const SESSION_T *session,
    const DIFFUSION_RECOVERABLE_UPDATE_STREAM_T *recoverable_update_stream,
    const BUF_T *value,
    DIFFUSION_RECOVERABLE_UPDATE_STREAM_PARAMS_T params,
    DIFFUSION_ERROR_T *error);


/**
 * @brief Return the latest value of the topic set using this update stream.
 *
 * The returned value reflects the last value that has been set, before it
 * is sent to the server.
 *
 * If the server rejects a set operation, the topic value will not change
 * and this update stream will be invalidated.
 *
 * `diffusion_value_free` should be called on the pointer when no longer needed.
 *
 * @param recoverable_update_stream     The stream to get the latest value from.
 * @param error                         Populated when an error occurs.
 *
 * @return                              The cached value of the topic.
 *                                      NULL, if the update stream is in an invalid state.
 *
 * @since 6.10
 */
DIFFUSION_VALUE_T *
diffusion_recoverable_update_stream_get(const DIFFUSION_RECOVERABLE_UPDATE_STREAM_T *recoverable_update_stream, DIFFUSION_ERROR_T *error);

/**
 * @brief Validates the update stream.
 *
 * Update streams are validated lazily when the method
 * `diffusion_recoverable_update_stream_set` is called.
 * This function allows the stream to be validated before a value needs to be set.
 *
 * If the update stream has not been validated yet, calling this function
 * checks the topic exists, the topic type is correct, the constraint is
 * satisfied and the session has permission to update the topic.
 *
 * Once it has been validated calling this function checks the topic has not been
 * removed, no other stream has been created for the topic, the value
 * of the topic has not been changed by anything else and the session
 * still has permission to update the topic.
 *
 * This function may fail with the following errors:
 * <dl>
 *      <dt>
 *              `DIFF_ERR_INCOMPATIBLE_EXISTING_TOPIC`
 *      </dt>
 *      <dd>
 *              The existing topic specification in the server does not match
 *              the topic specification provided to the update stream.
 *              Subsequent calls may fail with a `DIFF_ERR_INVALID_UPDATE_STREAM`
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_NO_SUCH_TOPIC`
 *      </dt>
 *      <dd>
 *              The topic does not exists in the server and no topic specification
 *              was provided to the update stream to create one.
 *              Subsequent calls may fail with a `DIFF_ERR_INVALID_UPDATE_STREAM`
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_ACCESS_DENIED`
 *      </dt>
 *      <dd>
 *              Session does not have enough permissions for operation.
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_NO_SESSION`
 *      </dt>
 *      <dd>
 *              Session no longer exists.
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_UPDATE_STREAM_RECOVERY_LIMIT_EXCEEDED`
 *      </dt>
 *      <dd>
 *              `DIFF_ERR_CLUSTER_ROUTING` or `DIFF_ERR_CLUSTER_REPARTITION` has occurred
 *              in the cluster where the server resides.
 *              The total amount of retries set by the User has been reached and the
 *              update stream has stopped attempting to recover.
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_UNSATISFIED_CONSTRAINT`
 *      </dt>
 *      <dd>
 *              An update constraint has been provided and it was not satisfied while
 *              processing the request on the server.
 *      </dd>
 *      <dt>
 *              `DIFF_ERR_INVALID_UPDATE_STREAM`
 *      </dt>
 *      <dd>
 *              The update stream is currently in an invalid state and no further operations
 *              are possible.
 *      </dd>
 * </dl>
 *
 * @param session                       The session handle. If NULL, this function returns immediately.
 * @param recoverable_update_stream     The update stream to be validated.
 * @param params                        Parameters describing a recoverable update stream validate request and callbacks handlers
 *                                      which may be invoked in response.
 * @param error                         Populated when an error occurs.
 *
 * @return                              True is operation is successful, false otherwise.
 *                                      If the operation fails, the error is populated, if present.
 *
 * @since 6.10
 */
bool diffusion_recoverable_update_stream_validate(
    const SESSION_T *session,
    const DIFFUSION_RECOVERABLE_UPDATE_STREAM_T *recoverable_update_stream,
    DIFFUSION_RECOVERABLE_UPDATE_STREAM_PARAMS_T params,
    DIFFUSION_ERROR_T *error);


/**
 * @brief Free a memory allocated `DIFFUSION_RECOVERABLE_UPDATE_STREAM_T`
 *
 * @param recoverable_update_stream     The recoverable update stream to be freed.
 *
 * @since 6.10
 */
void diffusion_recoverable_update_stream_free(DIFFUSION_RECOVERABLE_UPDATE_STREAM_T *recoverable_update_stream);


/**
 * @brief Indicates if the recoverable update stream is currently recovering
 * from a transient exception
 *
 * @param recoverable_update_stream     The recoverable update stream.
 *
 * @return                              True if the recoverable update stream is recovering,
 *                                      false otherwise
 *
 * @since 6.10
 */
bool diffusion_recoverable_update_stream_is_in_recovery(const DIFFUSION_RECOVERABLE_UPDATE_STREAM_T *recoverable_update_stream);


/**
 * @brief Indicates if the error is recoverable by the update stream.
 *
 * @param error         The error.
 * @return              True if the error is recoverable, false otherwise.
 *
 * @since 6.10
 */
bool diffusion_recoverable_update_stream_is_error_recoverable(const DIFFUSION_ERROR_T *error);


/**
 * @brief Begins the procedure to attempt recovery from a transient error.
 *
 * @param session                       The session where the recoverable update stream encountered an error.
 * @param recoverable_update_stream     The recoverable update stream that will attempt to recover.
 *
 * @since 6.10
 */
void diffusion_recoverable_update_stream_recover(
    const SESSION_T *session,
    const DIFFUSION_RECOVERABLE_UPDATE_STREAM_T *recoverable_update_stream);

#endif
