/*
 * integrate swish-e into PostgreSQL
 *
 * Dobrica Pavlinusic <dpavlin@rot13.org> 2005-02-18
 *
 * TODO:
 * - check null input using PG_ARGISNULL before using PG_GETARG_xxxx
 * - support composite type arguments
 *
 * NOTES:
 * - clear structures with memset to support hash indexes (who whould like
 *   to create hash index on table returned from function?)
 * - number of returned rows is set by PostgreSQL evaluator, see:
 *   http://archives.postgresql.org/pgsql-hackers/2005-02/msg00546.php
 *
 * Based on:
 * - C example from PostgreSQL documentation (BSD licence)
 * - swish-e example src/libtest.c (GPL)
 * - _textin/_textout from pgcurl.c (LGPL)
 *
 * This code is licenced under GPL
 */

#include "postgres.h"
#include "fmgr.h"
#include "funcapi.h"
#include "utils/builtins.h"
#include "utils/array.h"
#include "miscadmin.h"
#include <swish-e.h>

#define _textin(str) DirectFunctionCall1(textin, CStringGetDatum(str))
#define _textout(str) DatumGetPointer(DirectFunctionCall1(textout, PointerGetDatum(str)))
#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))
#define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(cstrp)))


SW_HANDLE   swish_handle = NULL;/* Database handle */
SW_SEARCH   search = NULL;	/* search handle -- holds search parameters */
SW_RESULTS  swish_results = NULL;	/* results handle -- holds list of results */
SW_RESULT   *sw_res = NULL;	/* one row from swish-e results */

/* define PostgreSQL v1 function */
PG_FUNCTION_INFO_V1(pgswish);
Datum pgswish(PG_FUNCTION_ARGS) {

	FuncCallContext	*funcctx;
	int		call_cntr;
	int		max_calls;
	TupleDesc	tupdesc;
	TupleTableSlot	*slot;
	AttInMetadata	*attinmeta;
	char		*index_path;
	char		*query;

	/* stuff done only on the first call of the function */
	if (SRF_IS_FIRSTCALL()) {
		MemoryContext	oldcontext;

		/* take arguments from function */
		//index_path = _textout(PG_GETARG_TEXT_P(0));
		index_path = _textout(PG_GETARG_TEXT_P(0));
		query = _textout(PG_GETARG_TEXT_P(1));

		/* create a function context for cross-call persistence */
		funcctx = SRF_FIRSTCALL_INIT();

		/* switch to memory context appropriate for multiple function calls */
		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

		
		/* Send any errors or warnings to stderr (default is stdout) */
		SwishErrorsToStderr();

		elog(INFO, "pgswish: SwishInit(%s)", index_path);
		
		swish_handle = SwishInit( index_path );

		if (! swish_handle) {
			elog(ERROR, "pgswish: can't open %s", index_path);
			SRF_RETURN_DONE(funcctx);
		}
		
		error_or_abort( swish_handle );
		/* set ranking scheme. default is 0 */
		SwishRankScheme( swish_handle, 0 );
		error_or_abort( swish_handle );

		elog(INFO, "pgswish: SwishQuery(%s)", query);
		/* Here's a short-cut to searching that creates a search object and searches at the same time */
		swish_results = SwishQuery( swish_handle, query);
		error_or_abort( swish_handle );

		/* total number of tuples to be returned */
		funcctx->max_calls = SwishHits( swish_results );

		/* check if results exists */
		if ( 0 == funcctx->max_calls )
			elog(INFO, "no results for: %s", query );

		elog(INFO, "pgswish: SwishHits = %d", funcctx->max_calls);

		/* Build a tuple description for a __pgswish tuple */
		tupdesc = RelationNameGetTupleDesc("__pgswish");

		/* allocate a slot for a tuple with this tupdesc */
		slot = TupleDescGetSlot(tupdesc);

		/* assign slot to function context */
		funcctx->slot = slot;

		/*
		 * generate attribute metadata needed later to produce tuples from raw
		 * C strings
		 */
		attinmeta = TupleDescGetAttInMetadata(tupdesc);
		funcctx->attinmeta = attinmeta;

		MemoryContextSwitchTo(oldcontext);

		elog(INFO, "SRF_IS_FIRSTCALL done");
	}

	/* stuff done on every call of the function */
	funcctx = SRF_PERCALL_SETUP();

	call_cntr = funcctx->call_cntr;
	max_calls = funcctx->max_calls;
	slot = funcctx->slot;
	attinmeta = funcctx->attinmeta;
 
	if (call_cntr < max_calls) {
		char		**values;
		HeapTuple	tuple;
		Datum		result;

		elog(INFO, "pgswish: loop count %d", call_cntr);

		if (! swish_results) {
			elog(ERROR, "pgswish: no swish-e results");
			SRF_RETURN_DONE(funcctx);
		}
		
		elog(DEBUG1, "pgswish: check for swish-e error");
		error_or_abort( swish_handle );

		/*
		 * Prepare a values array for storage in our slot.
		 * This should be an array of C strings which will
		 * be processed later by the type input functions.
		 */

		sw_res = SwishNextResult( swish_results );
		if (! sw_res) {
			elog(ERROR, "pgswish: swish-e sort result list: %d rows expected %d", call_cntr, max_calls - 1);
			SRF_RETURN_DONE(funcctx);
		}
		
		elog(INFO, "Path: %s\n  Rank: %lu\n  Size: %lu\n  Title: %s\n  Index: %s\n  Modified: %s\n  Record #: %lu\n  File   #: %lu\n\n",
			SwishResultPropertyStr   ( sw_res, "swishdocpath" ),
			SwishResultPropertyULong ( sw_res, "swishrank" ),
			SwishResultPropertyULong ( sw_res, "swishdocsize" ),
			SwishResultPropertyStr   ( sw_res, "swishtitle"),
			SwishResultPropertyStr   ( sw_res, "swishdbfile" ),
			SwishResultPropertyStr   ( sw_res, "swishlastmodified" ),
			SwishResultPropertyULong ( sw_res, "swishreccount" ),  /* can figure this out in loop, of course */
			SwishResultPropertyULong ( sw_res, "swishfilenum" )
		);

		values = (char **) palloc(4 * sizeof(char *));

		values[0] = prop2int( sw_res, "swishrank" );
		values[1] = prop2text( sw_res, "swishdocpath" );
		values[2] = prop2text( sw_res, "swishtitle" );
		values[3] = prop2int( sw_res, "swishdocsize" );

/*
		values[0] = (char *) palloc(16 * sizeof(char));
		snprintf(values[0], 16, "%d", 1);
		values[1] = (char *) palloc(16 * sizeof(char));
		snprintf(values[1], 16, "%d", 2);
		values[2] = (char *) palloc(16 * sizeof(char));
		snprintf(values[2], 16, "%d", 3);
		values[3] = (char *) palloc(16 * sizeof(char));
		snprintf(values[3], 16, "%d", 4);
*/

		/* build a tuple */
		tuple = BuildTupleFromCStrings(attinmeta, values);

		/* make the tuple into a datum */
		result = TupleGetDatum(slot, tuple);

		/* clean up ? */
		pfree(values[0]);
		pfree(values[1]);
		pfree(values[2]);
		pfree(values[3]);
		pfree(values);
		
		elog(INFO, "row: %s|%s|%s|%s",values[0],values[1],values[2],values[3]);
	
		SRF_RETURN_NEXT(funcctx, result);
	} else {
		elog(INFO, "loop over");

		/* free swish object and close */
		Free_Search_Object( search );
		SwishClose( swish_handle );

		/* do when there is no more left */
		SRF_RETURN_DONE(funcctx);
	}
}

/* work in progress */
PG_FUNCTION_INFO_V1(pgswish2);
Datum pgswish2(PG_FUNCTION_ARGS)
{
	int		ncols = 2;
	int		nrows = 3;
	int16		typlen;
	bool		typbyval;
	char		typalign;
	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	AttInMetadata	*attinmeta;
	TupleDesc	tupdesc;
	Tuplestorestate	*tupstore = NULL;
	HeapTuple	tuple;
	MemoryContext	per_query_ctx;
	MemoryContext	oldcontext;
	Datum		dvalue;
	char		**values;
	int		rsinfo_ncols;
	int		i, j;

	/* check to see if caller supports us returning a tuplestore */
	if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize))
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("materialize mode required, but it is not " \
						"allowed in this context")));

	/* get the requested return tuple description */
	tupdesc = rsinfo->expectedDesc;
	rsinfo_ncols = tupdesc->natts;

	/*
	 * The requested tuple description better match up with the array
	 * we were given.
	 */
	elog(INFO, "rsinfo_ncols = %d, ncols = %d", rsinfo_ncols, ncols);

	/* OK, use it */
	attinmeta = TupleDescGetAttInMetadata(tupdesc);

	/* Now go to work */
	rsinfo->returnMode = SFRM_Materialize;

	per_query_ctx = fcinfo->flinfo->fn_mcxt;
	oldcontext = MemoryContextSwitchTo(per_query_ctx);

	/* initialize our tuplestore */
	tupstore = tuplestore_begin_heap(true, false, SortMem);

	values = (char **) palloc(ncols * sizeof(char *));

	for (i = 0; i < nrows; i++)
	{
		for (j = 0; j < ncols; j++)
		{
			values[j] = DatumGetCString( "foo" );
		}
		/* construct the tuple */
		tuple = BuildTupleFromCStrings(attinmeta, values);

		/* now store it */
		tuplestore_puttuple(tupstore, tuple);
	}

	tuplestore_donestoring(tupstore);
	rsinfo->setResult = tupstore;

	/*
	 * SFRM_Materialize mode expects us to return a NULL Datum. The actual
	 * tuples are in our tuplestore and passed back through
	 * rsinfo->setResult. rsinfo->setDesc is set to the tuple description
	 * that we actually used to build our tuples with, so the caller can
	 * verify we did what it was expecting.
	 */
	rsinfo->setDesc = tupdesc;
	MemoryContextSwitchTo(oldcontext);

	return (Datum) 0;
}


/* make text var prom property */
char *prop2text(SW_RESULT sw_res, char *propname) {
	char *val;
	char *prop;
	int len;

	elog(DEBUG1, "prop2text(%s)", propname);

	prop = SwishResultPropertyStr( sw_res, propname );
	error_or_abort( swish_handle );

	len = strlen(prop);
	elog(INFO, "prop2text(%s) = '%s' %d bytes", propname, prop, len);

	len++;
	len *= sizeof(char);

	elog(DEBUG1, "palloc(%d)", len);

	val = palloc(len);

	memset(val, 0, len);
	strncpy(val, prop, len);

	elog(DEBUG1, "val=%s", val);

	return val;
}

/* make integer variable from property */
char *prop2int(SW_RESULT sw_res, char *propname) {
	char *val;
	unsigned long prop;
	int len;

	elog(DEBUG1, "prop2int(%s)", propname);

	prop = SwishResultPropertyULong( sw_res, propname );
	error_or_abort( swish_handle );

	elog(INFO, "prop2int(%s) = %lu", propname, prop);

	len = 128 * sizeof(char);
	elog(DEBUG1, "palloc(%d)", len);

	val = palloc(len);
	memset(val, 0, len);

	snprintf(val, len, "%lu", prop);

	elog(DEBUG1, "val=%s", val);

	return val;
}


/*
 * check if swish has returned error, and elog it.
 */
static void error_or_abort( SW_HANDLE swish_handle ) {
	if ( !SwishError( swish_handle ) )
		return;

	/* print a message */
	elog(ERROR, 
		"pgswish error: Number [%d], Type [%s],  Optional Message: [%s]\n",
			SwishError( swish_handle ),
			SwishErrorString( swish_handle ),
			SwishLastErrorMsg( swish_handle )
	);
	if ( search ) Free_Search_Object( search );
	SwishClose( swish_handle );
}

