#include "pg_query_readfuncs.h"

#include "nodes/nodes.h"
#include "nodes/parsenodes.h"
#include "nodes/pg_list.h"

#include "protobuf/pg_query.pb-c.h"

#define OUT_TYPE(typename, typename_c) PgQuery__##typename_c*

#define READ_COND(typename, typename_c, typename_underscore, typename_underscore_upcase, typename_cast, outname) \
	case PG_QUERY__NODE__NODE_##typename_underscore_upcase: \
		return (Node *) _read##typename_c(msg->outname);

#define READ_INT_FIELD(outname, outname_json, fldname) node->fldname = msg->outname;
#define READ_UINT_FIELD(outname, outname_json, fldname) node->fldname = msg->outname;
#define READ_UINT64_FIELD(outname, outname_json, fldname) node->fldname = msg->outname;
#define READ_LONG_FIELD(outname, outname_json, fldname) node->fldname = msg->outname;
#define READ_FLOAT_FIELD(outname, outname_json, fldname) node->fldname = msg->outname;
#define READ_BOOL_FIELD(outname, outname_json, fldname) node->fldname = msg->outname;

#define READ_CHAR_FIELD(outname, outname_json, fldname) \
	if (msg->outname != NULL && strlen(msg->outname) > 0) { \
		node->fldname = msg->outname[0]; \
	}

#define READ_STRING_FIELD(outname, outname_json, fldname) \
	if (msg->outname != NULL && strlen(msg->outname) > 0) { \
		node->fldname = pstrdup(msg->outname); \
	}

#define READ_ENUM_FIELD(typename, outname, outname_json, fldname) \
	node->fldname = _intToEnum##typename(msg->outname);

#define READ_LIST_FIELD(outname, outname_json, fldname) \
	{ \
		if (msg->n_##outname > 0) \
			node->fldname = list_make1(_readNode(msg->outname[0])); \
	    for (int i = 1; i < msg->n_##outname; i++) \
			node->fldname = lappend(node->fldname, _readNode(msg->outname[i])); \
	}

#define READ_BITMAPSET_FIELD(outname, outname_json, fldname) // FIXME

#define READ_NODE_FIELD(outname, outname_json, fldname) \
	node->fldname = *_readNode(msg->outname);

#define READ_NODE_PTR_FIELD(outname, outname_json, fldname) \
	if (msg->outname != NULL) { \
		node->fldname = _readNode(msg->outname); \
	}

#define READ_ABSTRACT_PTR_FIELD(outname, outname_json, fldname, fldtype) \
	if (msg->outname != NULL) { \
		node->fldname = (fldtype) _readNode(msg->outname); \
	}

#define READ_VALUE_FIELD(outname, outname_json, fldname) \
	if (msg->outname != NULL) { \
		node->fldname = *((Value *) _readNode(msg->outname)); \
	}

#define READ_VALUE_PTR_FIELD(outname, outname_json, fldname) \
	if (msg->outname != NULL) { \
		node->fldname = (Value *) _readNode(msg->outname); \
	}

#define READ_SPECIFIC_NODE_FIELD(typename, typename_underscore, outname, outname_json, fldname) \
	node->fldname = *_read##typename(msg->outname);

#define READ_SPECIFIC_NODE_PTR_FIELD(typename, typename_underscore, outname, outname_json, fldname) \
	if (msg->outname != NULL) { \
		node->fldname = _read##typename(msg->outname); \
	}

static Node * _readNode(PgQuery__Node *msg);

static String *
_readString(PgQuery__String* msg)
{
	return makeString(pstrdup(msg->sval));
}

#include "pg_query_enum_defs.c"
#include "pg_query_readfuncs_defs.c"

static List * _readList(PgQuery__List *msg)
{
	List *node = NULL;
	if (msg->n_items > 0)
		node = list_make1(_readNode(msg->items[0]));
	for (int i = 1; i < msg->n_items; i++)
		node = lappend(node, _readNode(msg->items[i]));
	return node;
}

static Node * _readNode(PgQuery__Node *msg)
{
	switch (msg->node_case)
	{
		#include "pg_query_readfuncs_conds.c"

		case PG_QUERY__NODE__NODE_INTEGER:
			return (Node *) makeInteger(msg->integer->ival);
		case PG_QUERY__NODE__NODE_FLOAT:
			return (Node *) makeFloat(pstrdup(msg->float_->fval));
		case PG_QUERY__NODE__NODE_BOOLEAN:
			return (Node *) makeBoolean(msg->boolean->boolval);
		case PG_QUERY__NODE__NODE_STRING:
			return (Node *) makeString(pstrdup(msg->string->sval));
		case PG_QUERY__NODE__NODE_BIT_STRING:
			return (Node *) makeBitString(pstrdup(msg->bit_string->bsval));
		case PG_QUERY__NODE__NODE_A_CONST: {
			A_Const *ac = makeNode(A_Const);
			ac->location = msg->a_const->location;

			if (msg->a_const->isnull) {
				ac->isnull = true;
			} else {
				switch (msg->a_const->val_case) {
					case PG_QUERY__A__CONST__VAL_IVAL:
						ac->val.ival = *makeInteger(msg->a_const->ival->ival);
						break;
					case PG_QUERY__A__CONST__VAL_FVAL:
						ac->val.fval = *makeFloat(pstrdup(msg->a_const->fval->fval));
						break;
					case PG_QUERY__A__CONST__VAL_BOOLVAL:
						ac->val.boolval = *makeBoolean(msg->a_const->boolval->boolval);
						break;
					case PG_QUERY__A__CONST__VAL_SVAL:
						ac->val.sval = *makeString(pstrdup(msg->a_const->sval->sval));
						break;
					case PG_QUERY__A__CONST__VAL_BSVAL:
						ac->val.bsval = *makeBitString(pstrdup(msg->a_const->bsval->bsval));
						break;
					case PG_QUERY__A__CONST__VAL__NOT_SET:
					case _PG_QUERY__A__CONST__VAL__CASE_IS_INT_SIZE:
						Assert(false);
						break;
				}
			}

			return (Node *) ac;
		}
		case PG_QUERY__NODE__NODE_LIST:
			return (Node *) _readList(msg->list);
		case PG_QUERY__NODE__NODE__NOT_SET:
			return NULL;
		default:
			elog(ERROR, "unsupported protobuf node type: %d",
				 (int) msg->node_case);
	}
}

List * pg_query_protobuf_to_nodes(PgQueryProtobuf protobuf)
{
	PgQuery__ParseResult *result = NULL;
	List * list = NULL;
	size_t i = 0;

	result = pg_query__parse_result__unpack(NULL, protobuf.len, (const uint8_t *) protobuf.data);

	// TODO: Handle this by returning an error instead
	Assert(result != NULL);

	// TODO: Handle this by returning an error instead
	Assert(result->version == PG_VERSION_NUM);

	if (result->n_stmts > 0)
		list = list_make1(_readRawStmt(result->stmts[0]));
    for (i = 1; i < result->n_stmts; i++)
		list = lappend(list, _readRawStmt(result->stmts[i]));

	pg_query__parse_result__free_unpacked(result, NULL);

	return list;
}
