/*
 * dif.h — DTOB-based diff format with custom types.
 *
 * A .dif file is a DTOB array of entries. Each entry is:
 *   [cid, sha256cid, [op...]]
 *
 * Where op is an enum(copy, add):
 *   copy(start, end)  — copy bytes [start..end] from previous version
 *   add(data)         — insert new raw bytes
 *
 * The first entry is just [add(full_document)] (no previous version).
 * Subsequent entries describe transformations from the previous version.
 *
 * Custom type codes (all in DTOB custom range 16+):
 *   16  cid        — raw (32 bytes, Bromberg hash)
 *   17  sha256cid  — raw (32 bytes, SHA-256 hash)
 *   18  op         — enum(copy, add)
 *   19  copy       — struct(start, end)
 *   20  start      — uint64
 *   21  end        — uint64
 *   22  add        — raw
 */

#ifndef DIF_H
#define DIF_H

#include "dtob.h"
#include "dtob_types.h"

/* custom type codes */
#define DIF_CID        (DTOB_CUSTOM_MIN + 0)   /* 16 — raw 32 bytes */
#define DIF_SHA256CID  (DTOB_CUSTOM_MIN + 1)   /* 17 — raw 32 bytes */
#define DIF_OP         (DTOB_CUSTOM_MIN + 2)   /* 18 — enum(copy, add) */
#define DIF_COPY       (DTOB_CUSTOM_MIN + 3)   /* 19 — struct(start, end) */
#define DIF_START      (DTOB_CUSTOM_MIN + 4)   /* 20 — uint64 */
#define DIF_END        (DTOB_CUSTOM_MIN + 5)   /* 21 — uint64 */
#define DIF_ADD        (DTOB_CUSTOM_MIN + 6)   /* 22 — raw */

DTOB_DEFINE_CUSTOM_TYPES(dif, th,
    DTOB_CUSTOM_TYPE_RAW    (th, DIF_CID,       "cid")
    DTOB_CUSTOM_TYPE_RAW    (th, DIF_SHA256CID,  "sha256cid")
    DTOB_CUSTOM_TYPE_ENUM   (th, DIF_OP,         "op",        DIF_COPY, DIF_ADD)
    DTOB_CUSTOM_TYPE_STRUCT (th, DIF_COPY,       "copy",      DIF_START, DIF_END)
    DTOB_CUSTOM_TYPE_UINT64 (th, DIF_START,      "start")
    DTOB_CUSTOM_TYPE_UINT64 (th, DIF_END,        "end")
    DTOB_CUSTOM_TYPE_RAW    (th, DIF_ADD,        "add")
)

/* ---- value constructors ---- */

static inline DtobValue *dif_cid(const uint8_t *hash) {
    DtobValue *v = dtob_custom(DIF_CID, hash, 32);
    v->inner_code = DTOB_CODE_RAW;
    return v;
}

static inline DtobValue *dif_sha256cid(const uint8_t *hash) {
    DtobValue *v = dtob_custom(DIF_SHA256CID, hash, 32);
    v->inner_code = DTOB_CODE_RAW;
    return v;
}

static inline DtobValue *dif_start(uint64_t val) {
    uint8_t bytes[8];
    for (int i = 7; i >= 0; i--) { bytes[i] = val & 0xFF; val >>= 8; }
    DtobValue *v = dtob_custom(DIF_START, bytes, 8);
    v->inner_code = DTOB_CODE_UINT64;
    return v;
}

static inline DtobValue *dif_end(uint64_t val) {
    uint8_t bytes[8];
    for (int i = 7; i >= 0; i--) { bytes[i] = val & 0xFF; val >>= 8; }
    DtobValue *v = dtob_custom(DIF_END, bytes, 8);
    v->inner_code = DTOB_CODE_UINT64;
    return v;
}

/* copy op: op(copy(start, end)) */
static inline DtobValue *dif_copy(uint64_t s, uint64_t e) {
    DtobValue *copy_struct = dtob_custom(DIF_COPY, NULL, 0);
    dtob_custom_push(copy_struct, dif_start(s));
    dtob_custom_push(copy_struct, dif_end(e));

    DtobValue *op = dtob_custom(DIF_OP, NULL, 0);
    op->inner_code = DIF_COPY;
    /* attach struct children to the enum value */
    for (size_t i = 0; i < copy_struct->num_elements; i++)
        dtob_custom_push(op, copy_struct->elements[i]);
    /* free the copy wrapper without freeing its children */
    copy_struct->num_elements = 0;
    dtob_free(copy_struct);

    return op;
}

/* add op: op(add(data)) */
static inline DtobValue *dif_add(const uint8_t *data, size_t len) {
    DtobValue *op = dtob_custom(DIF_OP, data, len);
    op->inner_code = DIF_ADD;
    return op;
}

/* Build a diff ops array (plain DTOB array of op values) */
static inline DtobValue *dif_diff_new(void) {
    return dtob_array();
}

static inline void dif_diff_push(DtobValue *diff, DtobValue *op) {
    dtob_array_push(diff, op);
}

/* Build a complete entry: [cid, sha256cid, diff] */
static inline DtobValue *dif_entry(const uint8_t *cid_hash,
                                    const uint8_t *sha256_hash,
                                    DtobValue *diff) {
    DtobValue *entry = dtob_array();
    dtob_array_push(entry, dif_cid(cid_hash));
    dtob_array_push(entry, dif_sha256cid(sha256_hash));
    dtob_array_push(entry, diff);
    return entry;
}

#endif /* DIF_H */
