#include "postgres.h"
#include "executor/spi.h"       /* this is what you need to work with SPI */
#include "commands/trigger.h"   /* ... and triggers */
#include "utils/lsyscache.h"	/* for get_namespace_name() */

/*#define PGIS_DEBUG 1*/

#define ABORT_ON_AUTH_FAILURE 1

Datum check_authorization(PG_FUNCTION_ARGS);

/*
 * This trigger will check for authorization before
 * allowing UPDATE or DELETE of specific rows.
 * Rows are identified by the provided column.
 * Authorization info is extracted by the
 * "authorization_table"
 *
 */
PG_FUNCTION_INFO_V1(check_authorization);
Datum check_authorization(PG_FUNCTION_ARGS)
{
	TriggerData *trigdata = (TriggerData *) fcinfo->context;
	char *colname;
	HeapTuple rettuple_ok;
	HeapTuple rettuple_fail;
	TupleDesc tupdesc;
	int SPIcode;
	char query[1024];
	const char *pk_id = NULL;
	SPITupleTable *tuptable;
	HeapTuple tuple;
	char *lockcode;
	char *authtable = "authorization_table";
	const char *op;
	char errmsg[256];


	/* Make sure trigdata is pointing at what I expect */
	if ( ! CALLED_AS_TRIGGER(fcinfo) )
	{
		elog(ERROR,"check_authorization: not fired by trigger manager");
	}

	if ( ! TRIGGER_FIRED_BEFORE(trigdata->tg_event) )
	{
		elog(ERROR,"check_authorization: not fired *before* event");
	}

	if ( TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event) )
	{
		rettuple_ok = trigdata->tg_newtuple;
		rettuple_fail = NULL;
		op = "UPDATE";
	}
	else if ( TRIGGER_FIRED_BY_DELETE(trigdata->tg_event) )
	{
		rettuple_ok = trigdata->tg_trigtuple;
		rettuple_fail = NULL;
		op = "DELETE";
	}
	else
	{
		elog(ERROR,"check_authorization: not fired by update or delete");
		PG_RETURN_NULL();
	}


	tupdesc = trigdata->tg_relation->rd_att;

	/* Connect to SPI manager */
	SPIcode = SPI_connect();

	if (SPIcode  != SPI_OK_CONNECT)
	{
		elog(ERROR,"check_authorization: could not connect to SPI");
		PG_RETURN_NULL() ;
	}

	colname  = trigdata->tg_trigger->tgargs[0];
	pk_id = SPI_getvalue(trigdata->tg_trigtuple, tupdesc,
	                     SPI_fnumber(tupdesc, colname));

#if PGIS_DEBUG
	elog(NOTICE,"check_authorization called");
#endif

	snprintf(query, sizeof(query), "SELECT authid FROM \"%s\" WHERE expires >= now() AND toid = '%d' AND rid = '%s'", authtable, trigdata->tg_relation->rd_id, pk_id);

#if PGIS_DEBUG > 1
	elog(NOTICE,"about to execute :%s", query);
#endif

	SPIcode = SPI_exec(query,0);
	if (SPIcode !=SPI_OK_SELECT )
		elog(ERROR,"couldnt execute to test for lock :%s",query);

	if (!SPI_processed )
	{
#if PGIS_DEBUG
		elog(NOTICE,"there is NO lock on row '%s'", pk_id);
#endif
		SPI_finish();
		return PointerGetDatum(rettuple_ok);
	}

	/* there is a lock - check to see if I have rights to it! */

	tuptable = SPI_tuptable;
	tupdesc = tuptable->tupdesc;
	tuple = tuptable->vals[0];
	lockcode = SPI_getvalue(tuple, tupdesc, 1);

#if PGIS_DEBUG
	elog(NOTICE, "there is a lock on row '%s' (auth: '%s').", pk_id, lockcode);
#endif

	/*
	 * check to see if temp_lock_have_table table exists
	 * (it might not exist if they own no locks)
	 */
	snprintf(query, sizeof(query), "SELECT * FROM pg_class WHERE relname = 'temp_lock_have_table'");
	SPIcode = SPI_exec(query,0);
	if (SPIcode != SPI_OK_SELECT )
		elog(ERROR,"couldnt execute to test for lockkey temp table :%s",query);
	if (SPI_processed==0)
	{
		goto fail;
	}

	snprintf(query, sizeof(query), "SELECT * FROM temp_lock_have_table WHERE xideq( transid, getTransactionID() ) AND lockcode ='%s'", lockcode);

#if PGIS_DEBUG
	elog(NOTICE,"about to execute :%s", query);
#endif

	SPIcode = SPI_exec(query,0);
	if (SPIcode != SPI_OK_SELECT )
		elog(ERROR, "couldnt execute to test for lock acquire: %s", query);

	if (SPI_processed >0)
	{
#if PGIS_DEBUG
		elog(NOTICE,"I own the lock - I can modify the row");
#endif
		SPI_finish();
		return PointerGetDatum(rettuple_ok);
	}

fail:

	snprintf(errmsg, sizeof(errmsg), "%s where \"%s\" = '%s' requires authorization '%s'",
	         op, colname, pk_id, lockcode);
	errmsg[ERRMSGLEN-1] = '\0';

#ifdef ABORT_ON_AUTH_FAILURE
	elog(ERROR, "%s", errmsg);
#else
	elog(NOTICE, "%s", errmsg);
#endif

	SPI_finish();
	return PointerGetDatum(rettuple_fail);


}

PG_FUNCTION_INFO_V1(getTransactionID);
Datum getTransactionID(PG_FUNCTION_ARGS)
{
	TransactionId xid = GetCurrentTransactionId();
	PG_RETURN_DATUM( TransactionIdGetDatum(xid) );
}
