#include "postgres.h"
#include "fmgr.h"

#include "query_util.h"

PG_FUNCTION_INFO_V1(tsquery_numnode);
Datum		tsquery_numnode(PG_FUNCTION_ARGS);

Datum
tsquery_numnode(PG_FUNCTION_ARGS)
{
	QUERYTYPE  *query = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)));
	int			nnode = query->size;

	PG_FREE_IF_COPY(query, 0);
	PG_RETURN_INT32(nnode);
}

static QTNode *
join_tsqueries(QUERYTYPE * a, QUERYTYPE * b)
{
	QTNode	   *res = (QTNode *) palloc0(sizeof(QTNode));

	res->flags |= QTN_NEEDFREE;

	res->valnode = (ITEM *) palloc0(sizeof(ITEM));
	res->valnode->type = OPR;

	res->child = (QTNode **) palloc0(sizeof(QTNode *) * 2);
	res->child[0] = QT2QTN(GETQUERY(b), GETOPERAND(b));
	res->child[1] = QT2QTN(GETQUERY(a), GETOPERAND(a));
	res->nchild = 2;

	return res;
}

PG_FUNCTION_INFO_V1(tsquery_and);
Datum		tsquery_and(PG_FUNCTION_ARGS);

Datum
tsquery_and(PG_FUNCTION_ARGS)
{
	QUERYTYPE  *a = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)));
	QUERYTYPE  *b = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(1)));
	QTNode	   *res;
	QUERYTYPE  *query;

	if (a->size == 0)
	{
		PG_FREE_IF_COPY(a, 1);
		PG_RETURN_POINTER(b);
	}
	else if (b->size == 0)
	{
		PG_FREE_IF_COPY(b, 1);
		PG_RETURN_POINTER(a);
	}

	res = join_tsqueries(a, b);

	res->valnode->val = '&';

	query = QTN2QT(res, PlainMemory);

	QTNFree(res);
	PG_FREE_IF_COPY(a, 0);
	PG_FREE_IF_COPY(b, 1);

	PG_RETURN_POINTER(query);
}

PG_FUNCTION_INFO_V1(tsquery_or);
Datum		tsquery_or(PG_FUNCTION_ARGS);

Datum
tsquery_or(PG_FUNCTION_ARGS)
{
	QUERYTYPE  *a = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)));
	QUERYTYPE  *b = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(1)));
	QTNode	   *res;
	QUERYTYPE  *query;

	if (a->size == 0)
	{
		PG_FREE_IF_COPY(a, 1);
		PG_RETURN_POINTER(b);
	}
	else if (b->size == 0)
	{
		PG_FREE_IF_COPY(b, 1);
		PG_RETURN_POINTER(a);
	}

	res = join_tsqueries(a, b);

	res->valnode->val = '|';

	query = QTN2QT(res, PlainMemory);

	QTNFree(res);
	PG_FREE_IF_COPY(a, 0);
	PG_FREE_IF_COPY(b, 1);

	PG_RETURN_POINTER(query);
}

PG_FUNCTION_INFO_V1(tsquery_not);
Datum		tsquery_not(PG_FUNCTION_ARGS);

Datum
tsquery_not(PG_FUNCTION_ARGS)
{
	QUERYTYPE  *a = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)));
	QTNode	   *res;
	QUERYTYPE  *query;

	if (a->size == 0)
		PG_RETURN_POINTER(a);

	res = (QTNode *) palloc0(sizeof(QTNode));

	res->flags |= QTN_NEEDFREE;

	res->valnode = (ITEM *) palloc0(sizeof(ITEM));
	res->valnode->type = OPR;
	res->valnode->val = '!';

	res->child = (QTNode **) palloc0(sizeof(QTNode *));
	res->child[0] = QT2QTN(GETQUERY(a), GETOPERAND(a));
	res->nchild = 1;

	query = QTN2QT(res, PlainMemory);

	QTNFree(res);
	PG_FREE_IF_COPY(a, 0);

	PG_RETURN_POINTER(query);
}

static int
CompareTSQ(QUERYTYPE * a, QUERYTYPE * b)
{
	if (a->size != b->size)
	{
		return (a->size < b->size) ? -1 : 1;
	}
	else if (VARSIZE(a) != VARSIZE(b))
	{
		return (VARSIZE(a) < VARSIZE(b)) ? -1 : 1;
	}
	else
	{
		QTNode	   *an = QT2QTN(GETQUERY(a), GETOPERAND(a));
		QTNode	   *bn = QT2QTN(GETQUERY(b), GETOPERAND(b));
		int			res = QTNodeCompare(an, bn);

		QTNFree(an);
		QTNFree(bn);

		return res;
	}

	return 0;
}

PG_FUNCTION_INFO_V1(tsquery_cmp);
\
Datum		tsquery_cmp(PG_FUNCTION_ARGS);

Datum
tsquery_cmp(PG_FUNCTION_ARGS)
{
	QUERYTYPE  *a = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)));
	QUERYTYPE  *b = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(1)));
	int			res = CompareTSQ(a, b);

	PG_FREE_IF_COPY(a, 0);
	PG_FREE_IF_COPY(b, 1);

	PG_RETURN_INT32(res);
}

#define CMPFUNC( NAME, ACTION )									\
Datum	NAME(PG_FUNCTION_ARGS);									\
													\
Datum													\
NAME(PG_FUNCTION_ARGS) {										\
	QUERYTYPE  *a = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)));	\
	QUERYTYPE  *b = (QUERYTYPE *) DatumGetPointer(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(1)));	\
	int res = CompareTSQ(a,b);									\
													\
	PG_FREE_IF_COPY(a,0);										\
	PG_FREE_IF_COPY(b,1);										\
													\
	PG_RETURN_BOOL( ACTION );									\
}													\
													\
PG_FUNCTION_INFO_V1(NAME)

CMPFUNC(tsquery_lt, res < 0);
CMPFUNC(tsquery_le, res <= 0);
CMPFUNC(tsquery_eq, res == 0);
CMPFUNC(tsquery_ge, res >= 0);
CMPFUNC(tsquery_gt, res > 0);
CMPFUNC(tsquery_ne, res != 0);