/*-------------------------------------------------------------------------
 *
 * tsquery_op.c
 *	  Various operations with tsquery
 *
 * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/tsquery_op.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "tsearch/ts_utils.h"
#include "utils/builtins.h"

Datum
tsquery_numnode(PG_FUNCTION_ARGS)
{
	TSQuery		query = PG_GETARG_TSQUERY(0);
	int			nnode = query->size;

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

static QTNode *
join_tsqueries(TSQuery a, TSQuery b, int8 operator, uint16 distance)
{
	QTNode	   *res = (QTNode *) palloc0(sizeof(QTNode));

	res->flags |= QTN_NEEDFREE;

	res->valnode = (QueryItem *) palloc0(sizeof(QueryItem));
	res->valnode->type = QI_OPR;
	res->valnode->qoperator.oper = operator;
	if (operator == OP_PHRASE)
		res->valnode->qoperator.distance = distance;

	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;
}

Datum
tsquery_and(PG_FUNCTION_ARGS)
{
	TSQuery		a = PG_GETARG_TSQUERY_COPY(0);
	TSQuery		b = PG_GETARG_TSQUERY_COPY(1);
	QTNode	   *res;
	TSQuery		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, OP_AND, 0);

	query = QTN2QT(res);

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

	PG_RETURN_TSQUERY(query);
}

Datum
tsquery_or(PG_FUNCTION_ARGS)
{
	TSQuery		a = PG_GETARG_TSQUERY_COPY(0);
	TSQuery		b = PG_GETARG_TSQUERY_COPY(1);
	QTNode	   *res;
	TSQuery		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, OP_OR, 0);

	query = QTN2QT(res);

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

	PG_RETURN_TSQUERY(query);
}

Datum
tsquery_phrase_distance(PG_FUNCTION_ARGS)
{
	TSQuery		a = PG_GETARG_TSQUERY_COPY(0);
	TSQuery		b = PG_GETARG_TSQUERY_COPY(1);
	QTNode	   *res;
	TSQuery		query;
	int32		distance = PG_GETARG_INT32(2);

	if (distance < 0 || distance > MAXENTRYPOS)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("distance in phrase operator should be non-negative and less than %d",
						MAXENTRYPOS)));
	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, OP_PHRASE, (uint16) distance);

	query = QTN2QT(res);

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

	PG_RETURN_TSQUERY(query);
}

Datum
tsquery_phrase(PG_FUNCTION_ARGS)
{
	PG_RETURN_POINTER(DirectFunctionCall3(
										  tsquery_phrase_distance,
										  PG_GETARG_DATUM(0),
										  PG_GETARG_DATUM(1),
										  Int32GetDatum(1)));
}

Datum
tsquery_not(PG_FUNCTION_ARGS)
{
	TSQuery		a = PG_GETARG_TSQUERY_COPY(0);
	QTNode	   *res;
	TSQuery		query;

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

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

	res->flags |= QTN_NEEDFREE;

	res->valnode = (QueryItem *) palloc0(sizeof(QueryItem));
	res->valnode->type = QI_OPR;
	res->valnode->qoperator.oper = OP_NOT;

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

	query = QTN2QT(res);

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

	PG_RETURN_POINTER(query);
}

static int
CompareTSQ(TSQuery a, TSQuery 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 if (a->size != 0)
	{
		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;
}

Datum
tsquery_cmp(PG_FUNCTION_ARGS)
{
	TSQuery		a = PG_GETARG_TSQUERY_COPY(0);
	TSQuery		b = PG_GETARG_TSQUERY_COPY(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, CONDITION )				\
Datum											\
NAME(PG_FUNCTION_ARGS) {						\
	TSQuery  a = PG_GETARG_TSQUERY_COPY(0);		\
	TSQuery  b = PG_GETARG_TSQUERY_COPY(1);		\
	int res = CompareTSQ(a,b);					\
												\
	PG_FREE_IF_COPY(a,0);						\
	PG_FREE_IF_COPY(b,1);						\
												\
	PG_RETURN_BOOL( CONDITION );				\
}	\
/* keep compiler quiet - no extra ; */			\
extern int no_such_variable

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);

TSQuerySign
makeTSQuerySign(TSQuery a)
{
	int			i;
	QueryItem  *ptr = GETQUERY(a);
	TSQuerySign sign = 0;

	for (i = 0; i < a->size; i++)
	{
		if (ptr->type == QI_VAL)
			sign |= ((TSQuerySign) 1) << (((unsigned int) ptr->qoperand.valcrc) % TSQS_SIGLEN);
		ptr++;
	}

	return sign;
}

static char **
collectTSQueryValues(TSQuery a, int *nvalues_p)
{
	QueryItem  *ptr = GETQUERY(a);
	char	   *operand = GETOPERAND(a);
	char	  **values;
	int			nvalues = 0;
	int			i;

	values = (char **) palloc(sizeof(char *) * a->size);

	for (i = 0; i < a->size; i++)
	{
		if (ptr->type == QI_VAL)
		{
			int			len = ptr->qoperand.length;
			char	   *val;

			val = palloc(len + 1);
			memcpy(val, operand + ptr->qoperand.distance, len);
			val[len] = '\0';

			values[nvalues++] = val;
		}
		ptr++;
	}

	*nvalues_p = nvalues;
	return values;
}

static int
cmp_string(const void *a, const void *b)
{
	const char *sa = *((const char **) a);
	const char *sb = *((const char **) b);

	return strcmp(sa, sb);
}

static int
remove_duplicates(char **strings, int n)
{
	if (n <= 1)
		return n;
	else
	{
		int			i;
		char	   *prev = strings[0];
		int			new_n = 1;

		for (i = 1; i < n; i++)
		{
			if (strcmp(strings[i], prev) != 0)
			{
				strings[new_n++] = strings[i];
				prev = strings[i];
			}
		}
		return new_n;
	}
}

Datum
tsq_mcontains(PG_FUNCTION_ARGS)
{
	TSQuery		query = PG_GETARG_TSQUERY(0);
	TSQuery		ex = PG_GETARG_TSQUERY(1);
	char	  **query_values;
	int			query_nvalues;
	char	  **ex_values;
	int			ex_nvalues;
	bool		result = true;

	/* Extract the query terms into arrays */
	query_values = collectTSQueryValues(query, &query_nvalues);
	ex_values = collectTSQueryValues(ex, &ex_nvalues);

	/* Sort and remove duplicates from both arrays */
	qsort(query_values, query_nvalues, sizeof(char *), cmp_string);
	query_nvalues = remove_duplicates(query_values, query_nvalues);
	qsort(ex_values, ex_nvalues, sizeof(char *), cmp_string);
	ex_nvalues = remove_duplicates(ex_values, ex_nvalues);

	if (ex_nvalues > query_nvalues)
		result = false;
	else
	{
		int			i;
		int			j = 0;

		for (i = 0; i < ex_nvalues; i++)
		{
			for (; j < query_nvalues; j++)
			{
				if (strcmp(ex_values[i], query_values[j]) == 0)
					break;
			}
			if (j == query_nvalues)
			{
				result = false;
				break;
			}
		}
	}

	PG_RETURN_BOOL(result);
}

Datum
tsq_mcontained(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(
					DirectFunctionCall2(
										tsq_mcontains,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)
										)
		);
}
