/* ---------------------------------------------------------------------------
Nullsoft Database Engine
--------------------
codename: Near Death Experience
--------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------

Scanner Class

--------------------------------------------------------------------------- */

#include "nde.h"
#include "../nu/AutoChar.h"
#include "../nu/AutoWide.h"
#include "BinaryField.h"
#include "Binary32Field.h"
#include <assert.h>
//---------------------------------------------------------------------------
Scanner::Scanner(Table *parentTable)
{
	disable_date_resolution=0;
	index = NULL;
	pTable = parentTable;
	Edition=FALSE;

	lastLocateIndex = NULL;
	lastLocateId = -1;
	lastLocateIdx = -1;
	lastLocateFrom = -128;
	lastLocateFieldClone = NULL;
	CurrentRecord=NULL;
	CurrentRecordIdx=0;
	iModified = FALSE;
	FiltersOK = FALSE;
	
	search_any = FALSE;
	ResultPtr = 0;

	/*inMatchJoins = 0;
	lastJoinCache = 0;
	invalidJoinCache = 1;*/
	last_query = NULL;
	last_query_failed = FALSE;
	token = NULL;
	in_query_parser = 0;
}

//---------------------------------------------------------------------------
Scanner::~Scanner()
{
	if (CurrentRecord) CurrentRecord->Release();

	if (lastLocateFieldClone)
	{
		delete lastLocateFieldClone;
	}

	Query_CleanUp();

	if (token) CFRelease(token);
	if (last_query) CFRelease(last_query);
}

//---------------------------------------------------------------------------
Record *Scanner::GetRecord(int Idx)
{
	int Ptr;
	Ptr = index->Get(Idx);

	Record *r = pTable->RowCache_Get(Ptr);
	if (r)
	{
		return r;
	}

	r = new Record(Ptr, Idx, pTable->Handle, pTable);
	pTable->RowCache_Add(r, Ptr);
	return r;
}

//---------------------------------------------------------------------------
void Scanner::GetCurrentRecord(void)
{
	/*
	if (Eof() || Bof()) 
	{
	if (CurrentRecord) CurrentRecord->Release();
	CurrentRecord = NULL;
	return;
	}

	Record *old_record = CurrentRecord;
	CurrentRecord = NULL;

	Record *new_record = GetRecord(CurrentRecordIdx);	
	if (old_record) old_record->Release();
	CurrentRecord = new_record;
	*/
	if (CurrentRecord) CurrentRecord->Release();
	CurrentRecord = NULL;

	//invalidJoinCache = 1;
	if (Eof() || Bof()) return;
	CurrentRecord = GetRecord(CurrentRecordIdx);

}

//---------------------------------------------------------------------------
void Scanner::GetRecordById(int Id, BOOL checkFilters)
{
	CurrentRecordIdx=max(min(index->NEntries, Id+NUM_SPECIAL_RECORDS), 0);
	GetCurrentRecord();
	if (!checkFilters || MatchFilters())
		return;
	Next();
}

//---------------------------------------------------------------------------
void Scanner::First(int *killswitch)
{
	if (last_query_failed) return;
	GetRecordById(0);
	if (!MatchFilters() && !Eof())
		Next(killswitch);
}

//---------------------------------------------------------------------------
int Scanner::Next(int *killswitch)
{
	if (last_query_failed) return 0;

	while (!Eof() && !Bof())
	{
		CurrentRecordIdx++;
		GetCurrentRecord();
		if (MatchFilters())
			break;
		else
		{
			if ((killswitch && *killswitch))
				return 0;
		}
	}
	return 1;
}

//---------------------------------------------------------------------------
int Scanner::Previous(int *killswitch)
{
	if (last_query_failed) return 0;

	while (CurrentRecordIdx >= NUM_SPECIAL_RECORDS)
	{
		CurrentRecordIdx--;
		GetCurrentRecord();
		if (MatchFilters())
			break;
		else
		{
			if ((killswitch && *killswitch))
				return 0;
		}
	}
	return 1;
}

//---------------------------------------------------------------------------
void Scanner::Last(int *killswitch)
{
	if (last_query_failed) return;
	GetRecordById(index->NEntries-NUM_SPECIAL_RECORDS-1); // -3 here because 1)GetRecordById is public, so -NUM_SPECIAL_RECORDS, and 2)last entry is nentries-1, so -1
	if (!MatchFilters() && !Bof())
		Previous(killswitch);
	if (CurrentRecordIdx < NUM_SPECIAL_RECORDS)
	{
		CurrentRecordIdx = index->NEntries;
		GetCurrentRecord(); // will only delete current record if it exists
	}
}

//---------------------------------------------------------------------------
BOOL Scanner::Eof(void)
{
	if (last_query_failed) return TRUE;
	return CurrentRecordIdx >= index->NEntries;
}

//---------------------------------------------------------------------------
BOOL Scanner::Bof(void)
{
	if (last_query_failed) return TRUE;
	return CurrentRecordIdx < NUM_SPECIAL_RECORDS;
}

//---------------------------------------------------------------------------
#ifdef _WIN32
Field *Scanner::GetFieldByName(const wchar_t *FieldName)
{
	ColumnField *header = pTable->GetColumnByName(FieldName);
	if (header)
	{
		unsigned char Idx = header->ID;
		return GetFieldById(Idx);
	}
	return NULL;
}
#else
Field *Scanner::GetFieldByName(CFStringRef FieldName)
{
	ColumnField *header = pTable->GetColumnByName(FieldName);
	if (header)
	{
		unsigned char Idx = header->ID;
		return GetFieldById(Idx);
	}
	return NULL;
}
#endif

//---------------------------------------------------------------------------
Field *Scanner::GetFieldById(unsigned char Id)
{
	if (!CurrentRecord)
		return NULL;
	Field *field = CurrentRecord->GetField(Id);
	if (field)
	{
		int column_type = pTable->GetColumnType(Id);
		if (!CompatibleFields(field->GetType(), column_type))
		{
			return NULL;
		}
	}
	return field;
}

//---------------------------------------------------------------------------
#ifdef _WIN32
Field *Scanner::NewFieldByName(const wchar_t *fieldName, unsigned char Perm)
{
	ColumnField *header = pTable->GetColumnByName(fieldName);
	if (header)
	{
		unsigned char Id = header->ID;
		Field *field = NewFieldById(Id, Perm);
		return field;
	}
	return NULL;
}
#else
Field *Scanner::NewFieldByName(CFStringRef fieldName, unsigned char Perm)
{
	ColumnField *header = pTable->GetColumnByName(fieldName);
	if (header)
	{
		unsigned char Id = header->ID;
		Field *field = NewFieldById(Id, Perm);
		return field;
	}
	return NULL;
}
#endif

//---------------------------------------------------------------------------
Field *Scanner::NewFieldById(unsigned char Id, unsigned char Perm)
{
	ColumnField *field = GetColumnById(Id);
	if (!field)
		return NULL;
	Field *O=GetFieldById(Id);
	if (O) return O;
	switch (field->GetDataType())
	{
	case FIELD_STRING:
		O = new StringField();
		break;
	case FIELD_INTEGER:
		O = new IntegerField();
		break;
	case FIELD_INT64:
		O = new Int64Field();
		break;
	case FIELD_INT128:
		O = new Int128Field();
		break;
	case FIELD_DATETIME:
		if (disable_date_resolution)
			O= new StringField();
		else
			O = new DateTimeField();
		break;
	case FIELD_LENGTH:
		O = new LengthField();
		break;
	case FIELD_FILENAME:
		O = new FilenameField();
		break;
	case FIELD_BINARY:
		O = new BinaryField();
		break;
	case FIELD_BINARY32:
		O = new Binary32Field();
		break;
	default:
#ifdef WIN32
		MessageBox(NULL, "unknown field type for id", "debug", 0);
#else
		printf("NDE Error: unknown field type for id\n");
#endif
		O = new Field();
		break;
	}
	O->Type = field->GetDataType();
	O->ID = Id;
	CurrentRecord->AddField(O);
	return O;
}

//---------------------------------------------------------------------------
void Scanner::Post(void)
{
	if (!CurrentRecord) return;
	/*if (CurrentRecord->RecordIndex == NEW_RECORD)
	NEntries++;*/
	if (pTable->use_row_cache && CurrentRecordIdx != NEW_RECORD)
	{
		int Ptr;
		Ptr = index->Get(CurrentRecordIdx);
		pTable->RowCache_Remove(Ptr);
	}
	CurrentRecordIdx = CurrentRecord->WriteFields(pTable, CurrentRecordIdx);
	Edition=FALSE;
	if (pTable->use_row_cache)
	{
		int Ptr;
		Ptr = index->Get(CurrentRecordIdx);
		pTable->RowCache_Add(CurrentRecord, Ptr);
	}
}

//---------------------------------------------------------------------------
void Scanner::New(void)
{
	if (CurrentRecord) CurrentRecord->Release();
	CurrentRecord = NULL;

	CurrentRecord = new Record(0, index->NEntries, pTable->Handle, pTable);
	CurrentRecordIdx = NEW_RECORD;
	Edition = TRUE;
}

//---------------------------------------------------------------------------
void Scanner::Insert(void)
{
	if (CurrentRecord) CurrentRecord->Release();
	CurrentRecord = NULL;

	CurrentRecord = new Record(0, CurrentRecordIdx, pTable->Handle, pTable);
	CurrentRecordIdx = NEW_RECORD;
	Edition=TRUE;
}

//---------------------------------------------------------------------------
void Scanner::Delete(void)
{
	if (CurrentRecord)
	{
		if (pTable->use_row_cache && CurrentRecordIdx != NEW_RECORD)
		{
			int Ptr;
			Ptr = index->Get(CurrentRecordIdx);
			pTable->RowCache_Delete(Ptr);
		}
		CurrentRecord->Delete(pTable, CurrentRecordIdx);
	}
	if (Eof())
		Previous();
	GetRecordById(CurrentRecordIdx-NUM_SPECIAL_RECORDS);
}

//---------------------------------------------------------------------------
int Scanner::GetRecordId(void)
{
	return CurrentRecordIdx != NEW_RECORD ? CurrentRecordIdx-NUM_SPECIAL_RECORDS : CurrentRecordIdx;
}

//---------------------------------------------------------------------------
void Scanner::Edit(void)
{
	if (Edition) return;
	if (!CurrentRecord)
		return;
	/*Field *f = (Field *)CurrentRecord->Fields->GetHead();
	while (f)
	{
	f->SubtableRecord = INVALID_RECORD;
	f = (Field *)f->GetNext();
	}*/

	if (CurrentRecord->InCache()) // if it's in the cache
	{
		// benski> make copy of CurrentRecord, outside the cache
		int Ptr;
		Ptr = index->Get(CurrentRecordIdx);
		pTable->RowCache_Remove(Ptr);
		Record *r = new Record(Ptr, CurrentRecordIdx, pTable->Handle, pTable);
		CurrentRecord->Release();
		CurrentRecord = r;
	}

	Edition = TRUE;
}


//---------------------------------------------------------------------------
void Scanner::Cancel(void)
{
	Edition = FALSE;
	GetCurrentRecord();
}

//---------------------------------------------------------------------------
#ifdef _WIN32
BOOL Scanner::LocateByName(const wchar_t *col, int From, Field *field, int *nskip)
{
	ColumnField *f = pTable->GetColumnByName(col);
	if (!f)
		return NULL;
	return LocateById(f->GetFieldId(), From, field, nskip);
}
#else
BOOL Scanner::LocateByName(CFStringRef col, int From, Field *field, int *nskip)
{
	ColumnField *f = pTable->GetColumnByName(col);
	if (!f)
		return NULL;
	return LocateById(f->GetFieldId(), From, field, nskip);
}
#endif

//---------------------------------------------------------------------------
void Scanner::CacheLastLocate(int Id, int From, Field *field, Index *i, int j)
{
	lastLocateId = Id;
	lastLocateFrom = From;
	if (lastLocateFieldClone)
	{
		delete lastLocateFieldClone;
		lastLocateFieldClone = NULL;
	}
	lastLocateFieldClone = field->Clone(pTable);
	lastLocateIndex = i;
	i->locateUpToDate = TRUE;
	pTable->SetGlobalLocateUpToDate(TRUE);
	lastLocateIdx = j;
}


//---------------------------------------------------------------------------
BOOL Scanner::LocateById(int Id, int From, Field *field, int *nskip)
{
	return LocateByIdEx(Id, From, field, nskip, COMPARE_MODE_EXACT);
}
//---------------------------------------------------------------------------
BOOL Scanner::LocateByIdEx(int Id, int From, Field *field, int *nskip, int comp_mode)
{
	IndexField *i = pTable->GetIndexById(Id);
	Field *compField;
	int j;
	int n;
	Field *cfV;

	if (index->NEntries == NUM_SPECIAL_RECORDS)
		return FALSE;

	int success;

	if (nskip) *nskip=0;
	// I know this is stupid but.... May be do something later
	switch (comp_mode)
	{
	case COMPARE_MODE_CONTAINS:
		while (1)
		{
			success = FALSE;
			if (!i)
			{
				// No index for this column. Using slow locate, enumerates the database, still faster than what the user
				// can do since we have access to QuickFindField which only read field headers
				// in order to locate the field we have to compare. user could only read the entire record.
				if (From == FIRST_RECORD)
					From = NUM_SPECIAL_RECORDS;
				else
					From+=(NUM_SPECIAL_RECORDS+1);
				if (From == lastLocateFrom && Id == lastLocateId && field->Contains(lastLocateFieldClone)==0 && index == lastLocateIndex && (index->locateUpToDate || pTable->GLocateUpToDate))
				{
					if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
					success = TRUE;
					goto nextiter_1;
				}
				for (j=From;j<index->NEntries;j++)
				{
					compField = index->QuickFindField(Id, index->Get(j));
					cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
					if (!field->Contains(cfV))
					{
						if (compField)
						{
							delete compField;
						}
						if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, FALSE);
						CacheLastLocate(Id, From, field, index, j);
						success = TRUE;
						goto nextiter_1;
					}
					delete compField;
				}
				success = FALSE;
				goto nextiter_1;
			}
			else
			{
				// Index available. Using fast locate. nfetched=log2(nrecords) for first locate, 1 more fetch per locate on same criteria
				int p;
				if (From == FIRST_RECORD) From = NUM_SPECIAL_RECORDS;
				else From = index->TranslateIndex(From+NUM_SPECIAL_RECORDS, i->index)+1;
				if (From == lastLocateFrom && Id == lastLocateId && field->Contains(lastLocateFieldClone)==0 && index == lastLocateIndex && (i->index->locateUpToDate || pTable->GLocateUpToDate))
				{
					if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
					success = TRUE;
					goto nextiter_1;
				}
				if (From >= index->NEntries)
				{
					return FALSE;
				}
				compField = i->index->QuickFindField(Id, i->index->Get(From));
				cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
				if (field->Contains(cfV) == 0)
				{
					delete compField;
					n = i->index->TranslateIndex(From, index);
					if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
					CacheLastLocate(Id, From, field, i->index, n);
					success = TRUE;
					goto nextiter_1;
				}
				delete compField;
				p = i->index->QuickFindEx(Id, field, From, comp_mode);
				if (p != FIELD_NOT_FOUND)
				{
					n = (index->GetId() == Id) ? p : i->index->TranslateIndex(p, index);
					if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
					CacheLastLocate(Id, From, field, i->index, n);
					success = TRUE;
					goto nextiter_1;
				}
			}
nextiter_1: // eek
			if (success)
			{
				if (!MatchFilters() && !Eof())
				{
					From = GetRecordId();
					if (nskip) (*nskip)++;
				}
				else break;
			}
			else break;
		}

		break;
	case COMPARE_MODE_EXACT:
		while (1)
		{
			success = FALSE;
			if (!i)
			{
				// No index for this column. Using slow locate, enumerates the database, still faster than what the user
				// can do since we have access to QuickFindField which only read field headers
				// in order to locate the field we have to compare. user could only read the entire record.
				if (From == FIRST_RECORD)
					From = NUM_SPECIAL_RECORDS;
				else
					From+=(NUM_SPECIAL_RECORDS+1);
				if (From == lastLocateFrom && Id == lastLocateId && field->Compare(lastLocateFieldClone)==0 && index == lastLocateIndex && (index->locateUpToDate || pTable->GLocateUpToDate))
				{
					if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
					success = TRUE;
					goto nextiter_2;
				}
				for (j=From;j<index->NEntries;j++)
				{
					compField = index->QuickFindField(Id, index->Get(j));
					cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
					if (!field->Compare(cfV))
					{
						delete compField;
						if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, FALSE);
						CacheLastLocate(Id, From, field, index, j);
						success = TRUE;
						goto nextiter_2;
					}
					delete compField;
				}
				success = FALSE;
				goto nextiter_2;
			}
			else
			{
				// Index available. Using fast locate. nfetched=log2(nrecords) for first locate, 1 more fetch per locate on same criteria
				int p;
				if (From == FIRST_RECORD) From = NUM_SPECIAL_RECORDS;
				else From = index->TranslateIndex(From+NUM_SPECIAL_RECORDS, i->index)+1;
				if (From == lastLocateFrom && Id == lastLocateId && field->Compare(lastLocateFieldClone)==0 && index == lastLocateIndex && (i->index->locateUpToDate || pTable->GLocateUpToDate))
				{
					if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
					success = TRUE;
					goto nextiter_2;
				}
				if (From >= index->NEntries)
				{
					return FALSE;
				}
				compField = i->index->QuickFindField(Id, i->index->Get(From));
				cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
				if (field->Compare(cfV) == 0)
				{
					if (compField)
					{
						delete compField;
					}
					n = i->index->TranslateIndex(From, index);
					if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
					CacheLastLocate(Id, From, field, i->index, n);
					success = TRUE;
					goto nextiter_2;
				}
				delete compField;
				p = i->index->QuickFindEx(Id, field, From, comp_mode);
				if (p != FIELD_NOT_FOUND)
				{
					n = (index->GetId() == Id) ? p : i->index->TranslateIndex(p, index);
					if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
					CacheLastLocate(Id, From, field, i->index, n);
					success = TRUE;
					goto nextiter_2;
				}
			}
nextiter_2: // eek
			if (success)
			{
				if (!MatchFilters() && !Eof())
				{
					From = GetRecordId();
					if (nskip) (*nskip)++;
				}
				else break;
			}
			else break;
		}

		break;
	case COMPARE_MODE_STARTS:
		while (1)
		{
			success = FALSE;
			if (!i)
			{
				// No index for this column. Using slow locate, enumerates the database, still faster than what the user
				// can do since we have access to QuickFindField which only read field headers
				// in order to locate the field we have to compare. user could only read the entire record.
				if (From == FIRST_RECORD)
					From = NUM_SPECIAL_RECORDS;
				else
					From+=(NUM_SPECIAL_RECORDS+1);
				if (From == lastLocateFrom && Id == lastLocateId && field->Starts(lastLocateFieldClone)==0 && index == lastLocateIndex && (index->locateUpToDate || pTable->GLocateUpToDate))
				{
					if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
					success = TRUE;
					goto nextiter_3;
				}
				for (j=From;j<index->NEntries;j++)
				{
					compField = index->QuickFindField(Id, index->Get(j));
					cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
					if (!field->Starts(cfV))
					{
						if (compField)
						{
							delete compField;
						}
						if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, FALSE);
						CacheLastLocate(Id, From, field, index, j);
						success = TRUE;
						goto nextiter_3;
					}
					if (compField)
					{
						delete compField;
					}
				}
				success = FALSE;
				goto nextiter_3;
			}
			else
			{
				// Index available. Using fast locate. nfetched=log2(nrecords) for first locate, 1 more fetch per locate on same criteria
				int p;
				if (From == FIRST_RECORD) From = NUM_SPECIAL_RECORDS;
				else From = index->TranslateIndex(From+NUM_SPECIAL_RECORDS, i->index)+1;
				if (From == lastLocateFrom && Id == lastLocateId && field->Starts(lastLocateFieldClone)==0 && index == lastLocateIndex && (i->index->locateUpToDate || pTable->GLocateUpToDate))
				{
					if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
					success = TRUE;
					goto nextiter_3;
				}
				if (From >= index->NEntries)
				{
					return FALSE;
				}
				compField = i->index->QuickFindField(Id, i->index->Get(From));
				cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
				if (field->Starts(cfV) == 0)
				{
					delete compField;
					n = i->index->TranslateIndex(From, index);
					if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
					CacheLastLocate(Id, From, field, i->index, n);
					success = TRUE;
					goto nextiter_3;
				}
				delete compField;
				p = i->index->QuickFindEx(Id, field, From, comp_mode);
				if (p != FIELD_NOT_FOUND)
				{
					n = (index->GetId() == Id) ? p : i->index->TranslateIndex(p, index);
					if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
					CacheLastLocate(Id, From, field, i->index, n);
					success = TRUE;
					goto nextiter_3;
				}
			}
nextiter_3: // eek
			if (success)
			{
				if (!MatchFilters() && !Eof())
				{
					From = GetRecordId();
					if (nskip) (*nskip)++;
				}
				else break;
			}
			else break;
		}

		break;
	}


	return success;
}


//---------------------------------------------------------------------------
#ifdef _WIN32
void Scanner::DeleteFieldByName(const wchar_t *name)
{
	ColumnField *header = pTable->GetColumnByName(name);
	if (header)
	{
		unsigned char Idx = header->ID;
		DeleteFieldById(Idx);
	}
	return;
}
#else
void Scanner::DeleteFieldByName(CFStringRef name)
{
	ColumnField *header = pTable->GetColumnByName(name);
	if (header)
	{
		unsigned char Idx = header->ID;
		DeleteFieldById(Idx);
	}
	return;
}
#endif

//---------------------------------------------------------------------------
void Scanner::DeleteFieldById(unsigned char Id)
{
	Field *field = CurrentRecord->GetField(Id);
	if (!field) return;
	CurrentRecord->RemoveField(field);
}

//---------------------------------------------------------------------------
void Scanner::DeleteField(Field *field)
{
	if (!field) return;
	CurrentRecord->RemoveField(field);
}


static bool TotalSizeCalculator(Record *record, Field *f, void *context)
{
	size_t *totalSize = (size_t *)context;
	*totalSize += f->GetTotalSize();
	return true;
}

//---------------------------------------------------------------------------
float Scanner::FragmentationLevel(void)
{
	int oldP = GetRecordId();
	int i;
	size_t totalSize=0;

	if (CurrentRecord)
	{
		if (CurrentRecord) CurrentRecord->Release();
		CurrentRecord = NULL;
		CurrentRecordIdx = 0;
	}

	for (i=0;i<index->NEntries;i++)
	{
		Record *r = GetRecord(i);
		if (r)
		{
			r->WalkFields(TotalSizeCalculator, &totalSize);
			r->Release();
		}
	}
	GetRecordById(oldP);
	Vfseek(pTable->Handle, 0, SEEK_END);
	return (((float)(Vftell(pTable->Handle)-strlen(__TABLE_SIGNATURE__)) / (float)totalSize) - 1) * 100;
}

//---------------------------------------------------------------------------
int Scanner::GetRecordsCount(void)
{
	if (index)
		return index->NEntries-NUM_SPECIAL_RECORDS;
	else
		return 0;
}

//---------------------------------------------------------------------------
BOOL Scanner::SetWorkingIndexById(unsigned char Id)
{
	IndexField *indx = pTable->GetIndexById(Id);
	int v = CurrentRecordIdx;
	if (indx)
	{
		if (!Eof() && !Bof())
		{
			IndexField *f = index->SecIndex;
			v = index->GetCooperative(CurrentRecordIdx);
			while (f != indx)
			{
				v = f->index->GetCooperative(v);
				f = f->index->SecIndex;
			}
		}
		index = indx->index;
		CurrentRecordIdx = v;
		GetCurrentRecord();
	}
	return (indx != NULL);
}

//---------------------------------------------------------------------------
#ifdef _WIN32
BOOL Scanner::SetWorkingIndexByName(const wchar_t *desc)
{
	IndexField *indx = pTable->GetIndexByName(desc);
	if (indx)
		return SetWorkingIndexById(indx->ID);
	else
		return SetWorkingIndexById(-1);
}
#else
BOOL Scanner::SetWorkingIndexByName(CFStringRef desc)
{
	IndexField *indx = pTable->GetIndexByName(desc);
	if (indx)
		return SetWorkingIndexById(indx->ID);
	else
		return SetWorkingIndexById(-1);
}
#endif

//---------------------------------------------------------------------------
void Scanner::IndexModified(void)
{
	iModified = TRUE;
}

//---------------------------------------------------------------------------
void Scanner::ClearDirtyBit(void)
{
	iModified = FALSE;
}

//---------------------------------------------------------------------------
Table *Scanner::GetTable(void)
{
	return pTable;
}

//---------------------------------------------------------------------------
#ifdef _WIN32
ColumnField *Scanner::GetColumnByName(const wchar_t *FieldName)
{
	return pTable->GetColumnByName(FieldName);
}
#else
ColumnField *Scanner::GetColumnByName(CFStringRef FieldName)
{
	return pTable->GetColumnByName(FieldName);
}

#endif

//---------------------------------------------------------------------------
ColumnField *Scanner::GetColumnById(unsigned char Idx)
{
	return pTable->GetColumnById(Idx);
}

//---------------------------------------------------------------------------
#ifdef _WIN32
int Scanner::AddFilterByName(const wchar_t *name, Field *Data, unsigned char Op)
{
	ColumnField *f = pTable->GetColumnByName(name);
	if (f)
		return AddFilterById(f->GetFieldId(), Data, Op);
	return ADDFILTER_FAILED;
}
#else
int Scanner::AddFilterByName(CFStringRef name, Field *Data, unsigned char Op)
{
	ColumnField *f = pTable->GetColumnByName(name);
	if (f)
		return AddFilterById(f->GetFieldId(), Data, Op);
	return ADDFILTER_FAILED;
}
#endif

//---------------------------------------------------------------------------
int Scanner::AddFilterById(unsigned char Id, Field *Data, unsigned char Op)
{
	ColumnField *f = pTable->GetColumnById(Id);
	if (f)
	{
		Filter *filter = new Filter(Data, f->GetFieldId(), Op);
		FilterList.AddEntry(filter, TRUE);
	}
	else
		return ADDFILTER_FAILED;

	if (in_query_parser) return 1;
	return CheckFilters();
}

//---------------------------------------------------------------------------
int Scanner::AddFilterOp(unsigned char Op)
{
	Filter *filter = new Filter(Op);
	FilterList.AddEntry(filter, TRUE);
	if (in_query_parser) return 1;
	return CheckFilters();
}

//---------------------------------------------------------------------------
Filter *Scanner::GetLastFilter(void)
{
	if (FilterList.GetNElements() == 0) return NULL;
	return (Filter *)FilterList.GetFoot();
}

//---------------------------------------------------------------------------
void Scanner::RemoveFilters(void)
{
	last_query_failed = FALSE;
	while (FilterList.GetNElements() > 0)
		FilterList.RemoveEntry(FilterList.GetHead());
	FiltersOK = FALSE;
}

//---------------------------------------------------------------------------
BOOL Scanner::CheckFilters(void)
{
	int f=0;
	FiltersOK = FALSE;
	if (FilterList.GetNElements() == 0) // Should never happen
		return FILTERS_INVALID;
	Filter *filter = (Filter *)FilterList.GetHead();
	while (filter)
	{
		if (f == 256) return FILTERS_INVALID;
		int op = filter->GetOp();
		if (filter->Data() || op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY)
			f++;
		else
		{
			if (op != FILTER_NOT)
				f--;
		}
		if (f == 0) return FILTERS_INVALID;
		filter = (Filter *)filter->GetNext();
	}

	if (f == 1)
	{
		FiltersOK = TRUE;
		return FILTERS_COMPLETE;
	}
	return FILTERS_INCOMPLETE;
}

//---------------------------------------------------------------------------
static bool EmptyMeansTrue(int op)
{
	return op == FILTER_ISEMPTY
		|| op == FILTER_NOTEQUALS
		|| op == FILTER_NOTCONTAINS;
}

//---------------------------------------------------------------------------
bool Scanner::MatchFilter(Filter *filter)
{
	Field *field = GetFieldById(filter->GetId());
	int op = filter->GetOp();
	Field * f = filter->Data();
	/* old behaviour
	if (!field)
	return EmptyMeansTrue(op);
	else if (op == FILTER_ISEMPTY)
	return FALSE;
	else if (op == FILTER_ISNOTEMPTY)
	return TRUE;
	*/

	// new behaviour
	if (!field)
	{
		// if field is empty and we're doing an equals op, match if f is also empty
		if (op == FILTER_EQUALS && f) return f->ApplyFilter(f,FILTER_ISEMPTY);
		if (op == FILTER_NOTEQUALS && f) return f->ApplyFilter(f,FILTER_ISNOTEMPTY);
		return EmptyMeansTrue(op);
	}
	// no need to check for op == FILTER_ISEMPTY, the fields now handle that

	return field->ApplyFilter(f, op);
}

struct Results
{
	void operator=(bool _val)
	{
		calculated=true;
		value=_val;
	}

	bool Calc(Scanner *scanner)
	{
		if (!calculated)
		{
#if 0 /* if we want to do field-to-field comparisons */
			Field *compare_column = filter->Data();
			if (compare_column && compare_column->Type == FIELD_COLUMN)
			{
				Field *field = scanner->GetFieldById(filter->GetId());
				Field *compare_field = scanner->GetFieldById(compare_column->ID);
				int op = filter->GetOp();
				value = field->ApplyFilter(compare_field, op);
				calculated=true;
				return value;
			}
#endif

			value = scanner->MatchFilter(filter);
			calculated=true;
		}
		return value;
	}

	void SetFilter(Filter *_filter)
	{
		calculated=false;
		filter=_filter;
	}
private:
	bool value;
	bool calculated;
	Filter *filter;
};

bool Scanner::MatchSearch(const SearchFields &fields, StringField *search_field)
{
	for (SearchFields::const_iterator itr = fields.begin(); itr != fields.end();itr++)
	{
		Field *f = GetFieldById(*itr);
		if (f && f->Contains(search_field))
		{
			return true;
		}
	}
	return false; // if none of the fields matched the search strings, then bail out
}

bool Scanner::MatchSearches()
{
	// no search means always match
	if (search_strings.empty())
		return true;

	Scanner *s = pTable->GetDefaultScanner(); // kind of a hack but gets around private member variables
	const SearchFields &fields = s->search_fields;
	if (search_any)
	{
		for (SearchStrings::const_iterator field_itr=search_strings.begin();field_itr!=search_strings.end();field_itr++)
		{
			StringField *search_field = *field_itr;
			if (MatchSearch(fields, search_field) == true)
				return true;
		}
		return false; // we'll only get here if no search strings matched
	}
	else
	{ // normal search (subsequent terms do further filtering
		for (SearchStrings::const_iterator field_itr=search_strings.begin();field_itr!=search_strings.end();field_itr++)
		{
			StringField *search_field = *field_itr;
			if (MatchSearch(fields, search_field) == false)
				return false;
		}
		return true; // we'll only get here if all search strings have a match
	}
}

//---------------------------------------------------------------------------
bool Scanner::MatchFilters(void)
{
	if (!FiltersOK || FilterList.GetNElements() == 0)
	{
		//  return MatchJoins();
		return true;
	}

	//if (!MatchSearches())
	//return false;

	ResultPtr = 0;

	Results resultTable[256];

	Filter *filter = (Filter *)FilterList.GetHead();
	while (filter)
	{
		if (ResultPtr == 256)
		{
			FiltersOK = FALSE; // Should never happen, case already discarded by CheckFilters
			return TRUE;
		}
		int op = filter->GetOp();
		if (filter->Data() || op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY)
			resultTable[ResultPtr++].SetFilter(filter);
		else
			switch (op)
		{
			case FILTER_AND:
				if (ResultPtr > 1)
					resultTable[ResultPtr-2] = resultTable[ResultPtr-2].Calc(this) && resultTable[ResultPtr-1].Calc(this);
				ResultPtr--;
				break;
			case FILTER_OR:
				if (ResultPtr > 1)
					resultTable[ResultPtr-2] = resultTable[ResultPtr-2].Calc(this) || resultTable[ResultPtr-1].Calc(this);
				ResultPtr--;
				break;
			case FILTER_NOT:
				if (ResultPtr > 0)
					resultTable[ResultPtr-1] = !resultTable[ResultPtr-1].Calc(this);
				break;
		}
		filter = (Filter *)filter->GetNext();
	}

	if (ResultPtr != 1) // Should never happen, case already discarded by CheckFilters
	{
		FiltersOK = FALSE;
		return TRUE;
	}

	if (!resultTable[0].Calc(this)) return 0;
	//  return MatchJoins();
	//return FALSE;
	return MatchSearches();
}

void Scanner::WalkFilters(FilterWalker callback, void *context)
{
	if (callback)
	{
		LinkedListEntry *entry = FilterList.GetHead();
		while (entry)
		{
			if (!callback(this, (Filter *)entry, context))
				break;
			entry = entry->Next;
		}
	}
}

#ifdef _WIN32
void Scanner::Search(const wchar_t *search_string)
{
	// first, clear existing search terms
	for (SearchStrings::iterator itr=search_strings.begin();itr!=search_strings.end();itr++)
	{
		delete *itr;
	}
	search_strings.clear();

	if (*search_string == L'*' && search_string[1] == L' ')
	{
		search_any=true;
		search_string += 2;
	}
	else
		search_any=false;

	if (search_string)
	{

		while (*search_string)
		{
			while (*search_string && (*search_string == ' ' || *search_string == '\t'))
				search_string++;

			const wchar_t *end=search_string;

			wchar_t c = *search_string;
			if (c == L'\"') // a quoted string
			{
				end++;
				search_string++;
				while (*end && *end != '\"')
					end++;

				if (*search_string) // make sure it's not just a quote by itself
				{
					if (*end == 0) // no terminating quotes
					{
						wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string);
						search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING));
					}
					else if (end > (search_string+1)) // at least one character in the quotes
					{
						wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string-1);
						search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING));
						end++;
					}
				}
				search_string=end;
			}
			else if (c)
			{
				while (*end && *end != ' ' && *end != '\t')
					end++;

				wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string-1);
				search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING));
			}
		}
	}
}
#endif