/*
Copyright 2024 Andrew Noyes

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#pragma once

#include <stdint.h>

#ifdef __cplusplus
namespace weaselab {
/** A data structure for optimistic concurrency control on ranges of
 * bitwise-lexicographically-ordered keys. All versions must be >= 0.
 *
 * Thread safety:
 *   - It's safe to operate on two different ConflictSets in two different
 * threads concurrently
 *   - It's safe to have multiple threads operating on the same ConflictSet
 * concurrently if and only if all threads only call const methods
 */
struct __attribute__((__visibility__("default"))) ConflictSet {
  enum Result {
    /** The result of a check which does not intersect any conflicting writes */
    Commit,
    /** The result of a check which intersects a write at a version >
       readVersion */
    Conflict,
    /** The result of a check with a readVersion older than the highest call to
       `setOldestVersion` */
    TooOld
  };

  /** Bytes ordered lexicographically */
  struct Key {
    const uint8_t *p;
    int len;
  };

  /** Denotes a set of keys to be checked for conflicting writes since
   * `readVersion` */
  struct ReadRange {
    Key begin;
    /** `end` having length 0 denotes that this range is the single key {begin}.
     * Otherwise this denotes the range [begin, end), and begin must be < end */
    Key end;
    /** `readVersion` older than the oldestVersion or the version of the
     * latest call to `addWrites` minus two billion will result in `TooOld`.
     * Must be <= the version of the latest call to `addWrites` */
    int64_t readVersion;
  };

  /** Denotes a set of keys to be written at `writeVersion` */
  struct WriteRange {
    Key begin;
    /** `end` having length 0 denotes that this range is the single key {begin}.
     * Otherwise this denotes the range [begin, end), and begin must be < end */
    Key end;
  };

  /** The result of checking reads[i] is written in results[i] */
  void check(const ReadRange *reads, Result *results, int count) const;

  /** Reads intersecting writes where readVersion < `writeVersion` will result
   * in `Conflict` (or `TooOld`, eventually).  `writeVersion` must be greater
   * than or equal to all previous write versions. Call `addWrites` with `count`
   * zero to only advance the version. */
  void addWrites(const WriteRange *writes, int count, int64_t writeVersion);

  /** Reads where readVersion < `oldestVersion` will result in `TooOld`. Must be
   * greater than or equal to all previous oldest versions. Must be <= the
   * version of the latest call to `addWrites` */
  void setOldestVersion(int64_t oldestVersion);

  /** Reads where readVersion < oldestVersion will result in `TooOld`. There are
   * no writes initially */
  explicit ConflictSet(int64_t oldestVersion);

  ~ConflictSet();

  /** Returns the total bytes in use by this ConflictSet */
  int64_t getBytes() const;

  /** Experimental! */
  struct MetricsV1 {
    /** A null-terminated string with static lifetime. Identifies this metric.
     * Matches the regex [a-zA-Z][a-zA-Z0-9_]*
     */
    const char *name;
    /** A null-terminated string with static lifetime. Describes this metric.
     * May contain any sequence of UTF-8 characters, but the backslash and the
     * line feed characters are escaped as \\ and \n, respectively.
     */
    const char *help;
    /** Counters are >= 0 and non-decreasing. Gauges are any value. */
    enum Type { Counter, Gauge } type;
    /** Get the most up-to-date (best effort) value for this metric.
     * Thread-safe. */
    double getValue() const;
    /** @private */
    void *p;
  };

  /** Experimental! Store a pointer to an array of MetricsV1 (owned by the
   * ConflictSet) to `*metrics`, and its length to `*count`. This function makes
   * no guarantees about the contents of the metrics (e.g. names, help text, and
   * the meaning of values). A correct implementation is free to return nonsense
   * or nothing at all. Not intended to be inspected programmatically. Only
   * intended to be plumbed along to e.g. Prometheus. Callers may repeatedly
   * call `getValue` on the metrics they're interested in. */
  void getMetricsV1(MetricsV1 **metrics, int *count) const;

#if __cplusplus > 199711L
  ConflictSet(ConflictSet &&) noexcept;
  ConflictSet &operator=(ConflictSet &&) noexcept;
  ConflictSet(const ConflictSet &) = delete;
  ConflictSet &operator=(const ConflictSet &) = delete;
#endif

  /** @private */
  struct Impl;

private:
  Impl *impl;
};
} /* namespace weaselab */

#else

/** A data structure for optimistic concurrency control on ranges of
 * bitwise-lexicographically-ordered keys. All versions must be >= 0.
 *
 * Thread safety:
 *   - It's safe to operate on two different ConflictSets in two different
 * threads concurrently
 *   - It's safe to have multiple threads operating on the same ConflictSet
 * concurrently if and only if all threads only call functions that accept a
 * const ConflictSet pointer
 */
typedef struct ConflictSet ConflictSet;

typedef enum {
  /** The result of a check which does not intersect any conflicting writes */
  ConflictSet_Commit,
  /** The result of a check which intersects a write at a version > readVersion
   */
  ConflictSet_Conflict,
  /** The result of a check with a readVersion older than the highest call to
     `setOldestVersion` */
  ConflictSet_TooOld
} ConflictSet_Result;

/** Bytes ordered lexicographically */
typedef struct {
  const uint8_t *p;
  int len;
} ConflictSet_Key;

/** Denotes a set of keys to be checked for conflicting writes since
 * `readVersion` */
typedef struct {
  ConflictSet_Key begin;
  /** `end` having length 0 denotes that this range is the single key {begin}.
   * Otherwise this denotes the range [begin, end), and begin must be < end */
  ConflictSet_Key end;
  /** `readVersion` older than the oldestVersion or the version of the
   * latest call to `addWrites` minus two billion will result in `TooOld`.
   * Must be <= the version of the latest call to `addWrites` */
  int64_t readVersion;
} ConflictSet_ReadRange;

/** Denotes a set of keys to be written at `writeVersion` */
typedef struct {
  ConflictSet_Key begin;
  /** `end` having length 0 denotes that this range is the single key {begin}.
   * Otherwise this denotes the range [begin, end), and begin must be < end */
  ConflictSet_Key end;
} ConflictSet_WriteRange;

/** The result of checking reads[i] is written in results[i] */
void ConflictSet_check(const ConflictSet *cs,
                       const ConflictSet_ReadRange *reads,
                       ConflictSet_Result *results, int count);

/** Reads intersecting writes where readVersion < `writeVersion` will result
 * in `Conflict` (or `TooOld`, eventually).  `writeVersion` must be greater
 * than or equal to all previous write versions. Call `addWrites` with `count`
 * zero to only advance the version. */
void ConflictSet_addWrites(ConflictSet *cs,
                           const ConflictSet_WriteRange *writes, int count,
                           int64_t writeVersion);

/** Reads where readVersion < `oldestVersion` will result in `TooOld`. Must be
 * greater than or equal to all previous oldest versions. Must be <= the
 * version of the latest call to `addWrites` */
void ConflictSet_setOldestVersion(ConflictSet *cs, int64_t oldestVersion);

/** Reads where readVersion < oldestVersion will result in `TooOld`. There are
 * no writes initially */
ConflictSet *ConflictSet_create(int64_t oldestVersion);

void ConflictSet_destroy(ConflictSet *cs);

/** Returns the total bytes in use by this ConflictSet */
int64_t ConflictSet_getBytes(const ConflictSet *cs);

#endif
