Dim g_availableFields() AS string
Dim g_selectedFields() AS string

Begin Dialog dlgSearch 0,20,330,300,"Text Search", .SearchDialogHandler
  PushButton 10,13,72,14, "Select File", .btnSelectFile
  Text 90,14,195,16, "No file selected", .lblFileName
  Text 10,40,48,10, "Search Term 1", .lblTerm1
  TextBox 70,40,153,10, .TextBox0
  Text 10,55,48,10, "Search Term 2", .lblTerm2
  TextBox 70,55,153,10, .TextBox1
  Text 10,70,48,10, "Search Term 3", .lblTerm3
  TextBox 70,70,153,10, .TextBox2
  Text 10,85,48,10, "Search Term 4", .lblTerm4
  TextBox 70,85,153,10, .TextBox3
  Text 10,100,48,10, "Search Term 5", .lblTerm5
  TextBox 70,100,153,10, .TextBox4
  Text 10,115,48,10, "Search Term 6", .lblTerm6
  TextBox 70,115,153,10, .TextBox5
  Text 10,130,48,10, "Search Term 7", .lblTerm7
  TextBox 70,130,153,10, .TextBox6
  Text 10,145,48,10, "Search Term 8", .lblTerm8
  TextBox 70,145,153,10, .TextBox7
  Text 10,160,48,10, "Search Term 9", .lblTerm9
  TextBox 70,160,153,10, .TextBox8
  Text 10,175,48,10, "Search Term 10", .lblTerm10
  TextBox 70,175,153,10, .TextBox9
  PushButton 231,40,72,14, "Select Fields to Search", .btnSelectFields
  CheckBox 231,60,56,14, "Case Sensitive", .chkCaseSensitive
  GroupBox 231,78,69,52, "Search Type", .grpSearchType
  OptionGroup .optSearchType
  OptionButton 241,91,50,14, "OR", .optOr
  OptionButton 241,109,50,14, "AND", .optAnd
  Text 231,137,63,10, "File Name Prefix", .lblPrefix
  TextBox 231,155,65,10, .txtPrefix
  Text 20,193,204,20, "Advanced Search: Use the Advanced Search button to select a text file with predefined search terms.", .lblAdvancedHelp
  PushButton 10,219,72,14, "Advanced Search", .btnAdvanced
  Text 90,221,195,14, "No text file selected", .lblTermFile
  Text 10,262,280,10, .lblStatus
  OKButton 22,241,40,14, "OK", .OKButton1
  CancelButton 84,241,40,14, "Cancel", .CancelButton1
  PushButton 146,241,40,14, "Help", .btnHelp
End Dialog

Begin Dialog dlgFields 0,40,384,235,"Select Fields", .FieldsDialogHandler
  Text 17,10,60,14, "Available Fields", .lblAvailable
  ListBox 15,29,122,160, g_availableFields(), .ListBox1
  Text 167,11,60,14, "Selected Fields", .lblSelected
  ListBox 166,29,122,160, g_selectedFields(), .ListBox2
  PushButton 142,47,20,14, ">", .btnAddOne
  PushButton 142,73,20,14, "<", .btnRemoveOne
  PushButton 142,99,20,14, ">>", .btnAddAll
  PushButton 142,125,20,14, "<<", .btnRemoveAll
  Text 15,196,330,10, .lblStatus
  OKButton 300,35,40,14, "OK", .OKButton1
  CancelButton 300,57,40,14, "Cancel", .CancelButton1
End Dialog

Begin Dialog dlgHelpInst 50,46,293,114,"Help", .HelpDialogHandler
  Text 26,14,240,39, .lblHelpText
  OKButton 30,66,40,14, "OK", .OKButton1
End Dialog






'===============================================================================
' Script:   text_search.iss
' Purpose:  An alternative to IDEA's built-in Search dialog. Accepts up to 10
'           search terms (with ? and * wildcards), optionally merged with a
'           text file of predefined terms, and runs them as a single AND / OR
'           search across the chosen character fields.
'
' Author:   Brian Element - brian.element@ideascripting.com
' Original: August 22, 2012. Documentation by Brian Element, Sunder Gee and
'           Scott Winkel
'
' History:  Aug 29, 2012 - read search terms from a text file
'           Sept 9, 2012 - added help
'           Sept 24, 2012 - require a text file to be selected
'           Aug 17, 2018 - rewrote the dialogs; text import uses FileOpen
'           2026-09-17   - Scott WInkel fixed the "two terms with OR returns 0 hits" bug
'                          (see BuildSearchCriteria and CaptureSearchType) and
'                          added logger, error handling and declaration patterns
'===============================================================================



Option Explicit

Const SCRIPT_NAME = "Text Search"
Const SCRIPT_VERSION = "v2026-09-17"
Const LOG_HANDLE = 2
Const ERROR_LOG_HANDLE = 3
Const ACTION_NONE = 0
Const ACTION_PICK_DATABASE = 1
Const ACTION_PICK_FIELDS = 2
Const ACTION_LOAD_TERM_FILE = 3
Const ACTION_HELP = 4
Const ACTION_RUN = 5
Const ACTION_CANCEL = 6
Const SEARCH_OR = 0
Const SEARCH_AND = 1
Const MAX_TERMS = 10
Const MAX_PREFIX_TRIES = 100
Dim g_dlgFields As dlgFields
Dim g_dlgHelp As dlgHelpInst
Dim g_dlgSearch As dlgSearch
Dim g_charFields() As String
Dim g_crlf As String
Dim g_databaseName As String
Dim g_fileTerms() As String
Dim g_prefix As String
Dim g_statusMessage As String
Dim g_termFile As String
Dim g_terms(MAX_TERMS - 1) As String
Dim g_errorLogFileName As String
Dim g_ideaUser As String
Dim g_ideaVersion As String
Dim g_logDir As String
Dim g_logFileName As String
Dim g_workingDirectory As String
Dim g_bDisableLogging As Boolean
Dim g_logInitialized As Boolean
Dim g_errorLogHandle As Integer
Dim g_logHandle As Integer
Dim g_errorCount As Long
Dim g_warningCount As Long
Dim g_caseSensitive As Integer
Dim g_fieldsSelected As Integer
Dim g_listSelection1 As Integer
Dim g_listSelection2 As Integer
Dim g_pendingAction As Integer
Dim g_searchType As Integer

Sub Main
	Dim criteria As String
	Dim errDesc As String
	Dim msg As String
	Dim resultName As String
	Dim errNum As Long

	On Error GoTo MainError

	Call InitializeState

	On Error GoTo MainError

	If Not ShowSearchDialog() Then
		Call LogInfo("User cancelled before running the search.")
		Call FinalizeLog
		msg = "Script cancelled."
		MsgBox msg, MB_OK Or MB_ICONINFORMATION, SCRIPT_NAME
		Exit Sub
	End If

	criteria = BuildSearchCriteria()
	resultName = RunSearch(criteria)
	On Error GoTo MainError

	If resultName = "" Then
		Call LogError("Search did not complete - no results database created.")
		Call FinalizeLog
		msg = "The search did not complete. No results database was created." _
			& g_crlf & g_crlf & "Search string:" & g_crlf & criteria
		MsgBox msg, MB_OK Or MB_ICONEXCLAMATION, SCRIPT_NAME
		Exit Sub
	End If

	Client.RefreshFileExplorer
	Call FinalizeLog

	msg = "Script complete." & g_crlf & g_crlf _
		& "Results database: " & resultName & g_crlf _
		& "Search string: " & criteria & g_crlf & g_crlf _
		& "Log file: " & g_logFileName
	MsgBox msg, MB_OK Or MB_ICONINFORMATION, SCRIPT_NAME
	Exit Sub

MainError:
	errNum = Err.Number
	errDesc = Err.Description
	Call LogError("CRITICAL in Main: " & errDesc & " (Error " & errNum & ")")
	Call FinalizeLog
	msg = "Unexpected error " & errNum & ": " & errDesc
	MsgBox msg, MB_OK Or MB_ICONSTOP, SCRIPT_NAME
End Sub

Sub InitializeState
	Dim i As Integer

	On Error GoTo InitializeStateError

	g_crlf = Chr(13) & Chr(10)
	g_databaseName = ""
	g_prefix = "TS"
	g_statusMessage = ""
	g_termFile = ""

	g_workingDirectory = Client.WorkingDirectory()
	If Right(g_workingDirectory, 1) <> "\" Then
		g_workingDirectory = g_workingDirectory & "\"
	End If
	g_logDir = g_workingDirectory & "Log Files\"

	g_caseSensitive = 0
	g_fieldsSelected = 0
	g_listSelection1 = 0
	g_listSelection2 = 0
	g_pendingAction = ACTION_NONE
	g_searchType = SEARCH_OR

	For i = 0 To MAX_TERMS - 1
		g_terms(i) = ""
	Next i

	ReDim g_charFields(0)
	ReDim g_fileTerms(0)
	ReDim g_availableFields(0)
	ReDim g_selectedFields(0)

	g_databaseName = CurrentDatabaseName()

	Call InitializeLogging
	Call LogInfo("=== " & SCRIPT_NAME & " " & SCRIPT_VERSION & " started ===")
	Call LogInfo("Working directory: " & g_workingDirectory)
	If g_databaseName <> "" Then
		Call LogInfo("Preselected the open database: " & g_databaseName)
	End If
	Exit Sub

InitializeStateError:
	Err.Clear
End Sub

Function CurrentDatabaseName() As String
	CurrentDatabaseName = ""
	On Error Resume Next
	Err.Clear
	CurrentDatabaseName = Client.CurrentDatabase.Name
	If Err.Number <> 0 Then
		CurrentDatabaseName = ""
		Err.Clear
	End If
	On Error GoTo 0
End Function

Function ShowSearchDialog() As Boolean
	Dim done As Boolean
	Dim button As Integer

	On Error GoTo ShowSearchDialogError

	ShowSearchDialog = False
	done = False

	Do
		Call LoadSearchDialogStruct
		g_pendingAction = ACTION_NONE

		button = Dialog(g_dlgSearch)

		If g_pendingAction = ACTION_NONE And button = 0 Then
			g_pendingAction = ACTION_CANCEL
		End If

		g_statusMessage = ""

		Select Case g_pendingAction

			Case ACTION_PICK_DATABASE
				Call PickDatabase

			Case ACTION_PICK_FIELDS
				If g_databaseName = "" Then
					g_statusMessage = "Please select a file first."
				Else
					Call ShowFieldsDialog
				End If

			Case ACTION_LOAD_TERM_FILE
				Call LoadTermFile

			Case ACTION_HELP
				Call ShowHelpDialog

			Case ACTION_RUN
				If ValidateSearchInput() Then
					ShowSearchDialog = True
					done = True
				End If

			Case Else
				done = True

		End Select
	Loop Until done

	Exit Function

ShowSearchDialogError:
	MsgBox "Error " & Err.Number & " opening the Text Search dialog: " _
		& Err.Description, MB_OK Or MB_ICONSTOP, SCRIPT_NAME
	ShowSearchDialog = False
End Function

Sub LoadSearchDialogStruct
	On Error GoTo LoadSearchDialogStructError

	g_dlgSearch.TextBox0 = g_terms(0)
	g_dlgSearch.TextBox1 = g_terms(1)
	g_dlgSearch.TextBox2 = g_terms(2)
	g_dlgSearch.TextBox3 = g_terms(3)
	g_dlgSearch.TextBox4 = g_terms(4)
	g_dlgSearch.TextBox5 = g_terms(5)
	g_dlgSearch.TextBox6 = g_terms(6)
	g_dlgSearch.TextBox7 = g_terms(7)
	g_dlgSearch.TextBox8 = g_terms(8)
	g_dlgSearch.TextBox9 = g_terms(9)
	g_dlgSearch.txtPrefix = g_prefix
	g_dlgSearch.chkCaseSensitive = g_caseSensitive
	g_dlgSearch.optSearchType = g_searchType
	Exit Sub

LoadSearchDialogStructError:
	Err.Clear
End Sub

Sub CaptureSearchDialogValues
	On Error GoTo CaptureSearchDialogValuesError

	g_terms(0) = Trim(DlgText("TextBox0"))
	g_terms(1) = Trim(DlgText("TextBox1"))
	g_terms(2) = Trim(DlgText("TextBox2"))
	g_terms(3) = Trim(DlgText("TextBox3"))
	g_terms(4) = Trim(DlgText("TextBox4"))
	g_terms(5) = Trim(DlgText("TextBox5"))
	g_terms(6) = Trim(DlgText("TextBox6"))
	g_terms(7) = Trim(DlgText("TextBox7"))
	g_terms(8) = Trim(DlgText("TextBox8"))
	g_terms(9) = Trim(DlgText("TextBox9"))
	g_prefix = Trim(DlgText("txtPrefix"))
	g_caseSensitive = DlgValue("chkCaseSensitive")
	Exit Sub

CaptureSearchDialogValuesError:
	Err.Clear
End Sub

Function SearchDialogHandler(ControlID$, Action%, SuppValue%) As Integer
	Dim keepOpen As Boolean

	On Error GoTo SearchDialogHandlerError

	keepOpen = True

	Select Case Action%

		Case 1
			Call RefreshSearchDialogLabels

		Case 2
			Select Case ControlID$

				Case "optOr"
					g_searchType = SEARCH_OR

				Case "optAnd"
					g_searchType = SEARCH_AND

				Case "btnSelectFile"
					Call CaptureSearchDialogValues
					g_pendingAction = ACTION_PICK_DATABASE
					keepOpen = False

				Case "btnSelectFields"
					Call CaptureSearchDialogValues
					g_pendingAction = ACTION_PICK_FIELDS
					keepOpen = False

				Case "btnAdvanced"
					Call CaptureSearchDialogValues
					g_pendingAction = ACTION_LOAD_TERM_FILE
					keepOpen = False

				Case "btnHelp"
					Call CaptureSearchDialogValues
					g_pendingAction = ACTION_HELP
					keepOpen = False

				Case "OKButton1"
					Call CaptureSearchDialogValues
					g_pendingAction = ACTION_RUN
					keepOpen = False

				Case "CancelButton1"
					g_pendingAction = ACTION_CANCEL
					keepOpen = False

			End Select

	End Select

	If keepOpen Then
		SearchDialogHandler = 1
	Else
		SearchDialogHandler = 0
	End If
	Exit Function

SearchDialogHandlerError:
	Err.Clear
	SearchDialogHandler = 1
End Function

Sub RefreshSearchDialogLabels
	On Error GoTo RefreshSearchDialogLabelsError

	If g_databaseName = "" Then
		DlgText "lblFileName", "No file selected"
	Else
		DlgText "lblFileName", FileNameOnly(g_databaseName)
	End If

	If g_termFile = "" Then
		DlgText "lblTermFile", "No text file selected"
	Else
		DlgText "lblTermFile", FileNameOnly(g_termFile)
	End If

	DlgText "lblStatus", g_statusMessage
	Exit Sub

RefreshSearchDialogLabelsError:
	Err.Clear
End Sub

Function ValidateSearchInput() As Boolean
	On Error GoTo ValidateSearchInputError

	ValidateSearchInput = False

	If g_databaseName = "" Then
		g_statusMessage = "Please select a file."
	ElseIf g_prefix = "" Then
		g_statusMessage = "Please enter a file name prefix."
	ElseIf g_fieldsSelected = 0 Then
		g_statusMessage = "Please select at least one field to search."
	ElseIf CountSearchTerms() = 0 Then
		g_statusMessage = "Please enter at least one search term."
	Else
		ValidateSearchInput = True
	End If
	Exit Function

ValidateSearchInputError:
	Err.Clear
End Function

Function CountSearchTerms() As Integer
	Dim i As Integer
	Dim total As Integer

	On Error GoTo CountSearchTermsError

	total = 0
	For i = 0 To MAX_TERMS - 1
		If g_terms(i) <> "" Then total = total + 1
	Next i
	For i = 0 To UBound(g_fileTerms)
		If g_fileTerms(i) <> "" Then total = total + 1
	Next i

	CountSearchTerms = total
	Exit Function

CountSearchTermsError:
	Err.Clear
End Function

Sub ShowFieldsDialog
	Dim errDesc As String
	Dim button As Integer
	Dim errNum As Long

	On Error GoTo ShowFieldsDialogError

	Call LoadCharacterFields

	If UBound(g_charFields) = 0 Then
		If g_charFields(0) = "" Then
			g_statusMessage = "The selected file has no character fields to search."
			Exit Sub
		End If
	End If

	If UBound(g_selectedFields) = 0 Then
		If g_selectedFields(0) = "" Then
			Call ResetFieldLists
		End If
	End If

	button = Dialog(g_dlgFields)

	If g_fieldsSelected = 1 Then
		Call LogInfo("Field selection confirmed: " & CountSelectedFields() & " field(s)")
	Else
		Call LogInfo("Field selection cancelled - previous selection unchanged.")
	End If
	Exit Sub

ShowFieldsDialogError:
	errNum = Err.Number
	errDesc = Err.Description
	Call LogError("Field picker failed with error " & errNum & ": " & errDesc)
	g_statusMessage = "Error " & errNum & " reading the field list: " & errDesc
	Err.Clear
End Sub

Function CountSelectedFields() As Integer
	Dim i As Integer
	Dim total As Integer

	On Error GoTo CountSelectedFieldsError

	total = 0
	For i = 0 To UBound(g_selectedFields)
		If g_selectedFields(i) <> "" Then total = total + 1
	Next i

	CountSelectedFields = total
	Exit Function

CountSelectedFieldsError:
	Err.Clear
End Function

Sub ResetFieldLists
	Dim i As Integer

	On Error GoTo ResetFieldListsError

	ReDim g_availableFields(UBound(g_charFields))
	For i = 0 To UBound(g_charFields)
		g_availableFields(i) = g_charFields(i)
	Next i

	ReDim g_selectedFields(0)
	g_selectedFields(0) = ""

	Call SortArray(g_availableFields)
	Call CompactArray(g_availableFields)
	Exit Sub

ResetFieldListsError:
	Err.Clear
End Sub

Function FieldsDialogHandler(ControlID$, Action%, SuppValue%) As Integer
	Dim keepOpen As Boolean

	On Error GoTo FieldsDialogHandlerError

	keepOpen = True

	Select Case Action%

		Case 1
			DlgListBoxArray "ListBox1", g_availableFields
			DlgListBoxArray "ListBox2", g_selectedFields
			g_listSelection1 = 0
			g_listSelection2 = 0
			DlgText "lblStatus", ""

		Case 2
			Select Case ControlID$

				Case "ListBox1"
					g_listSelection1 = SuppValue%

				Case "ListBox2"
					g_listSelection2 = SuppValue%

				Case "btnAddOne"
					Call MoveField(g_availableFields, g_selectedFields, g_listSelection1)
					g_listSelection1 = 0

				Case "btnRemoveOne"
					Call MoveField(g_selectedFields, g_availableFields, g_listSelection2)
					g_listSelection2 = 0

				Case "btnAddAll"
					Call MoveAllFields(g_availableFields, g_selectedFields)

				Case "btnRemoveAll"
					Call MoveAllFields(g_selectedFields, g_availableFields)

				Case "OKButton1"
					If g_selectedFields(0) = "" Then
						DlgText "lblStatus", "Please select at least one field."
					Else
						g_fieldsSelected = 1
						keepOpen = False
					End If

				Case "CancelButton1"
					g_fieldsSelected = 0
					keepOpen = False

			End Select

	End Select

	Call UpdateFieldButtonStates

	If keepOpen Then
		FieldsDialogHandler = 1
	Else
		FieldsDialogHandler = 0
	End If
	Exit Function

FieldsDialogHandlerError:
	Err.Clear
	FieldsDialogHandler = 1
End Function

Sub UpdateFieldButtonStates
	Dim hasAvailable As Integer
	Dim hasSelected As Integer

	On Error GoTo UpdateFieldButtonStatesError

	hasAvailable = 0
	hasSelected = 0

	If UBound(g_availableFields) > 0 Then
		hasAvailable = 1
	ElseIf g_availableFields(0) <> "" Then
		hasAvailable = 1
	End If

	If UBound(g_selectedFields) > 0 Then
		hasSelected = 1
	ElseIf g_selectedFields(0) <> "" Then
		hasSelected = 1
	End If

	DlgEnable "btnAddOne", hasAvailable
	DlgEnable "btnAddAll", hasAvailable
	DlgEnable "btnRemoveOne", hasSelected
	DlgEnable "btnRemoveAll", hasSelected
	Exit Sub

UpdateFieldButtonStatesError:
	Err.Clear
End Sub

Sub MoveField(source() As String, target() As String, itemIndex As Integer)
	Dim moved As String
	Dim source()
	Dim target()

	On Error GoTo MoveFieldError

	If itemIndex < 0 Then Exit Sub
	If itemIndex > UBound(source) Then Exit Sub

	moved = source(itemIndex)
	If moved = "" Then Exit Sub

	If target(0) = "" Then
		target(0) = moved
	Else
		ReDim Preserve target(UBound(target) + 1)
		target(UBound(target)) = moved
	End If

	source(itemIndex) = ""

	Call SortArray(source)
	Call CompactArray(source)
	Call SortArray(target)
	Call CompactArray(target)

	DlgListBoxArray "ListBox1", g_availableFields
	DlgListBoxArray "ListBox2", g_selectedFields
	Exit Sub

MoveFieldError:
	Err.Clear
End Sub

Sub MoveAllFields(source() As String, target() As String)
	Dim i As Integer
	Dim source()
	Dim target()

	On Error GoTo MoveAllFieldsError

	For i = 0 To UBound(source)
		If source(i) <> "" Then
			If target(0) = "" Then
				target(0) = source(i)
			Else
				ReDim Preserve target(UBound(target) + 1)
				target(UBound(target)) = source(i)
			End If
		End If
	Next i

	ReDim source(0)
	source(0) = ""

	Call SortArray(target)
	Call CompactArray(target)

	DlgListBoxArray "ListBox1", g_availableFields
	DlgListBoxArray "ListBox2", g_selectedFields
	Exit Sub

MoveAllFieldsError:
	Err.Clear
End Sub

Sub ShowHelpDialog
	Dim button As Integer

	On Error GoTo ShowHelpDialogError
	button = Dialog(g_dlgHelp)
	Exit Sub

ShowHelpDialogError:
	Err.Clear
End Sub

Function HelpDialogHandler(ControlID$, Action%, SuppValue%) As Integer
	Dim msg As String

	On Error GoTo HelpDialogHandlerError

	If Action% = 1 Then
		msg = "This script simplifies searching for text in character fields. "
		msg = msg & "A maximum of 10 search terms may be entered and the ? and * "
		msg = msg & "wildcards are supported. Terms containing a space are quoted "
		msg = msg & "automatically so they are searched as a phrase. "
		msg = msg & "The Advanced Search button adds predefined terms from a text "
		msg = msg & "file, one term per line, and may be combined with the terms "
		msg = msg & "typed above."
		DlgText "lblHelpText", msg
	End If

	If Action% = 2 Then
		HelpDialogHandler = 0
	Else
		HelpDialogHandler = 1
	End If
	Exit Function

HelpDialogHandlerError:
	Err.Clear
	HelpDialogHandler = 0
End Function

Function BuildSearchCriteria() As String
	Dim criteria As String
	Dim joiner As String
	Dim i As Integer

	On Error GoTo BuildSearchCriteriaError

	criteria = ""

	If g_searchType = SEARCH_AND Then
		joiner = " AND "
	Else
		joiner = " OR "
	End If

	For i = 0 To MAX_TERMS - 1
		criteria = AppendTerm(criteria, g_terms(i), joiner)
	Next i

	For i = 0 To UBound(g_fileTerms)
		criteria = AppendTerm(criteria, g_fileTerms(i), joiner)
	Next i

	BuildSearchCriteria = criteria
	Exit Function

BuildSearchCriteriaError:
	Err.Clear
End Function

Function AppendTerm(criteria As String, term As String, joiner As String) As String
	Dim cleaned As String
	Dim quote As String

	On Error GoTo AppendTermError

	AppendTerm = criteria

	cleaned = Trim(term)
	If cleaned = "" Then Exit Function

	quote = Chr(34)
	If InStr(cleaned, " ") > 0 Then
		If Left(cleaned, 1) <> quote Then
			cleaned = quote & cleaned & quote
		End If
	End If

	If criteria = "" Then
		AppendTerm = cleaned
	Else
		AppendTerm = criteria & joiner & cleaned
	End If
	Exit Function

AppendTermError:
	Err.Clear
End Function

Function RunSearch(criteria As String) As String
	Dim db As Object
	Dim resultDb As Object
	Dim task As Object
	Dim errDesc As String
	Dim fieldName As String
	Dim msg As String
	Dim resultName As String
	Dim sourceName As String
	Dim i As Integer
	Dim errNum As Long

	On Error GoTo RunSearchError

	RunSearch = ""

	If criteria = "" Then Exit Function

	sourceName = FileNameOnly(g_databaseName)
	g_prefix = ResolvePrefix(sourceName, g_prefix)
	If g_prefix = "" Then
		Call LogError("No unused file name prefix after " & MAX_PREFIX_TRIES _
			& " attempts for source " & sourceName)
		msg = "Could not find an unused file name prefix after " _
			& MAX_PREFIX_TRIES & " attempts."
		MsgBox msg, MB_OK Or MB_ICONEXCLAMATION, SCRIPT_NAME
		Exit Function
	End If

	Call LogSearchConfiguration(criteria)
	On Error GoTo RunSearchError

	Set db = Client.OpenDatabase(g_databaseName)
	Set task = db.Search

	For i = 0 To UBound(g_selectedFields)
		fieldName = g_selectedFields(i)
		If fieldName <> "" Then
			task.AddFieldToInc fieldName
		End If
	Next i

	task.RecordFilesPrefix = g_prefix

	task.PerformTask criteria, g_caseSensitive, 0, 1

	Set task = Nothing
	Set db = Nothing

	resultName = g_prefix & "-" & sourceName
	Call LogInfo("Search completed. Results database: " & resultName)
	On Error GoTo RunSearchError

	On Error Resume Next
	Err.Clear
	Set resultDb = Client.OpenDatabase(resultName)
	If Err.Number <> 0 Then
		Call LogWarn("Search succeeded but the results database could not be " _
			& "opened: " & resultName)
	End If
	Set resultDb = Nothing
	Err.Clear
	On Error GoTo RunSearchError

	RunSearch = resultName
	Exit Function

RunSearchError:
	errNum = Err.Number
	errDesc = Err.Description
	Call LogError("Search failed with error " & errNum & ": " & errDesc)
	Call LogError("Search string was: " & criteria)
	msg = "Error " & errNum & " running the search: " & errDesc _
		& g_crlf & g_crlf & "Search string: " & criteria
	MsgBox msg, MB_OK Or MB_ICONSTOP, SCRIPT_NAME
	Set task = Nothing
	Set db = Nothing
	Set resultDb = Nothing
	RunSearch = ""
End Function

Sub LogSearchConfiguration(criteria As String)
	Dim fieldList As String
	Dim searchTypeName As String
	Dim fieldCount As Integer
	Dim i As Integer

	On Error GoTo LogSearchConfigurationError

	If g_searchType = SEARCH_AND Then
		searchTypeName = "AND"
	Else
		searchTypeName = "OR"
	End If

	fieldCount = 0
	fieldList = ""
	For i = 0 To UBound(g_selectedFields)
		If g_selectedFields(i) <> "" Then
			If fieldList <> "" Then fieldList = fieldList & ", "
			fieldList = fieldList & g_selectedFields(i)
			fieldCount = fieldCount + 1
		End If
	Next i

	Call LogInfo("Source database : " & g_databaseName)
	Call LogInfo("Fields searched : " & fieldCount & " (" & fieldList & ")")
	Call LogInfo("Search type     : " & searchTypeName)
	Call LogInfo("Case sensitive  : " & g_caseSensitive & "   Whole word: 0   Advanced: 1")
	Call LogInfo("Terms typed     : " & CountTypedTerms())
	If g_termFile <> "" Then
		Call LogInfo("Term file       : " & g_termFile)
		Call LogInfo("Terms from file : " & CountFileTerms())
	End If
	Call LogInfo("Output prefix   : " & g_prefix)
	Call LogInfo("Search string   : " & criteria)
	Exit Sub

LogSearchConfigurationError:
	Err.Clear
End Sub

Function CountTypedTerms() As Integer
	Dim i As Integer
	Dim total As Integer

	On Error GoTo CountTypedTermsError

	total = 0
	For i = 0 To MAX_TERMS - 1
		If g_terms(i) <> "" Then total = total + 1
	Next i

	CountTypedTerms = total
	Exit Function

CountTypedTermsError:
	Err.Clear
End Function

Function CountFileTerms() As Integer
	Dim i As Integer
	Dim total As Integer

	On Error GoTo CountFileTermsError

	total = 0
	For i = 0 To UBound(g_fileTerms)
		If g_fileTerms(i) <> "" Then total = total + 1
	Next i

	CountFileTerms = total
	Exit Function

CountFileTermsError:
	Err.Clear
End Function

Function ResolvePrefix(sourceName As String, basePrefix As String) As String
	Dim candidate As String
	Dim uniqueName As String
	Dim i As Integer

	On Error GoTo ResolvePrefixError

	ResolvePrefix = ""
	candidate = basePrefix

	For i = 1 To MAX_PREFIX_TRIES
		uniqueName = Client.UniqueFileName(candidate & "-" & sourceName)
		If FileNameOnly(uniqueName) = candidate & "-" & sourceName Then
			ResolvePrefix = candidate
			Exit Function
		End If
		candidate = basePrefix & i
	Next i

	Exit Function

ResolvePrefixError:
	Err.Clear
	ResolvePrefix = ""
End Function

Sub PickDatabase
	Dim obj As Object
	Dim chosen As String
	Dim errDesc As String
	Dim errNum As Long

	On Error GoTo PickDatabaseError

	Set obj = Client.CommonDialogs()
	chosen = obj.FileExplorer()
	Set obj = Nothing

	If chosen = "" Then Exit Sub

	If chosen <> g_databaseName Then
		g_databaseName = chosen
		g_fieldsSelected = 0
		ReDim g_charFields(0)
		ReDim g_availableFields(0)
		ReDim g_selectedFields(0)
		Call LogInfo("Database selected: " & chosen & " (field selection reset)")
	End If
	Exit Sub

PickDatabaseError:
	Set obj = Nothing
	errNum = Err.Number
	errDesc = Err.Description
	Call LogError("Selecting a database failed with error " & errNum & ": " & errDesc)
	g_statusMessage = "Error " & errNum & " selecting a file: " & errDesc
	Err.Clear
End Sub

Sub LoadCharacterFields
	Dim db As Object
	Dim fieldDef As Object
	Dim tableDef As Object
	Dim errDesc As String
	Dim fieldCount As Integer
	Dim found As Integer
	Dim i As Integer
	Dim errNum As Long

	On Error GoTo LoadCharacterFieldsError

	ReDim g_charFields(0)
	g_charFields(0) = ""
	found = 0

	If g_databaseName = "" Then Exit Sub

	Set db = Client.OpenDatabase(g_databaseName)
	Set tableDef = db.TableDef
	fieldCount = tableDef.Count

	For i = 1 To fieldCount
		Set fieldDef = tableDef.GetFieldAt(i)
		If fieldDef.IsCharacter Then
			If found = 0 Then
				g_charFields(0) = fieldDef.Name
			Else
				ReDim Preserve g_charFields(UBound(g_charFields) + 1)
				g_charFields(UBound(g_charFields)) = fieldDef.Name
			End If
			found = found + 1
		End If
		Set fieldDef = Nothing
	Next i

	Set tableDef = Nothing
	Set db = Nothing
	Exit Sub

LoadCharacterFieldsError:
	Set fieldDef = Nothing
	Set tableDef = Nothing
	Set db = Nothing
	errNum = Err.Number
	errDesc = Err.Description
	Call LogError("Reading character fields from " & g_databaseName _
		& " failed with error " & errNum & ": " & errDesc)
	g_statusMessage = "Error " & errNum & " reading fields: " & errDesc
	Err.Clear
End Sub

Sub LoadTermFile
	Dim fso As Object
	Dim obj As Object
	Dim stream As Object
	Dim chosen As String
	Dim errDesc As String
	Dim lineText As String
	Dim found As Integer
	Dim errNum As Long

	On Error GoTo LoadTermFileError

	Set obj = Client.CommonDialogs
	chosen = obj.FileOpen("", "", "Text Files (*.txt)|*.txt|All Files (*.*)|*.*||")
	Set obj = Nothing

	If chosen = "" Then Exit Sub

	ReDim g_fileTerms(0)
	g_fileTerms(0) = ""
	found = 0

	Set fso = CreateObject("Scripting.FileSystemObject")
	Set stream = fso.OpenTextFile(chosen, 1)

	Do While Not stream.AtEndOfStream
		lineText = Trim(stream.ReadLine)
		If lineText <> "" Then
			If found = 0 Then
				g_fileTerms(0) = lineText
			Else
				ReDim Preserve g_fileTerms(UBound(g_fileTerms) + 1)
				g_fileTerms(UBound(g_fileTerms)) = lineText
			End If
			found = found + 1
		End If
	Loop

	stream.Close
	Set stream = Nothing
	Set fso = Nothing

	g_termFile = chosen
	g_statusMessage = found & " search term(s) imported from " & FileNameOnly(chosen) & "."
	Call LogInfo("Imported " & found & " search term(s) from " & chosen)
	If found = 0 Then
		Call LogWarn("Term file contained no usable terms: " & chosen)
	End If
	Exit Sub

LoadTermFileError:
	Set stream = Nothing
	Set fso = Nothing
	Set obj = Nothing
	errNum = Err.Number
	errDesc = Err.Description
	Call LogError("Reading term file failed with error " & errNum & ": " & errDesc)
	g_statusMessage = "Error " & errNum & " reading the text file: " & errDesc
	Err.Clear
End Sub

Function FileNameOnly(fullPath As String) As String
	Dim i As Integer

	On Error GoTo FileNameOnlyError

	FileNameOnly = fullPath
	For i = Len(fullPath) To 1 Step -1
		If Mid(fullPath, i, 1) = "\" Then
			FileNameOnly = Mid(fullPath, i + 1)
			Exit Function
		End If
	Next i
	Exit Function

FileNameOnlyError:
	Err.Clear
End Function

Sub SortArray(target() As String)
	Dim swapValue As String
	Dim inner As Integer
	Dim outer As Integer

	On Error GoTo SortArrayError

	For outer = 0 To UBound(target)
		For inner = outer To UBound(target)
			If UCase(target(inner)) < UCase(target(outer)) Then
				swapValue = target(outer)
				target(outer) = target(inner)
				target(inner) = swapValue
			End If
		Next inner
	Next outer
	Exit Sub

SortArrayError:
	Err.Clear
End Sub

Sub CompactArray(target() As String)
	Dim packed() As String
	Dim found As Integer
	Dim i As Integer
	Dim target()

	On Error GoTo CompactArrayError

	ReDim packed(0)
	packed(0) = ""
	found = 0

	For i = 0 To UBound(target)
		If target(i) <> "" Then
			If found = 0 Then
				packed(0) = target(i)
			Else
				ReDim Preserve packed(UBound(packed) + 1)
				packed(UBound(packed)) = target(i)
			End If
			found = found + 1
		End If
	Next i

	ReDim target(UBound(packed))
	For i = 0 To UBound(packed)
		target(i) = packed(i)
	Next i
	Exit Sub

CompactArrayError:
	Err.Clear
End Sub

Sub InitializeLogging()
	Dim timestamp As String

	On Error GoTo LogInitError

	g_errorCount = 0
	g_warningCount = 0
	g_bDisableLogging = False
	g_logInitialized = False
	g_logHandle = 0
	g_errorLogHandle = 0

	On Error Resume Next
	MkDir g_logDir
	Err.Clear
	On Error GoTo LogInitError

	timestamp = Format(Now, "yyyymmdd_hhnnss")
	g_logFileName = g_logDir & "text_search_" & timestamp & ".txt"
	g_errorLogFileName = g_logDir & "text_search_Errors_" & timestamp & ".txt"

	On Error Resume Next
	g_logHandle = LOG_HANDLE
	Open g_logFileName For Output As g_logHandle
	If Err.Number <> 0 Then
		g_bDisableLogging = True
		g_logHandle = 0
	End If

	Err.Clear
	g_errorLogHandle = ERROR_LOG_HANDLE
	Open g_errorLogFileName For Output As g_errorLogHandle
	If Err.Number <> 0 Then
		g_errorLogHandle = 0
	End If
	g_ideaVersion = GetIdeaVersion()
	g_ideaUser = GetIdeaUser()
	On Error GoTo LogInitError

	If g_logHandle > 0 Then
		Print #g_logHandle, String(79, "=")
		Print #g_logHandle, SCRIPT_NAME & " " & SCRIPT_VERSION & " - PROCESSING LOG"
		Print #g_logHandle, String(79, "=")
		Print #g_logHandle, "Start Time : " & Format(Now, "yyyy-mm-dd hh:nn:ss")
		Print #g_logHandle, "IDEA Ver   : " & g_ideaVersion
		Print #g_logHandle, "User       : " & g_ideaUser
		Print #g_logHandle, "Working Dir: " & g_workingDirectory
		Print #g_logHandle, String(79, "=")
		Print #g_logHandle, ""
	End If

	g_logInitialized = True
	Exit Sub

LogInitError:
	g_bDisableLogging = True
	Err.Clear
End Sub

Sub WriteToLog(msg As String)
	If g_bDisableLogging Then Exit Sub
	If Not g_logInitialized Then Exit Sub
	If g_logHandle <= 0 Then Exit Sub

	On Error Resume Next
	Print #g_logHandle, Format(Now, "yyyy-mm-dd hh:nn:ss") & " - " & msg
	Err.Clear
End Sub

Sub WriteErrorLog(msg As String)
	If g_bDisableLogging Then Exit Sub
	If Not g_logInitialized Then Exit Sub
	If g_errorLogHandle <= 0 Then Exit Sub

	On Error Resume Next
	Print #g_errorLogHandle, Format(Now, "yyyy-mm-dd hh:nn:ss") & " - " & msg
	Err.Clear
End Sub

Sub LogInfo(msg As String)
	Call WriteToLog("[INFO]  " & msg)
End Sub

Sub LogWarn(msg As String)
	g_warningCount = g_warningCount + 1
	Call WriteToLog("[WARN]  " & msg)
End Sub

Sub LogError(msg As String)
	g_errorCount = g_errorCount + 1
	Call WriteToLog("[ERROR] " & msg)
	Call WriteErrorLog(msg)
End Sub

Sub FinalizeLog()
	If g_bDisableLogging Then Exit Sub
	If Not g_logInitialized Then Exit Sub

	On Error Resume Next

	If g_logHandle > 0 Then
		Print #g_logHandle, ""
		Print #g_logHandle, String(79, "=")
		Print #g_logHandle, "SUMMARY"
		Print #g_logHandle, String(79, "=")
		Print #g_logHandle, "End Time       : " & Format(Now, "yyyy-mm-dd hh:nn:ss")
		Print #g_logHandle, "Total Errors   : " & g_errorCount
		Print #g_logHandle, "Total Warnings : " & g_warningCount
		Print #g_logHandle, String(79, "=")
		Close g_logHandle
		g_logHandle = 0
	End If

	If g_errorLogHandle > 0 Then
		Close g_errorLogHandle
		g_errorLogHandle = 0
	End If

	If g_errorCount = 0 Then
		If g_errorLogFileName <> "" Then
			If Dir(g_errorLogFileName) <> "" Then Kill g_errorLogFileName
		End If
	End If

	g_logInitialized = False
	Err.Clear
End Sub

Function GetIdeaVersion() As String
	Dim ideaConfig As Object
	Dim installInfo As Object
	Dim sVer As String

	sVer = ""

	On Error Resume Next
	Set installInfo = CreateObject("Idea.InstallInfo")
	If Err.Number = 0 Then
		sVer = CStr(installInfo.Version)
	End If
	Set installInfo = Nothing
	Err.Clear

	If sVer = "" Then
		Set ideaConfig = CreateObject("Idea.configureIDEA")
		If Err.Number = 0 Then
			sVer = CStr(ideaConfig.VersionInCurrentUser)
		End If
		Set ideaConfig = Nothing
		Err.Clear
	End If

	If sVer = "" Then sVer = "Unknown"
	GetIdeaVersion = sVer
End Function

Function GetIdeaUser() As String
	Dim net As Object
	Dim sUser As String

	sUser = ""

	On Error Resume Next
	Set net = CreateObject("WScript.Network")
	If Err.Number = 0 Then
		sUser = CStr(net.UserName)
	End If
	Set net = Nothing
	Err.Clear

	If sUser = "" Then sUser = "Unknown"
	GetIdeaUser = sUser
End Function
