#ifndef _diffusion_update_constraint_h_
#define _diffusion_update_constraint_h_ 1

/*
 * Copyright © 2018 - 2024 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 update-constraint.h
 *
 * A constraint to be applied to an update operation or the creation of an
 * update stream.
 *
 * Constraints describe a condition that must be satisfied for an operation to
 * succeed. Constraints can be applied to the setting of a value or creation of
 * an update stream. Constraints are only evaluated on the server.
 *
 * The constraints are evaluated using the:
 *
 * - active session locks
 * - existence of the topic
 * - current value of the topic
 *
 * The value of a topic can be described in several ways. The value can be
 * described as an exact value, a partial value or an unset value.
 *
 * Constraints can be composed with one another. It is only possible to
 * construct logical ANDs of constraints. Constraints can only be composed if
 * the resulting constraint is satisfiable. Multiple session locks can be held
 * but a topic can only have a single value. Constraints specifying multiple
 * topic values cannot be constructed.
 *
 * @since 6.2
 */

#include "datatypes.h"
#include "misc/deprecate.h"
#include "session-lock.h"
#include "update-constraint-value.h"


/**
 * Opaque topic update constraint struct
 */
typedef struct DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T;

/**
 * An operator used in a constraint comparison.
 *
 * @since 6.10
 */
typedef enum
{
    /**
     * Strict binary equality.
     *
     * This operator requires that the binary topic value is exactly
     * equivalent to the value supplied for comparison.
     */
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS = 1,

    /**
     * Lenient equals.
     *
     * This operator requires that the topic value is logically equal to the
     * supplied value.
     *
     * If the supplied value is a string, the string representation of the
     * specified topic value is compared for string equality.
     *
     * If the supplied value is a number (`Integer`, `Long` or `Double`),
     * the corresponding topic value may be a number or a string containing
     * a parseable number and will be compared for numeric equality.
     *
     * If the supplied value is `null`, the condition will be satisfied if the
     * value at a specified pointer is JSON `null`.
     */
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_EQ = 2,

    /**
     * Lenient not equals.
     *
     * This operator requires that the topic value is logically not equal to
     * the supplied value.
     *
     * If the supplied value is a string, the string representation of the
     * specified topic value is compared for string equality.
     *
     * If the supplied value is a number (`Integer`, `Long` or `Double`),
     * the corresponding topic value may be a number or a string containing
     * a parseable number and will be compared for numeric equality.
     *
     * If the supplied value is `null`, the condition will be satisfied if the
     * value at a specified pointer not JSON `null`.
     */
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_NE = 3,

    /**
     * Lenient greater than.
     *
     * This operator requires that the topic value is greater than the
     * supplied value.
     *
     * The supplied value must be a number (`Integer`, `Long` or `Double`).
     * The corresponding topic value may be a number or a string containing
     * a parseable number and the condition will be satisfied if the topic
     * value is greater than the supplied value.
     */
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_GT = 4,

    /**
     * Lenient greater than or equals.
     *
     * This operator requires that the topic value is greater than or equal
     * to the supplied value.
     *
     * The supplied value must be a number (`Integer`, `Long` or `Double`).
     * The corresponding topic value may be a number or a string containing
     * a parseable number and the condition will be satisfied if the topic
     * value is greater than or equal to the supplied value.
     */
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_GE = 5,

    /**
     * Lenient less than.
     *
     * This operator requires that the topic value is less than the supplied
     * value.
     *
     * The supplied value must be a number (`Integer`, `Long` or `Double`).
     * The corresponding topic value may be a number or a string containing
     * a parseable number and the condition will be satisfied if the topic
     * value is less than the supplied value.
     */
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_LT = 6,

    /**
     * Lenient less than or equals.
     *
     * This operator requires that the topic value is less than or equal to
     * the supplied value.
     *
     * The supplied value must be a number (`Integer`, `Long` or `Double`).
     * The corresponding topic value may be a number or a string containing
     * a parseable number and the condition will be satisfied if the topic
     * value is less than or equal to the supplied value.
     */
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_LE = 7,
} DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_T;

/**
 * @brief Create a composed constraint that represents a logical AND of two constraints.
 *
 * `diffusion_topic_update_constraint_free` should be called on the pointer when no longer needed.
 *
 * @param left_constraint   a constraint that will be logically-ANDed with right_constraint
 * @param right_constraint  a constraint that will be logically-ANDed with left_constraint
 *
 * @return                  a composed constraint that represents a logical AND of `left_constraint`
 *                          and `right_constraint`
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_and(
    const DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *left_constraint,
    const DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *right_constraint);


/**
 * @brief Create a composed constraint that represents a logical OR of two constraints.
 *
 * `diffusion_topic_update_constraint_free` should be called on the pointer when no longer needed.
 *
 * @param left_constraint   a constraint that will be logically-ORed with right_constraint
 * @param right_constraint  a constraint that will be logically-ORed with left_constraint
 *
 * @return                  a composed constraint that represents a logical OR of `left_constraint`
 *                          and `right_constraint`
 *
 * @since 6.10
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_or(
    const DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *left_constraint,
    const DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *right_constraint);


/**
 * @brief Create a constraint requiring a lock to be held by the session.
 *
 * This can be used to co-ordination operations between multiple
 * sessions.
 *
 * `diffusion_topic_update_constraint_free` should be called on the pointer when no longer needed.
 *
 * @param lock  the lock
 *
 * @return      the constraint
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_locked(const DIFFUSION_SESSION_LOCK_T *lock);


/**
 * @brief Create a constraint requiring the current value of the topic to exactly
 * match the supplied value.
 *
 * `NULL` value cannot be passed as a parameter because
 * it would prevent the required topic type being inferred.
 *
 * This is exactly equivalent to calling
 * `diffusion_topic_update_constraint_value_comparison(DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS, value)`.
 *
 * This can be used to change the value of a topic. This constraint is
 * unsatisfied if no topic is present at the path making it unsuitable
 * for operations that try to add topics.
 *
 * `diffusion_topic_update_constraint_free` should be called on the pointer when no longer needed.
 *
 * @param value     the value
 *
 * @return          the constraint
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_value(const BUF_T *value);


/**
 * @brief Create a constraint comparing the current value of a topic to a
 * supplied value
 *
 * If a `String` value is supplied and the operator is
 * {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_EQ EQ} or
 * {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_NE NE}, the string representation of
 * the topic will be compared to the supplied value.
 * This can only be used with primitive topic types (or {@link TOPIC_TYPE_TIME_SERIES
 * TIME_SERIES} topics with a primitive event type).
 * Other operators (other than {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS IS}) are not
 * permitted with String values.
 *
 * If a number value is supplied (`Integer`, `Long` or `Double`), the value will be compared with
 * the number value of the topic.
 * This will work with {@link TOPIC_TYPE_STRING STRING},{@link TOPIC_TYPE_INT64 INT64} or
 * {@link TOPIC_TYPE_DOUBLE DOUBLE} topics (or {@link TOPIC_TYPE_TIME_SERIES TIME_SERIES} topics
 * with a primitive event type) only.
 * {@link TOPIC_TYPE_STRING STRING} topics can only be compared if they contain a value that can be
 * parsed as a number.
 *
 * If the value of a {@link TOPIC_TYPE_STRING STRING} topic cannot be parsed as a number, or the
 * topic is of any other non-number type the constraint will be unsatisfied.
 * Any of the operators (other than {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS IS})
 * can be used with such number comparisons.
 * Decimal numbers can be compared with integral numbers so `1` is equal to `1.0`, `"1"`, or `"1.0"`.
 *
 * If the {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS IS} operator is specified,
 * the specified value will be compared to the topic value for strict binary equality.
 *
 * The value type can be any value type supported by {@link DIFFUSION_DATATYPE DataTypes} (`Integer`
 * is allowed and is treated as a `Long`) or any `Bytes` value and can be used to compare
 * against any topic type.
 *
 * When a {@link TOPIC_TYPE_STRING STRING}, {@link TOPIC_TYPE_INT64 INT64} or
 * {@link TOPIC_TYPE_DOUBLE DOUBLE} topic is updated to a `NULL` value, the topic is set to have
 * no value.
 *
 * Use the {@link diffusion_topic_update_constraint_no_value()} constraint to check if the topic
 * has no value.
 * This constraint is useful when changing the value of a topic. This constraint is unsatisfied if
 * no topic is present at the path, making it unsuitable for operations that try to add topics.
 *
 * @param comparison_operator  the operator the determines the type of comparison
 * @param value     the value
 *
 * @return          the constraint
 *
 * @since 6.10
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_value_comparison(
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_T comparison_operator,
    DIFFUSION_UPDATE_CONSTRAINT_VALUE_T *value);


/**
 * @brief Create a constraint requiring the topic to have no value.
 *
 * This is useful when setting the first value of a topic. This
 * constraint is unsatisfied if no topic is present at the path, making
 * it unsuitable for operations that try to add topics.
 *
 * `diffusion_topic_update_constraint_free` should be called on the pointer when no longer needed.
 *
 * @return the constraint
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_no_value(void);


/**
 * @brief Create a constraint requiring the path to have no topic.
 *
 * This is useful when setting the first value of a topic being added
 * using {@link diffusion_topic_update_add_and_set} without changing the
 * value if the topic already exists.
 * This constraint is unsatisfied if a topic is present at the path,
 * making it unsuitable for operations that try to set topics without adding them.
 *
 * `diffusion_topic_update_constraint_free` should be called on the pointer when no longer needed.
 *
 * @return the constraint
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_no_topic(void);


/**
 * @brief Create a constraint with no requirements.
 *
 * This constraint is always satisfied.
 *
 * `diffusion_topic_update_constraint_free` should be called on the pointer when no longer needed.
 *
 * @return the constraint
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_unconstrained(void);


/**
 * @brief Require a value at a specific position in the JSON object.
 *
 * This is equivalent to calling `diffusion_topic_update_constraint_partial_json_comparison`
 * with an operator of `DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS`.
 *
 * The `pointer` is a
 * <a href="https://tools.ietf.org/html/rfc6901">JSON Pointer</a>
 * syntax reference locating the `value` in the JSON object.
 *
 * Only string, int64, and double values are supported. The null value
 * may be passed for any type.
 *
 * `diffusion_topic_update_constraint_free` should be called on the pointer when no longer needed.
 *
 * @param pointer  the pointer expression
 * @param datatype the value datatype
 * @param value    the value
 *
 * @return          the constraint.
 *                  `NULL` if `datatype` isn't one of `DATATYPE_STRING`, `DATATYPE_INT64` or
 *                  `DATATYPE_DOUBLE`.
 *                  Also `NULL` if `pointer` is `NULL`.
 *
 * @deprecated      since 6.10. Rather use `diffusion_topic_update_constraint_partial_json_comparison`
 *                  method with the `DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS` operator.
 */
DEPRECATED(
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T
        *diffusion_topic_update_constraint_partial_json_with_value(const char *pointer, DIFFUSION_DATATYPE datatype, const BUF_T *value));


/**
 * Compares a location within the JSON topic value to a specified value.
 *
 * If there is no value found at the specified pointer position, the
 * constraint will be unsatisfied.
 *
 * If a `String` value is supplied and the operator is
 * {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_EQ EQ} or
 * {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_NE NE}, the string
 * representation of the topic value at the given pointer will be
 * compared to the supplied value.
 * If the value at the pointer position is not a string or number the constraint
 * will be unsatisfied. Other operators (other than
 * {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS IS}) are not permitted with
 * String values.
 *
 * If a number value (`Integer`, `Long` or `Double`) is supplied the value will be
 * compared with the number value at the topic location.
 * This will work with JSON string or number values only. JSON strings can only be
 * compared if they contain a value that can be parsed as a number.
 * If a string value at the location cannot be parsed as a number, the constraint will
 * be unsatisfied.
 * Any of the operators (other than {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS IS})
 * can be used with such number comparisons.
 *
 * Decimal numbers can be compared with integral numbers so `1` is equal to `1.0`, `"1"`, or `"1.0"`.
 *
 * If a `NULL` value is supplied and the operator is
 * {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_EQ EQ} or
 * {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_NE NE}, the topic value at
 * the given pointer will be compared to JSON null. Other operators
 * (other than {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS IS}) are not permitted with a
 * `NULL` value.
 *
 * If a `Boolean` value is supplied and the operator is {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_EQ EQ},
 * the topic value at the given pointer will be compared to the boolean value.
 * Other operators are not permitted with a boolean value.
 *
 * If the {@link DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_IS IS} operator is specified
 * the supplied value will be compared to the topic value for strict binary equality.
 * In this case the value must be of type `String`, `Integer`, `Long`, `Double`, `Bytes`, or
 * `NULL`.
 * This is slightly more efficient than the lenient comparisons described above.
 *
 * @param pointer               the pointer expression
 * @param comparison_operator   the operator that determines the type of comparison
 * @param value                 the value
 *
 * @return  the constraint.
 *          `NULL` if `datatype` isn't one of `DATATYPE_STRING`, `DATATYPE_INT64` or
 *          `DATATYPE_DOUBLE`.
 *          Also `NULL` if `pointer` is `NULL`.
 *
 * @since 6.10
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_partial_json_comparison(
    const char *pointer,
    DIFFUSION_TOPIC_UPDATE_CONSTRAINT_OPERATOR_T comparison_operator,
    DIFFUSION_UPDATE_CONSTRAINT_VALUE_T *value);


/**
 * @brief Require a specific position in the JSON object to be absent. This
 * does not match positions that have null values.
 *
 * The `pointer` is a
 * <a href="https://tools.ietf.org/html/rfc6901">JSON Pointer</a>
 * syntax reference that should have no value in the JSON object.
 *
 * `diffusion_topic_update_constraint_free` should be called on the pointer when no longer needed.
 *
 * @param pointer the pointer expression
 *
 * @return the constraint. `NULL` if `pointer` is `NULL`
 */
DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *diffusion_topic_update_constraint_partial_json_without_value(const char *pointer);


/**
 * @brief Free a memory allocated `DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T`
 *
 * @param constraint the constraint to be freed
 */
void diffusion_topic_update_constraint_free(DIFFUSION_TOPIC_UPDATE_CONSTRAINT_T *constraint);


#endif
