/*-------------------------------------------------------------------------
 *
 * ginget.c
 *	  fetch tuples from a GIN scan.
 *
 *
 * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			$PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.9 2007/11/15 21:14:31 momjian Exp $
 *-------------------------------------------------------------------------
 */

#include "postgres.h"
#include "access/gin.h"
#include "catalog/index.h"
#include "utils/memutils.h"

static bool
findItemInPage(Page page, ItemPointer item, OffsetNumber *off)
{
	OffsetNumber maxoff = GinPageGetOpaque(page)->maxoff;
	int			res;

	if (GinPageGetOpaque(page)->flags & GIN_DELETED)
		/* page was deleted by concurrent  vacuum */
		return false;

	if (*off > maxoff || *off == InvalidOffsetNumber)
		res = -1;
	else
		res = compareItemPointers(item, (ItemPointer) GinDataPageGetItem(page, *off));

	if (res == 0)
	{
		/* page isn't changed */
		return true;
	}
	else if (res > 0)
	{
		/*
		 * some items was added before our position, look further to find it
		 * or first greater
		 */

		(*off)++;
		for (; *off <= maxoff; (*off)++)
		{
			res = compareItemPointers(item, (ItemPointer) GinDataPageGetItem(page, *off));

			if (res == 0)
				return true;

			if (res < 0)
			{
				(*off)--;
				return true;
			}
		}
	}
	else
	{
		/*
		 * some items was deleted before our position, look from begining to
		 * find it or first greater
		 */

		for (*off = FirstOffsetNumber; *off <= maxoff; (*off)++)
		{
			res = compareItemPointers(item, (ItemPointer) GinDataPageGetItem(page, *off));

			if (res == 0)
				return true;

			if (res < 0)
			{
				(*off)--;
				return true;
			}
		}
	}

	return false;
}

/*
 * Start* functions setup state of searches: find correct buffer and locks it,
 * Stop* functions unlock buffer (but don't release!)
 */
static void
startScanEntry(Relation index, GinState *ginstate, GinScanEntry entry, bool firstCall)
{
	if (entry->master != NULL)
	{
		entry->isFinished = entry->master->isFinished;
		return;
	}

	if (firstCall)
	{
		/*
		 * at first call we should find entry, and begin scan of posting tree
		 * or just store posting list in memory
		 */
		GinBtreeData btreeEntry;
		GinBtreeStack *stackEntry;
		Page		page;
		bool		needUnlock = TRUE;

		prepareEntryScan(&btreeEntry, index, entry->entry, ginstate);
		btreeEntry.searchMode = TRUE;
		stackEntry = ginFindLeafPage(&btreeEntry, NULL);
		page = BufferGetPage(stackEntry->buffer);

		entry->isFinished = TRUE;
		entry->buffer = InvalidBuffer;
		entry->offset = InvalidOffsetNumber;
		entry->list = NULL;
		entry->nlist = 0;
		entry->reduceResult = FALSE;
		entry->predictNumberResult = 0;

		if (btreeEntry.findItem(&btreeEntry, stackEntry))
		{
			IndexTuple	itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stackEntry->off));

			if (GinIsPostingTree(itup))
			{
				BlockNumber rootPostingTree = GinGetPostingTree(itup);
				GinPostingTreeScan *gdi;
				Page		page;

				LockBuffer(stackEntry->buffer, GIN_UNLOCK);
				needUnlock = FALSE;
				gdi = prepareScanPostingTree(index, rootPostingTree, TRUE);

				entry->buffer = scanBeginPostingTree(gdi);
				IncrBufferRefCount(entry->buffer);

				page = BufferGetPage(entry->buffer);
				entry->predictNumberResult = gdi->stack->predictNumber * GinPageGetOpaque(page)->maxoff;

				freeGinBtreeStack(gdi->stack);
				pfree(gdi);
				entry->isFinished = FALSE;
			}
			else if (GinGetNPosting(itup) > 0)
			{
				entry->nlist = GinGetNPosting(itup);
				entry->list = (ItemPointerData *) palloc(sizeof(ItemPointerData) * entry->nlist);
				memcpy(entry->list, GinGetPosting(itup), sizeof(ItemPointerData) * entry->nlist);
				entry->isFinished = FALSE;
			}
		}

		if (needUnlock)
			LockBuffer(stackEntry->buffer, GIN_UNLOCK);
		freeGinBtreeStack(stackEntry);
	}
	else if (entry->buffer != InvalidBuffer)
	{
		/* we should find place where we was stopped */
		BlockNumber blkno;
		Page		page;

		LockBuffer(entry->buffer, GIN_SHARE);

		if (!ItemPointerIsValid(&entry->curItem))
			/* start position */
			return;
		Assert(entry->offset != InvalidOffsetNumber);

		page = BufferGetPage(entry->buffer);

		/* try to find curItem in current buffer */
		if (findItemInPage(page, &entry->curItem, &entry->offset))
			return;

		/* walk to right */
		while ((blkno = GinPageGetOpaque(page)->rightlink) != InvalidBlockNumber)
		{
			LockBuffer(entry->buffer, GIN_UNLOCK);
			entry->buffer = ReleaseAndReadBuffer(entry->buffer, index, blkno);
			LockBuffer(entry->buffer, GIN_SHARE);
			page = BufferGetPage(entry->buffer);

			entry->offset = InvalidOffsetNumber;
			if (findItemInPage(page, &entry->curItem, &entry->offset))
				return;
		}

		/*
		 * curItem and any greated items was deleted by concurrent vacuum, so
		 * we finished scan with currrent entry
		 */
	}
}

static void
stopScanEntry(GinScanEntry entry)
{
	if (entry->buffer != InvalidBuffer)
		LockBuffer(entry->buffer, GIN_UNLOCK);
}

static void
startScanKey(Relation index, GinState *ginstate, GinScanKey key)
{
	uint32		i;

	for (i = 0; i < key->nentries; i++)
		startScanEntry(index, ginstate, key->scanEntry + i, key->firstCall);

	if (key->firstCall)
	{
		memset(key->entryRes, TRUE, sizeof(bool) * key->nentries);
		key->isFinished = FALSE;
		key->firstCall = FALSE;

		if (GinFuzzySearchLimit > 0)
		{
			/*
			 * If all of keys more than threshold we will try to reduce
			 * result, we hope (and only hope, for intersection operation of
			 * array our supposition isn't true), that total result will not
			 * more than minimal predictNumberResult.
			 */

			for (i = 0; i < key->nentries; i++)
				if (key->scanEntry[i].predictNumberResult <= key->nentries * GinFuzzySearchLimit)
					return;

			for (i = 0; i < key->nentries; i++)
				if (key->scanEntry[i].predictNumberResult > key->nentries * GinFuzzySearchLimit)
				{
					key->scanEntry[i].predictNumberResult /= key->nentries;
					key->scanEntry[i].reduceResult = TRUE;
				}
		}
	}
}

static void
stopScanKey(GinScanKey key)
{
	uint32		i;

	for (i = 0; i < key->nentries; i++)
		stopScanEntry(key->scanEntry + i);
}

static void
startScan(IndexScanDesc scan)
{
	uint32		i;
	GinScanOpaque so = (GinScanOpaque) scan->opaque;

	for (i = 0; i < so->nkeys; i++)
		startScanKey(scan->indexRelation, &so->ginstate, so->keys + i);
}

static void
stopScan(IndexScanDesc scan)
{
	uint32		i;
	GinScanOpaque so = (GinScanOpaque) scan->opaque;

	for (i = 0; i < so->nkeys; i++)
		stopScanKey(so->keys + i);
}


static void
entryGetNextItem(Relation index, GinScanEntry entry)
{
	Page		page = BufferGetPage(entry->buffer);

	entry->offset++;
	if (entry->offset <= GinPageGetOpaque(page)->maxoff && GinPageGetOpaque(page)->maxoff >= FirstOffsetNumber)
	{
		entry->curItem = *(ItemPointerData *) GinDataPageGetItem(page, entry->offset);
	}
	else
	{
		BlockNumber blkno = GinPageGetOpaque(page)->rightlink;

		LockBuffer(entry->buffer, GIN_UNLOCK);
		if (blkno == InvalidBlockNumber)
		{
			ReleaseBuffer(entry->buffer);
			entry->buffer = InvalidBuffer;
			entry->isFinished = TRUE;
		}
		else
		{
			entry->buffer = ReleaseAndReadBuffer(entry->buffer, index, blkno);
			LockBuffer(entry->buffer, GIN_SHARE);
			entry->offset = InvalidOffsetNumber;
			entryGetNextItem(index, entry);
		}
	}
}

#define gin_rand() (((double) random()) / ((double) MAX_RANDOM_VALUE))
#define dropItem(e) ( gin_rand() > ((double)GinFuzzySearchLimit)/((double)((e)->predictNumberResult)) )

/*
 * Sets entry->curItem to new found heap item pointer for one
 * entry of one scan key
 */
static bool
entryGetItem(Relation index, GinScanEntry entry)
{
	if (entry->master)
	{
		entry->isFinished = entry->master->isFinished;
		entry->curItem = entry->master->curItem;
	}
	else if (entry->list)
	{
		entry->offset++;
		if (entry->offset <= entry->nlist)
			entry->curItem = entry->list[entry->offset - 1];
		else
		{
			ItemPointerSet(&entry->curItem, InvalidBlockNumber, InvalidOffsetNumber);
			entry->isFinished = TRUE;
		}
	}
	else
	{
		do
		{
			entryGetNextItem(index, entry);
		} while (entry->isFinished == FALSE && entry->reduceResult == TRUE && dropItem(entry));
	}

	return entry->isFinished;
}

/*
 * Sets key->curItem to new found heap item pointer for one scan key
 * returns isFinished!
 */
static bool
keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx, GinScanKey key)
{
	uint32		i;
	GinScanEntry entry;
	bool		res;
	MemoryContext oldCtx;

	if (key->isFinished)
		return TRUE;

	do
	{
		/*
		 * move forward from previously value and set new curItem, which is
		 * minimal from entries->curItems
		 */
		ItemPointerSetMax(&key->curItem);
		for (i = 0; i < key->nentries; i++)
		{
			entry = key->scanEntry + i;

			if (key->entryRes[i])
			{
				if (entry->isFinished == FALSE && entryGetItem(index, entry) == FALSE)
				{
					if (compareItemPointers(&entry->curItem, &key->curItem) < 0)
						key->curItem = entry->curItem;
				}
				else
					key->entryRes[i] = FALSE;
			}
			else if (entry->isFinished == FALSE)
			{
				if (compareItemPointers(&entry->curItem, &key->curItem) < 0)
					key->curItem = entry->curItem;
			}
		}

		if (ItemPointerIsMax(&key->curItem))
		{
			/* all entries are finished */
			key->isFinished = TRUE;
			return TRUE;
		}

		if (key->nentries == 1)
		{
			/* we can do not call consistentFn !! */
			key->entryRes[0] = TRUE;
			return FALSE;
		}

		/* setting up array for consistentFn */
		for (i = 0; i < key->nentries; i++)
		{
			entry = key->scanEntry + i;

			if (entry->isFinished == FALSE && compareItemPointers(&entry->curItem, &key->curItem) == 0)
				key->entryRes[i] = TRUE;
			else
				key->entryRes[i] = FALSE;
		}

		oldCtx = MemoryContextSwitchTo(tempCtx);
		res = DatumGetBool(FunctionCall3(
										 &ginstate->consistentFn,
										 PointerGetDatum(key->entryRes),
										 UInt16GetDatum(key->strategy),
										 key->query
										 ));
		MemoryContextSwitchTo(oldCtx);
		MemoryContextReset(tempCtx);
	} while (!res);

	return FALSE;
}

/*
 * Get heap item pointer from scan
 * returns true if found
 */
static bool
scanGetItem(IndexScanDesc scan, ItemPointerData *item)
{
	uint32		i;
	GinScanOpaque so = (GinScanOpaque) scan->opaque;

	ItemPointerSetMin(item);
	for (i = 0; i < so->nkeys; i++)
	{
		GinScanKey	key = so->keys + i;

		if (keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx, key) == FALSE)
		{
			if (compareItemPointers(item, &key->curItem) < 0)
				*item = key->curItem;
		}
		else
			return FALSE;		/* finished one of keys */
	}

	for (i = 1; i <= so->nkeys; i++)
	{
		GinScanKey	key = so->keys + i - 1;

		for (;;)
		{
			int			cmp = compareItemPointers(item, &key->curItem);

			if (cmp == 0)
				break;
			else if (cmp > 0)
			{
				if (keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx, key) == TRUE)
					return FALSE;		/* finished one of keys */
			}
			else
			{					/* returns to begin */
				*item = key->curItem;
				i = 0;
				break;
			}
		}
	}

	return TRUE;
}

#define GinIsNewKey(s)		( ((GinScanOpaque) scan->opaque)->keys == NULL )
#define GinIsVoidRes(s)		( ((GinScanOpaque) scan->opaque)->isVoidRes == true )

Datum
gingetmulti(PG_FUNCTION_ARGS)
{
	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
	ItemPointer tids = (ItemPointer) PG_GETARG_POINTER(1);
	int32		max_tids = PG_GETARG_INT32(2);
	int32	   *returned_tids = (int32 *) PG_GETARG_POINTER(3);

	if (GinIsNewKey(scan))
		newScanKey(scan);

	*returned_tids = 0;

	if (GinIsVoidRes(scan))
		PG_RETURN_BOOL(false);

	startScan(scan);

	do
	{
		if (scanGetItem(scan, tids + *returned_tids))
			(*returned_tids)++;
		else
			break;
	} while (*returned_tids < max_tids);

	stopScan(scan);

	PG_RETURN_BOOL(*returned_tids == max_tids);
}

Datum
gingettuple(PG_FUNCTION_ARGS)
{
	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
	ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
	bool		res;

	if (dir != ForwardScanDirection)
		elog(ERROR, "Gin doesn't support other scan directions than forward");

	if (GinIsNewKey(scan))
		newScanKey(scan);

	if (GinIsVoidRes(scan))
		PG_RETURN_BOOL(false);

	startScan(scan);
	res = scanGetItem(scan, &scan->xs_ctup.t_self);
	stopScan(scan);

	PG_RETURN_BOOL(res);
}
