/*-------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation.  All rights reserved.
 *
 * include/io/pgbson.h
 *
 * The BSON type serialization.
 *
 *-------------------------------------------------------------------------
 */

#ifndef PRIVATE_PGBSON_H
#error Do not import this header file. Import bson_common.h instead
#endif

#ifndef PG_BSON_H
#define PG_BSON_H

#include <bson.h>

#if PG_VERSION_NUM >= 160000
#include <varatt.h>
#endif
#include <nodes/pg_list.h>

/*
 * Max length of string, generated by converting unit32 type value to a string.
 * Max value of a unit32 is 4294967295 (0xFFFFFFFF in hex), which is 10 digits.
 * For a string additional 1 char is required for '\0', so a total of 11 chars
 */
#define UINT32_MAX_STR_LEN 11

typedef struct
{
	int32 vl_len_;              /* varlena header (do not touch directly!) */
	char vl_dat[FLEXIBLE_ARRAY_MEMBER];         /* bson is here */
} pgbson;

#define DatumGetPgBson(n) ((pgbson *) PG_DETOAST_DATUM(n))
#define DatumGetPgBson_MAYBE_NULL(n) (DatumGetPointer(n) == NULL ? NULL : \
									  (pgbson *) PG_DETOAST_DATUM(n))
#define PG_GETARG_PGBSON(n) (DatumGetPgBson(PG_GETARG_DATUM(n)))
#define PG_GETARG_MAYBE_NULL_PGBSON(n) PG_ARGISNULL(n) ? NULL : PG_GETARG_PGBSON(n)

/*
 * Use this macro in general when you need the performance of not copying
 * small tuples. This will avoid copies for small tuples,
 * but also will keep a variable VARSIZE. to detoast this, you will need
 * VARSIZE_ANY or VARDATA_ANY
 */
#define DatumGetPgBsonPacked(n) ((pgbson *) PG_DETOAST_DATUM_PACKED(n))
#define PG_GETARG_PGBSON_PACKED(n) (DatumGetPgBsonPacked(PG_GETARG_DATUM(n)))
#define PG_GETARG_MAYBE_NULL_PGBSON_PACKED(n) PG_ARGISNULL(n) ? NULL : \
	PG_GETARG_PGBSON_PACKED(n)


/*
 * Compatibility alignment macro for pgbson struct,
 * pgbson has a 128 byte alignment requirement and PG 16+ supports aligned allocation.
 * We only want to suppress alignment sanitizer for PG versions < 16 via
 * pg_attribute_no_sanitize_alignment()
 */
#if PG_VERSION_NUM >= 160000
#define pgbson_require_alignment()
#else
#define pgbson_require_alignment() pg_attribute_no_sanitize_alignment()
#endif

/* basic type functions */
bool PgbsonEquals(const pgbson *left, const pgbson *right);
int PgbsonCountKeys(const pgbson *bsonDocument);
int BsonDocumentValueCountKeys(const bson_value_t *value);
bool IsBsonHexadecimalString(const char *string);

/* input functions from various sources (json string, buffers, bytea) */
pgbson * PgbsonInitFromJson(const char *jsonString);
pgbson * PgbsonInitFromBuffer(const char *buffer, uint32_t bufferLength);
pgbson * PgbsonCloneFromPgbson(const pgbson *bson);
pgbson * PgbsonInitEmpty(void);
pgbson * PgbsonInitFromIterDocumentValue(const bson_iter_t *iter);
pgbson * PgbsonInitFromDocumentBsonValue(const bson_value_t *value);
pgbson * PgbsonInitFromHexadecimalString(const char *hexString);
uint32_t PgbsonGetBsonSize(const pgbson *bson);
void PgbsonValidateInputBson(const pgbson *document, bson_validate_flags_t validateFlag);
void ValidateInputBsonBytes(const uint8_t *documentBytes,
							uint32_t documentBytesLength,
							bson_validate_flags_t validateFlag);

pgbson * CastByteaToPgbson(bytea *byteBuffer);

/* output functions functions to various formats (json string, bytea)s */
const char * PgbsonToJsonForLogging(const pgbson *bsonDocument);
const char * BsonValueToJsonForLogging(const bson_value_t *value);
const char * BsonValueToJsonForLoggingWithOptions(const bson_value_t *value, bool
												  quoteStrings);
const char * FormatBsonValueForShellLogging(const bson_value_t *bson);
const char * PgbsonIterDocumentToJsonForLogging(const bson_iter_t *iter);
const char * PgbsonToCanonicalExtendedJson(const pgbson *bsonDocument);
const char * PgbsonToLegacyJson(const pgbson *bsonDocument);
const char * PgbsonToHexadecimalString(const pgbson *bsonDocument);

bytea * CastPgbsonToBytea(pgbson *bsonDocument);
bson_value_t ConvertPgbsonToBsonValue(const pgbson *document);

/* pgbon bson_iter functions */
bool PgbsonInitIteratorAtPath(const pgbson *bson, const char *path,
							  bson_iter_t *iterator);
void PgbsonInitIterator(const pgbson *bson, bson_iter_t *iterator);
void BsonValueInitIterator(const bson_value_t *value, bson_iter_t *iterator);


pgbson * CopyPgbsonIntoMemoryContext(const pgbson *document, MemoryContext context);

pgbson * BsonValueToDocumentPgbson(const bson_value_t *value);

bool PgbsonHasDocumentId(const pgbson *document);
bool DocumentBsonValueHasDocumentId(const bson_value_t *document);
pgbson * PgbsonGetDocumentId(const pgbson *document);

pgbson * PgbsonDeduplicateFields(const pgbson *document);
List * PgbsonDecomposeFields(const pgbson *document);
void PgbsonGetBsonValueAtPath(const pgbson *bson, const char *path, bson_value_t *value);

/*
 * Validate if the pgbson is an empty document.
 * Note that the bson spec (https://bsonspec.org/spec.html) implies that an array has
 * 4 bytes for the array length, followed by a list of [ 1 byte type code, path, value]
 * This implies that a non empty array has 4 bytes of length, 1 byte of type code
 * (for the first element), and at least 1 byte for the index path which is 6 bytes at least.
 */
inline static bool
IsPgbsonEmptyDocument(const pgbson *value)
{
	return value != NULL && VARSIZE_ANY_EXHDR(value) < 6;
}


/*
 * Free pgbson value if not null.
 */
static inline void
PgbsonFreeIfNotNull(pgbson *value)
{
	if (value != NULL)
	{
		pfree(value);
	}
}


/*
 * Generate a new ObjectID document.
 * e.g, { "" : ObjectId("5f3e3b3b1d9f3b0001f3b3b1")}
 */
static inline pgbson *
PgbsonGenerateOidDocument()
{
	bson_value_t objectidValue;
	objectidValue.value_type = BSON_TYPE_OID;
	bson_oid_init(&(objectidValue.value.v_oid), NULL);
	return BsonValueToDocumentPgbson(&objectidValue);
}


#endif
