/*
 * 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 "nodes/readfuncs.h"

#include "nodes/cypher_readfuncs.h"
#include "nodes/cypher_nodes.h"

static char *nullable_string(const char *token, int length);
/*
 * Copied From Postgres
 *
 * Macros for declaring appropriate local variables.
 */
/* A few guys need only local_node */
#define READ_LOCALS_NO_FIELDS(nodeTypeName) \
    nodeTypeName *local_node = (nodeTypeName *) node

/* And a few guys need only the pg_strtok support fields */
#define READ_TEMP_LOCALS() \
    const char *token; \
    int length

/* ... but most need both */
#define READ_LOCALS(nodeTypeName) \
    READ_LOCALS_NO_FIELDS(nodeTypeName); \
    READ_TEMP_LOCALS()

/*
 * The READ_*_FIELD defines first skips the :fldname token (key) part of the string
 * and then converts the next token (value) to the correct data type.
 *
 * pg_strtok will split the passed string by whitespace, skipping whitespace in
 * strings. We do not setup pg_strtok. That is for the caller to do. By default
 * that is the responsibility of Postgres' nodeRead function. We assume that was setup
 * correctly.
 */

/* Read an integer field (anything written as ":fldname %d") */
#define READ_INT_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        local_node->fldname = strtol(token, 0, 10)

/* Read an unsigned integer field (anything written as ":fldname %u") */
#define READ_UINT_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        local_node->fldname = atoui(token)
/* Read an unsigned integer field (anything written using UINT64_FORMAT) */
#define READ_UINT64_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        local_node->fldname = pg_strtouint64(token, NULL, 10)

/* Read a long integer field (anything written as ":fldname %ld") */
#define READ_LONG_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        local_node->fldname = atol(token)

/* Read an OID field (don't hard-wire assumption that OID is same as uint) */
#define READ_OID_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        local_node->fldname = atooid(token)

/* Read a char field (ie, one ascii character) */
#define READ_CHAR_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        /* avoid overhead of calling debackslash() for one char */ \
        local_node->fldname = (length == 0) ? '\0' : (token[0] == '\\' ? token[1] : token[0])

/* Read an enumerated-type field that was written as an integer code */
#define READ_ENUM_FIELD(fldname, enumtype) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        local_node->fldname = (enumtype) strtol(token, 0, 10)

/* Read a float field */
#define READ_FLOAT_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        local_node->fldname = atof(token)

/* Read a boolean field */
#define READ_BOOL_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        local_node->fldname = strtobool(token)

/* Read a character-string field */
#define READ_STRING_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        local_node->fldname = nullable_string(token, length)

/* Read a parse location field (and throw away the value, per notes above) */
#define READ_LOCATION_FIELD(fldname) \
        token = pg_strtok(&length); \
        token = pg_strtok(&length); \
        (void) token; \
        local_node->fldname = -1

/* Read a Node field */
#define READ_NODE_FIELD(fldname) \
        token = pg_strtok(&length);  \
        (void) token; \
        local_node->fldname = nodeRead(NULL, 0)

/* Read a bitmapset field */
#define READ_BITMAPSET_FIELD(fldname) \
        token = pg_strtok(&length); \
        (void) token; \
        local_node->fldname = _readBitmapset()

/* Read an attribute number array */
#define READ_ATTRNUMBER_ARRAY(fldname, len) \
        token = pg_strtok(&length); \
        local_node->fldname = readAttrNumberCols(len);

/* Read an oid array */
#define READ_OID_ARRAY(fldname, len) \
        token = pg_strtok(&length); \
        local_node->fldname = readOidCols(len);

/* Read an int array */
#define READ_INT_ARRAY(fldname, len) \
        token = pg_strtok(&length); \
        local_node->fldname = readIntCols(len);

/* Read a bool array */
#define READ_BOOL_ARRAY(fldname, len) \
        token = pg_strtok(&length); \
        local_node->fldname = readBoolCols(len);

/*
 * NOTE: use atoi() to read values written with %d, or atoui() to read
 * values written with %u in outfuncs.c.  An exception is OID values,
 * for which use atooid().  (As of 7.1, outfuncs.c writes OIDs as %u,
 * but this will probably change in the future.)
 */
#define atoui(x)  ((unsigned int) strtoul((x), NULL, 10))

#define strtobool(x)  ((*(x) == 't') ? true : false)

/* copied from PG16 function of the same name for consistency */
static char *nullable_string(const char *token, int length)
{
    /* outToken emits <> for NULL, and pg_strtok makes that an empty string */
    if (length == 0)
    {
        return NULL;
    }
    /* outToken emits "" for empty string */
    if (length == 2 && token[0] == '"' && token[1] == '"')
    {
        return pstrdup("");
    }
    /* otherwise, we must remove protective backslashes added by outToken */
    return debackslash(token, length);
}

/*
 * Default read function for cypher nodes. For most nodes, we don't expect
 * the node to ever be read (deserialized). So throw an error.
 */
void read_ag_node(ExtensibleNode *node)
{
    ereport(ERROR, (errmsg("unexpected parseNodeString() for ag_node")));
}

/*
 * Deserialize a string representing the cypher_create_target_nodes
 * data structure.
 */
void read_cypher_create_target_nodes(struct ExtensibleNode *node)
{
    READ_LOCALS(cypher_create_target_nodes);

    READ_NODE_FIELD(paths);
    READ_UINT_FIELD(flags);
    READ_UINT_FIELD(graph_oid);
}

/*
 * Deserialize a string representing the cypher_create_path
 * data structure.
 */
void read_cypher_create_path(struct ExtensibleNode *node)
{
    READ_LOCALS(cypher_create_path);

    READ_NODE_FIELD(target_nodes);
    READ_INT_FIELD(path_attr_num);
    READ_STRING_FIELD(var_name);
}

/*
 * Deserialize a string representing the cypher_target_node
 * data structure.
 */
void read_cypher_target_node(struct ExtensibleNode *node)
{
    READ_LOCALS(cypher_target_node);

    READ_CHAR_FIELD(type);
    READ_UINT_FIELD(flags);
    READ_ENUM_FIELD(dir, cypher_rel_dir);
    READ_NODE_FIELD(id_expr);
    READ_NODE_FIELD(id_expr_state);
    READ_NODE_FIELD(prop_expr);
    READ_NODE_FIELD(prop_expr_state);
    READ_INT_FIELD(prop_attr_num);
    READ_NODE_FIELD(resultRelInfo);
    READ_NODE_FIELD(elemTupleSlot);
    READ_OID_FIELD(relid);
    READ_STRING_FIELD(label_name);
    READ_STRING_FIELD(variable_name);
    READ_INT_FIELD(tuple_position);
}

/*
 * Deserialize a string representing the cypher_update_information
 * data structure.
 */
void read_cypher_update_information(struct ExtensibleNode *node)
{
    READ_LOCALS(cypher_update_information);

    READ_NODE_FIELD(set_items);
    READ_UINT_FIELD(flags);
    READ_INT_FIELD(tuple_position);
    READ_STRING_FIELD(graph_name);
    READ_STRING_FIELD(clause_name);
}

/*
 * Deserialize a string representing the cypher_update_item
 * data structure.
 */
void read_cypher_update_item(struct ExtensibleNode *node)
{
    READ_LOCALS(cypher_update_item);

    READ_INT_FIELD(prop_position);
    READ_INT_FIELD(entity_position);
    READ_STRING_FIELD(var_name);
    READ_STRING_FIELD(prop_name);
    READ_NODE_FIELD(qualified_name);
    READ_BOOL_FIELD(remove_item);
    READ_BOOL_FIELD(is_add);
}

/*
 * Deserialize a string representing the cypher_delete_information
 * data structure.
 */
void read_cypher_delete_information(struct ExtensibleNode *node)
{
    READ_LOCALS(cypher_delete_information);

    READ_NODE_FIELD(delete_items);
    READ_UINT_FIELD(flags);
    READ_STRING_FIELD(graph_name);
    READ_UINT_FIELD(graph_oid);
    READ_BOOL_FIELD(detach);
}

/*
 * Deserialize a string representing the cypher_delete_item
 * data structure.
 */
void read_cypher_delete_item(struct ExtensibleNode *node)
{
    READ_LOCALS(cypher_delete_item);

    READ_NODE_FIELD(entity_position);
    READ_STRING_FIELD(var_name);
}

/*
 * Deserialize a string representing the cypher_merge_information
 * data structure.
 */
void read_cypher_merge_information(struct ExtensibleNode *node)
{
    READ_LOCALS(cypher_merge_information);

    READ_UINT_FIELD(flags);
    READ_UINT_FIELD(graph_oid);
    READ_INT_FIELD(merge_function_attr);
    READ_NODE_FIELD(path);
}
