/*-
 * Copyright (c) 2014-present MongoDB, Inc.
 * Copyright (c) 2008-2014 WiredTiger, Inc.
 *	All rights reserved.
 *
 * See the file LICENSE for redistribution information.
 */

#include "wt_internal.h"

/*
 * __wt_struct_check --
 *     Check that the specified packing format is valid, and whether it fits into a fixed-sized
 *     bitfield.
 */
int
__wt_struct_check(
  WT_SESSION_IMPL *session, const char *fmt, size_t len, bool *fixedp, uint32_t *fixed_lenp)
{
    WT_DECL_PACK_VALUE(pv);
    WT_DECL_RET;
    WT_PACK pack;
    int fields;

    WT_RET(__pack_initn(session, &pack, fmt, len));
    for (fields = 0; (ret = __pack_next(&pack, &pv)) == 0; fields++)
        ;
    WT_RET_NOTFOUND_OK(ret);

    if (fixedp != NULL && fixed_lenp != NULL) {
        if (fields == 0) {
            *fixedp = true;
            *fixed_lenp = 0;
        } else if (fields == 1 && pv.type == 't') {
            *fixedp = true;
            *fixed_lenp = pv.size;
        } else
            *fixedp = false;
    }

    return (0);
}

/*
 * __wt_struct_confchk --
 *     Check that the specified packing format is valid, configuration version.
 */
int
__wt_struct_confchk(WT_SESSION_IMPL *session, WT_CONFIG_ITEM *v)
{
    return (__wt_struct_check(session, v->str, v->len, NULL, NULL));
}

/*
 * __wt_struct_size --
 *     Calculate the size of a packed byte string.
 */
int
__wt_struct_size(WT_SESSION_IMPL *session, size_t *lenp, const char *fmt, ...)
{
    WT_DECL_RET;
    va_list ap;

    va_start(ap, fmt);
    ret = __wt_struct_sizev(session, lenp, fmt, ap);
    va_end(ap);

    return (ret);
}

/*
 * __wt_struct_pack --
 *     Pack a byte string.
 */
int
__wt_struct_pack(WT_SESSION_IMPL *session, void *buffer, size_t len, const char *fmt, ...)
{
    WT_DECL_RET;
    va_list ap;

    va_start(ap, fmt);
    ret = __wt_struct_packv(session, buffer, len, fmt, ap);
    va_end(ap);

    return (ret);
}

/*
 * __wt_struct_unpack --
 *     Unpack a byte string.
 */
int
__wt_struct_unpack(WT_SESSION_IMPL *session, const void *buffer, size_t len, const char *fmt, ...)
{
    WT_DECL_RET;
    va_list ap;

    va_start(ap, fmt);
    ret = __wt_struct_unpackv(session, buffer, len, fmt, ap);
    va_end(ap);

    return (ret);
}

/*
 * __wt_struct_repack --
 *     Return the subset of the packed buffer that represents part of the format. If the result is
 *     not contiguous in the existing buffer, a buffer is reallocated and filled.
 */
int
__wt_struct_repack(WT_SESSION_IMPL *session, const char *infmt, const char *outfmt,
  const WT_ITEM *inbuf, WT_ITEM *outbuf)
{
    WT_DECL_PACK_VALUE(pvin);
    WT_DECL_PACK_VALUE(pvout);
    WT_DECL_RET;
    WT_PACK packin, packout;
    const uint8_t *before, *end, *p;
    const void *start;

    start = NULL;
    p = inbuf->data;
    end = p + inbuf->size;

    WT_RET(__pack_init(session, &packout, outfmt));
    WT_RET(__pack_init(session, &packin, infmt));

    /* Outfmt should complete before infmt */
    while ((ret = __pack_next(&packout, &pvout)) == 0) {
        if (p >= end)
            WT_RET(EINVAL);
        if (pvout.type == 'x' && pvout.size == 0 && pvout.havesize)
            continue;
        WT_RET(__pack_next(&packin, &pvin));
        before = p;
        WT_RET(__unpack_read(session, &pvin, &p, (size_t)(end - p)));
        if (pvout.type != pvin.type)
            WT_RET(ENOTSUP);
        if (start == NULL)
            start = before;
    }
    WT_RET_NOTFOUND_OK(ret);

    /* Be paranoid - __pack_write should never overflow. */
    WT_ASSERT(session, p <= end);

    outbuf->data = start;
    outbuf->size = WT_PTRDIFF(p, start);

    return (0);
}
