/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

#include "postgres.h"

#include "utils/agtype_raw.h"

/*
 * Used for building an agtype container.
 */
struct agtype_build_state
{
    int a_offset;        // next location to write agtentry
    int i;               // index of current agtentry being processed
    int d_start;         // start of variable-length portion
    StringInfo buffer;
};

/*
 * Define the type and size of the agt_header.
 * Copied from agtype_ext.c.
 */
#define AGT_HEADER_TYPE uint32
#define AGT_HEADER_SIZE sizeof(AGT_HEADER_TYPE)

/*
 * Following macros are usable in the context where
 * agtype_build_state is available
 */
#define BUFFER_RESERVE(size) reserve_from_buffer(bstate->buffer, (size))
#define BUFFER_WRITE_PAD() pad_buffer_to_int(bstate->buffer)
#define BUFFER_WRITE_CONST(offset, type, val) *((type *)(bstate->buffer->data + (offset))) = (val)
#define BUFFER_WRITE_PTR(offset, ptr, len) memcpy(bstate->buffer->data + offset, ptr, len)

static int write_pointer(agtype_build_state *bstate, char *ptr, int len);
static void write_agtentry(agtype_build_state *bstate, agtentry agte);

/*
 * Same as `write_ptr` except the content comes from
 * constant value instead of pointer.
 */
#define write_const(val, type) \
    do \
    { \
        int len = sizeof(type); \
        int offset = BUFFER_RESERVE(len); \
        BUFFER_WRITE_CONST(offset, type, val); \
    } \
    while (0)
#define write_ptr(ptr, len) write_pointer(bstate, ptr, len)
#define write_agt(agte) write_agtentry(bstate, agte)

/*
 * Copies the content of `ptr` to the tail of the buffer (variable-length
 * portion).
 */
static int write_pointer(agtype_build_state *bstate, char *ptr, int len)
{
    int offset = BUFFER_RESERVE(len);
    BUFFER_WRITE_PTR(offset, ptr, len);
    return len;
}

/*
 * Copies the content of `agte` to the next available location in the agtentry
 * portion of the buffer. That location is pointed by `bstate->a_offset`.
 *
 * This function must be called after data is written to the variable-length
 * portion.
 */
static void write_agtentry(agtype_build_state *bstate, agtentry agte)
{
    int totallen = bstate->buffer->len - bstate->d_start;

    /*
     * Bail out if total variable-length data exceeds what will fit in a
     * agtentry length field.  We check this in each iteration, not just
     * once at the end, to forestall possible integer overflow.
     */
    if (totallen > AGTENTRY_OFFLENMASK)
    {
        ereport(
            ERROR,
            (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
             errmsg(
                "total size of agtype array elements exceeds the maximum of %u bytes",
                AGTENTRY_OFFLENMASK)));
    }

    if (((bstate->i) % AGT_OFFSET_STRIDE) == 0)
    {
        agte = (agte & AGTENTRY_TYPEMASK) | totallen | AGTENTRY_HAS_OFF;
    }

    BUFFER_WRITE_CONST(bstate->a_offset, agtentry, agte);
    bstate->a_offset += sizeof(agtentry);
}

/*
 * `header_flag` = a valid agtype_container header field
 * `size` = size of container (number of pairs or elements)
 */
agtype_build_state *init_agtype_build_state(uint32 size, uint32 header_flag)
{
    int agtentry_count;
    int agtentry_len;
    agtype_build_state *bstate;

    bstate = palloc0(sizeof(agtype_build_state));
    bstate->buffer = makeStringInfo();
    bstate->a_offset = 0;
    bstate->i = 0;

    // reserve for varlen header
    BUFFER_RESERVE(VARHDRSZ);
    bstate->a_offset += VARHDRSZ;

    // write container header
    BUFFER_RESERVE(sizeof(uint32));
    BUFFER_WRITE_CONST(bstate->a_offset, uint32, header_flag | size);
    bstate->a_offset += sizeof(uint32);

    // reserve for agtentry headers
    if ((header_flag & AGT_FOBJECT) != 0)
    {
        agtentry_count = size * 2;
    }
    else if ((header_flag & AGT_FARRAY) != 0)
    {
        agtentry_count = size;
    }
    else
    {
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("Invalid container type.")));
    }

    agtentry_len = sizeof(agtentry) * agtentry_count;
    BUFFER_RESERVE(agtentry_len);
    bstate->d_start = bstate->a_offset + agtentry_len;

    return bstate;
}

agtype *build_agtype(agtype_build_state *bstate)
{
    agtype *result = (agtype *) bstate->buffer->data;
    SET_VARSIZE(result, bstate->buffer->len);
    return result;
}

void pfree_agtype_build_state(agtype_build_state *bstate)
{
    /*
     * bstate->buffer->data is not pfree'd because this pointer
     * is returned by the `build_agtype` function.
     */
    pfree(bstate->buffer);
    pfree(bstate);
}

void write_string(agtype_build_state *bstate, char *str)
{
    int length = strlen(str);

    write_ptr(str, length);
    write_agt(AGTENTRY_IS_STRING | length);

    bstate->i++;
}

void write_graphid(agtype_build_state *bstate, graphid graphid)
{
    int length = 0;

    // padding
    length += BUFFER_WRITE_PAD();

    // graphid header
    write_const(AGT_HEADER_INTEGER, AGT_HEADER_TYPE);
    length += AGT_HEADER_SIZE;

    // graphid value
    write_const(graphid, int64);
    length += sizeof(int64);

    // agtentry
    write_agt(AGTENTRY_IS_AGTYPE | length);

    bstate->i++;
}

void write_container(agtype_build_state *bstate, agtype *agtype)
{
    int length = 0;

    // padding
    length += BUFFER_WRITE_PAD();

    // varlen data
    length += write_ptr((char *) &agtype->root, VARSIZE(agtype));

    // agtentry
    write_agt(AGTENTRY_IS_CONTAINER | length);

    bstate->i++;
}

/*
 * `val` = container of the extended type
 * `header` = AGT_HEADER_VERTEX, AGT_HEADER_EDGE or AGT_HEADER_PATH
 */
void write_extended(agtype_build_state *bstate, agtype *val, uint32 header)
{
    int length = 0;

    // padding
    length += BUFFER_WRITE_PAD();

    // vertex header
    write_const(header, AGT_HEADER_TYPE);
    length += AGT_HEADER_SIZE;

    // vertex data
    length += write_ptr((char *) &val->root, VARSIZE(val));

    // agtentry
    write_agt(AGTENTRY_IS_AGTYPE | length);

    bstate->i++;
}
