# knowledge.tcl --
#
#	The knowledge window(s) and related commands.
#

namespace eval NSKnowledge {

variable Priv

# NSKnowledge::InitModule --
#
#	One-time-only-ever initialization.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitModule {} {

	global Windows
	variable Priv

	set Priv(hook) {}
	lappend Priv(hook) know_hook_artifact Artifacts
	lappend Priv(hook) know_hook_monster Monsters
	lappend Priv(hook) know_hook_mutation Mutations
	lappend Priv(hook) know_hook_pet Pets
	lappend Priv(hook) know_hook_quest Quests

	set Priv(font) [Global font,fixed,normal]

	# Create the Knowledge Window
	NSObject::New NSKnowledge
}

# NSKnowledge::NSKnowledge --
#
#	Object constructor called by NSObject::New().
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc NSKnowledge oop {

	global Windows
	variable Priv

	if $::DEBUG {Verify $oop}

	InitWindow $oop

	Info $oop group,current -1
	Info $oop member,current -1

	# Info about selected group & member for each hook
	foreach {hook label} $Priv(hook) {
		Info $oop group,$hook 0
		Info $oop member,$hook -1
	}
	
	set win [Info $oop win]

	# Set the hook, but don't set the list
	Info $oop hook know_hook_monster
	Info $oop radio,hook know_hook_monster

	set Priv(find,string) ""
	set Priv(find,group) 0
	set Priv(find,member) 0

	NSWindowManager::RegisterWindow knowledge [Info $oop win] \
		"NSKnowledge::GeometryCmd $oop" "" "NSKnowledge::DisplayCmd $oop"

	#
	# Global list of application windows
	#

	set Windows(knowledge,win) [Info $oop win]
	set Windows(knowledge,class) NSKnowledge
	set Windows(knowledge,oop) $oop
}

# NSKnowledge::Info --
#
#	Query and modify info.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Info {oop info args} {

	global NSKnowledge

	# Set info
	if {[llength $args]} {
		switch -- $info {
			default {
				set NSKnowledge($oop,$info) [lindex $args 0]
			}
		}

	# Get info
	} else {
		switch -- $info {
			groupCanvas {
				set canvistId [Info $oop group,canvistId]
				return [NSCanvist::Info $canvistId canvas]
			}
			memberCanvas {
				set canvistId [Info $oop member,canvistId]
				return [NSCanvist::Info $canvistId canvas]
			}
			default {
				return $NSKnowledge($oop,$info)
			}
		}
	}
}

# NSKnowledge::InitWindow --
#
#	Create the window.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitWindow oop {

	global Windows
	variable Priv

	set win .knowledge$oop
	toplevel $win
	wm title $win Knowledge

	wm transient $win $Windows(main,win)

	# Do stuff when window closes
	wm protocol $win WM_DELETE_WINDOW "NSKnowledge::Close $oop"

	# Start out withdrawn (hidden)
	wm withdraw $win

	Info $oop win $win

	#
	# Menus
	#

	InitMenus $oop

	#
	# Divider
	#

	frame $win.divider2 \
		-borderwidth 1 -height 2 -relief groove

	#
	# Tabs!
	#

	set tabsId [NSObject::New NSTabs $win]
	foreach {hook label} $Priv(hook) {
		NSTabs::Add $tabsId $label
	}
	NSTabs::Info $tabsId invokeCmd "NSKnowledge::InvokeTab $oop"
	NSTabs::Info $tabsId active 1
	Info $oop tabsId $tabsId

	#
	# Group List
	#

	set cw [font measure $Priv(font) "W"]
	set width [expr $cw * 26]
	set iconSize [expr [icon size] + 8]

	frame $win.frameGroup \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $win.frameGroup $iconSize $width 300 \
		"NSKnowledge::NewItemCmd $oop" "NSKnowledge::HighlightItemCmd $oop"]
	set canvas [NSCanvist::Info $canvistId canvas]
	$canvas configure -background [NSColorPreferences::Get listBG]
	$canvas configure -yscrollcommand "$win.frameGroup.scroll set"
	scrollbar $win.frameGroup.scroll \
		-borderwidth 0 -command "$canvas yview" -orient vert

	# When a group is selected, show the artifacts/monsters in it
	NSCanvist::Info $canvistId selectionCmd \
		"NSKnowledge::SelectionChanged_Group $oop"

	# This call updates the list background color whenever the
	# global list background color changes
	Info $oop group,clientId \
		[NSValueManager::AddClient listBG "ListBackgroundChanged $canvas"]

	pack $win.frameGroup.scroll -side right -fill y
	pack $canvas -side left -expand yes -fill both -anchor nw

	Info $oop group,canvistId $canvistId

	#
	# Member List
	#

	set width [expr $cw * 50]

	frame $win.frame \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $win.frame $iconSize $width 300 \
		"NSKnowledge::NewItemCmd $oop" "NSKnowledge::HighlightItemCmd $oop"]
	set canvas [NSCanvist::Info $canvistId canvas]
	$canvas configure -background [NSColorPreferences::Get listBG]
	$canvas configure -yscrollcommand "$win.frame.scroll set"
	scrollbar $win.frame.scroll \
		-borderwidth 0 -command "$canvas yview" -orient vert

	# This call updates the list background color whenever the
	# global list background color changes
	Info $oop member,clientId \
		[NSValueManager::AddClient listBG "ListBackgroundChanged $canvas"]

	# When a artifact/monster is selected, recall it
	NSCanvist::Info $canvistId selectionCmd \
		"NSKnowledge::SelectionChanged_Member $oop"

	bind $canvas <Configure> "
		NSKnowledge::Configure $oop $canvas
	"

	Info $oop member,canvistId $canvistId

	pack $win.frame.scroll -side right -fill y
	pack $canvas -side left -expand yes -fill both -anchor nw

	#
	# Statusbar
	#

	frame $win.statusBar -relief flat -borderwidth 0
	label $win.statusBar.label -anchor w -relief sunken -padx 2
	label $win.statusBar.label2 -anchor w -relief sunken -padx 2 -width 20
	pack $win.statusBar.label -side left -expand yes -fill both
	pack $win.statusBar.label2 -side right -expand no

	#
	# Geometry
	#

	grid rowconfig $win 0 -weight 0 -minsize 0
	grid rowconfig $win 1 -weight 0 -minsize 0
	grid rowconfig $win 2 -weight 1 -minsize 0
	grid rowconfig $win 3 -weight 0 -minsize 0
	grid columnconfig $win 0 -weight 0 -minsize 0
	grid columnconfig $win 1 -weight 1 -minsize 0
 
	grid $win.divider2 \
		-row 0 -column 0 -rowspan 1 -columnspan 2 -sticky ew -pady 2
	grid [NSTabs::Info $tabsId canvas] \
		-row 1 -column 0 -rowspan 1 -columnspan 2 -sticky ew
	grid $win.frameGroup \
		-row 2 -column 0 -rowspan 1 -columnspan 1 -sticky ns
	grid $win.frame \
		-row 2 -column 1 -rowspan 1 -columnspan 1 -sticky news
	grid $win.statusBar -in $win \
		-row 3 -column 0 -rowspan 1 -columnspan 2 -sticky ew

	#
	# Feed Term when keys pressed
	#

	bind $win <KeyPress-Escape> "NSKnowledge::Close $oop"
	bind $win <Control-KeyPress-w> "NSKnowledge::Close $oop"

	bind $win <KeyPress-f> "NSKnowledge::Find $oop 0"
	bind $win <KeyPress-g> "NSKnowledge::Find $oop 1"

	# These two are for get_check() in know_hook_pet
	bind $win <KeyPress-y> {angband keypress y}
	bind $win <KeyPress-n> {angband keypress n}

	#
	# Synch the scrollbars when window is shown.
	# Hmmm. This doesn't work if you bind to $win.frame.scroll...
	#

	bind $win.frameGroup.scroll <Map> "NSKnowledge::SynchScrollBars $oop"
}

# NSKnowledge::InitMenus --
#
#	Create the menus.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitMenus oop {

	global NSKnowledge
	global NSMenu
	variable Priv

	set win [Info $oop win]

	set mod "Ctrl"

	#
	# Menu bar
	#

	set menuDef "-tearoff 0 -postcommand \"NSKnowledge::SetupMenus $oop\" -identifier MENUBAR"
	set mbar [NSObject::New NSMenu $win $menuDef]
	Info $oop mbar $mbar

	#
	# Knowledge Menu
	#

	NSObject::New NSMenu $mbar {-tearoff 0 -identifier MENU_KNOWLEDGE}
	NSMenu::MenuInsertEntry $mbar -end MENUBAR {-type cascade -menu MENU_KNOWLEDGE -label "Knowledge" -underline 0 -identifier M_KNOWLEDGE}

	set entries {}
	set i 1
	foreach {hook label} $Priv(hook) {
		lappend entries "-type radiobutton -label \"$label\" \
			-variable NSKnowledge($oop,radio,hook) -value $hook
			-command \"NSKnowledge::SetHook $oop $hook\" \
			-accelerator $i -identifier E_HOOK_$i"
		bind $win <KeyPress-$i> "NSKnowledge::SetHook $oop $hook"
		incr i
	}
	lappend entries "-type separator"
	lappend entries "-type command -label \"Find...\" \
		-command \"NSKnowledge::Find $oop 0\" -accelerator f \
		-underline 0 -identifier E_FIND"
	lappend entries "-type command -label \"Find Again\" \
		-command \"NSKnowledge::Find $oop 1\" -accelerator g \
		-underline 6 -identifier E_FINDAGAIN"
	lappend entries "-type separator"
	lappend entries "-type command -label \"Close\" \
		-command \"NSKnowledge::Close $oop\" -underline 0 \
		-accelerator $mod+W -identifier E_CLOSE"
	
	NSMenu::MenuInsertEntries $mbar -end MENU_KNOWLEDGE $entries

	#
	# Hook Menu
	#

	set m $NSMenu($mbar,menu).hook
	menu $m -tearoff 0
	Info $oop hookMenu,menu $m
	Info $oop hookMenu,inserted 0
}

# NSKnowledge::SetupMenus --
#
#	Prepare to post the menus.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetupMenus {oop mbarID} {

	variable Priv

	set i 0
	foreach {hook label} $Priv(hook) {
		lappend identList E_HOOK_[incr i]
	}

	lappend identList E_FIND E_FINDAGAIN E_CLOSE

	if {[Info $oop hookMenu,inserted]} {
		set menu [Info $oop hookMenu,menu]
		set last [$menu index end]
		for {set i 0} {$i <= $last} {incr i} {
			if {[$menu type $i] == "separator"} continue
			$menu entryconfigure $i -state disabled
		}
		CallHook $oop menu_setup
	}

	NSMenu::MenuEnable $mbarID $identList
}

# NSKnowledge::DisplayCmd --
#
#	Called by NSWindowManager::Display().
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc DisplayCmd {oop message first args} {

	switch -- $message {
		preDisplay {
			SetHook $oop [Info $oop hook]
		}
		postDisplay {
		}
		postWithdraw {
			StateRemember $oop
			NSCanvist::Delete [Info $oop group,canvistId] 0 end
			NSCanvist::Delete [Info $oop member,canvistId] 0 end
		}
	}
}

# NSKnowledge::GeometryCmd --
#
#	Called by NSWindowManager::Setup(). Returns the desired (default)
#	geometry for the window.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc GeometryCmd oop {

	global Windows

	set win [Info $oop win]
	set winMain $Windows(main,win)
	set winMicro $Windows(micromap,win)
	set x [winfo x $winMain]
	set y [winfo y $winMain]
	set width [NSToplevel::ContentWidth $win [expr [NSToplevel::EdgeRight $winMicro] - $x]]
	set height [NSToplevel::ContentHeight $win [expr [NSToplevel::EdgeBottom $winMain] - $y]]
	return ${width}x$height+$x+$y
}

# NSKnowledge::Close --
#
#	Do something when closing the window.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Close oop {

	angband keypress \033
}

# NSKnowledge::StateRemember --
#
#	Remember the selected group and member, then clear the lists.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc StateRemember oop {

	global Angband

	### Mega-Hack -- Don't do this during geometry foolishness
#	if !$NSWindowManager::Priv(knowledge,setup) return

	set hook [Info $oop hook]

	# Because we are clearing the lists here, and don't want to
	# upset the user, I save the selected group/member so it can
	# be restored in Map() below.

	# Because the contents of a group change as the character gains
	# knowledge, I save the proper index, not the row, of the
	# selected group/member.

	set row [Info $oop group,current]
	if {$row != -1} {
		set index [lindex [Info $oop group,match] $row]
	} else {
		set index -1
	}
	Info $oop group,$hook $index

	set row [Info $oop member,current]
	if {$row != -1} {
		set index [lindex [Info $oop member,match] $row]
	} else {
		set index -1
	}
	Info $oop member,$hook $index
}

# NSKnowledge::StateRestore --
#
#	Restore the display.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc StateRestore oop {

	global Angband

	### Mega-Hack -- Don't do this during geometry foolishness
#	if !$NSWindowManager::Priv(knowledge,setup) return

	set hook [Info $oop hook]
	
	# Restore the selected group. It might not be there if the
	# "cheat_know" option was turned off.
	set current [Info $oop group,$hook]
	if {$current != -1} {
		set row [lsearch -exact [Info $oop group,match] $current]
		if {$row != -1} {
			set canvistId [Info $oop group,canvistId]
			NSCanvist::UpdateSelection $canvistId $row {}
			NSCanvist::See $canvistId $row
		} else {
			Info $oop member,$hook -1
		}
	}

	# Restore the selected member. It might not be there if the
	# "cheat_know" option was turned off.
	set current [Info $oop member,$hook]
	if {$current != -1} {
		set row [lsearch -exact [Info $oop member,match] $current]
		if {$row != -1} {
			set canvistId [Info $oop member,canvistId]
			NSCanvist::UpdateSelection $canvistId $row {}
			NSCanvist::See $canvistId $row
		}
	}
}

# NSKnowledge::CallHook --
#
#	Call the hook.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc CallHook {oop message args} {

	uplevel #0 eval NSKnowledge::[Info $oop hook] $oop $message [list $args]
}

# NSKnowledge::SetHook --
#
#	Set the hook.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetHook {oop hook} {

	global NSMenu
	variable Priv

	if {$hook != [Info $oop hook]} {
		set again 0
	} else {
		set again 1
	}

	if {!$again} {
		StateRemember $oop
	}

	# Remember the hook
	Info $oop hook $hook

	# Clear the hook-specific menu
	set hookMenu [Info $oop hookMenu,menu]
	$hookMenu delete 0 end
	eval destroy [winfo children $hookMenu]

	# Show icons by default
	ConfigureList_Member $oop 1

	# Set the group list
	SetList_Group $oop

	# If the hook-menu is empty, remove it, otherwise insert it
	set mbarId [Info $oop mbar]
	if {[$hookMenu index end] == "none"} {
		if {[Info $oop hookMenu,inserted]} {
			$NSMenu($mbarId,menu) delete 2
			Info $oop hookMenu,inserted 0
		}
	} else {
		set index [lsearch -exact $Priv(hook) $hook]
		if {![Info $oop hookMenu,inserted]} {
			$NSMenu($mbarId,menu) add cascade -menu $hookMenu
			Info $oop hookMenu,inserted 1
		}
		$NSMenu($mbarId,menu) entryconfigure end \
			-label [lindex $Priv(hook) [incr index]]
	}

	# Radiobutton menu entries
	Info $oop radio,hook $hook

	set tabsId [Info $oop tabsId]
	set index [expr [lsearch -exact $Priv(hook) $hook] / 2]
	if {$index != [NSTabs::Info $tabsId current]} {
		NSTabs::Smaller $tabsId [NSTabs::Info $tabsId current]
		NSTabs::Bigger $tabsId $index
		NSTabs::Info $tabsId current $index
	}

	[Info $oop win].statusBar.label2 configure -text ""

	StateRestore $oop
}

# NSKnowledge::SelectionChanged_Group --
#
#	When a "group" list item is selected, display artifacts/monsters
#	in the "member" list.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged_Group {oop canvistId select deselect} {

	global NSCanvist

	# If nothing was selected, clear the member list
	if {![llength $select]} {
		set canvistId [Info $oop member,canvistId]
		NSCanvist::Delete $canvistId 0 end
		Info $oop group,current -1
		StatusBar $oop "" 0
		return
	}

	# Get the (first) row
	set row [lindex $select 0]
	Info $oop group,current $row

	# Add matching artifacts/monsters to the list
	SetList_Member $oop $row
}

# NSKnowledge::SelectionChanged_Member --
#
#	When a "member" list item is selected, display memory for the
#	artifact/monster in the Recall Window.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SelectionChanged_Member {oop canvistId select deselect} {

	global NSCanvist
	global Windows

	# Do nothing if no new row was selected
	if {![llength $select]} {
		Info $oop member,current -1
		StatusBar $oop "" 0
		return
	}

	# Get the (first) row
	set row [lindex $select 0]
	Info $oop member,current $row

	# Call the hook to do other stuff (recall, etc)
	CallHook $oop select_member $row
}

# NSKnowledge::SetList_Group --
#
#	Set the group list.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList_Group oop {

	set win [Info $oop win]
	set canvistId [Info $oop group,canvistId]

	# Feedback
	StatusBar $oop "Displaying..." 0
	update idletasks

	# Clear the list
	NSCanvist::Delete $canvistId 0 end

	# Call hook to set the group list
	CallHook $oop set_list_group

	Info $oop group,current -1

	# Hack -- Clear the "member" list
	NSCanvist::Delete [Info $oop member,canvistId] 0 end

	# Feedback
	StatusBar $oop "Done." 1
}

# NSKnowledge::SetList_Member --
#
#	Set the member list.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList_Member {oop group} {

	set canvistId [Info $oop member,canvistId]

	# Clear the list
	NSCanvist::Delete $canvistId 0 end

	Info $oop member,current -1

	# Call hook to set the member list
	CallHook $oop set_list_member $group
}

# NSKnowledge::ConfigureList_Member --
#
#	Set things up so the member list doesn't display icons.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc ConfigureList_Member {oop show_icons} {

	set canvistId [Info $oop member,canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]

	if $show_icons {
		set rowHgt [expr [icon size] + 8]
	} else {
		set rowHgt 20
	}	
	NSCanvist::Info $canvistId rowHgt $rowHgt
	$canvas configure -yscrollincrement $rowHgt
}

# NSKnowledge::SynchScrollBars --
#
#	There is a bug (my bug or in Tk?) which prevents the scroll bars
#	from synchronizing when the window is not mapped. So I bind to
#	the <Map> event and synch the scroll bars here.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SynchScrollBars oop {

	set win [Info $oop win]

	# Group list
	set canvistId [Info $oop group,canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]

	eval $win.frameGroup.scroll set [$canvas yview]

	# Monster/Artifact list
	set canvistId [Info $oop member,canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]

	eval $win.frame.scroll set [$canvas yview]
}

# NSKnowledge::StatusBar --
#
#	Display text in the status bar, perhaps clearing it later.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc StatusBar {oop text zap} {

	set win [Info $oop win]
	set label $win.statusBar.label
	$label configure -text $text
	if $zap {
		NSUtils::ZapLabel $label
	}
}

# NSKnowledge::NewItemCmd --
#
#	Called by NSCanvist::InsertItem() to create a list row.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc NewItemCmd {oop canvistId y iconSpec text {color White}} {

	variable Priv

	set c [NSCanvist::Info $canvistId canvas]
	set lineHeight [NSCanvist::Info $canvistId rowHgt]
	set font $Priv(font)

	set cw [font measure $font "W"]
	set fw [font measure $font $text]
	set fh [font metrics $font -linespace]
	set diff [expr int([expr ($lineHeight - $fh) / 2])]

	# Selection rectangle around everything
	lappend itemIdList [$c create rectangle 2 [expr $y + 2] \
		[expr [winfo width $c] - 3] [expr $y + $lineHeight - 2] \
		-fill {} -outline {} -tags {enabled selrect} -width 2.0]

	# Image
	if {$iconSpec != ""} {
		set iw [icon size]
		set ih [icon size]
		set wid [expr [icon size] + 8]
		set xdiff [expr int([expr ($wid - $iw) / 2])]
		set ydiff [expr int([expr ($lineHeight - $ih) / 2])]
		scan $iconSpec "%s %d" type index
		lappend itemIdList [$c create widget $xdiff [expr $y + $ydiff] \
			-type $type -index $index -tags enabled]
	} else {
		set wid 3
	}

	# Text
	lappend itemIdList [$c create text [expr $wid + 1] [expr $y + $diff] \
		-text $text -anchor nw -font $font -fill $color -tags enabled]

	return $itemIdList
}

# NSKnowledge::HighlightItemCmd --
#
#	Called by NSCanvist::Select() to highlight a row.
#
# Arguments:
#	oop					OOP ID. See above.
#	canvistId					OOP ID of NSCanvist object.
#	state					1 or 0 highlight state.
#	args					List of canvas item ids
#
# Results:
#	What happened.

proc HighlightItemCmd {oop canvistId state args} {

	global NSCanvist

	set canvas $NSCanvist($canvistId,canvas)
	set itemIdList $args

	if {[NSUtils::HasFocus $canvas]} {
		set fill [NSColorPreferences::Get listHilite]
	} else {
		set fill [NSColorPreferences::Get listInactive]
	}

	set idRect [lindex $itemIdList 0]

	if $state {
		$canvas itemconfigure $idRect -outline $fill

	} else {
		$canvas itemconfigure $idRect -fill {} -outline {}
	}
}

# NSKnowledge::Configure --
#
#	Called as a <Configure> event script. Resizes the selection rectangles
#	so they fit properly.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Configure {oop canvas} {

	foreach itemId [$canvas find withtag selrect] {
		set coords [$canvas coords $itemId]
		set right [expr [winfo width $canvas] -3]
		set coords [lreplace $coords 2 2 $right]
		eval $canvas coords $itemId $coords
	}
}

# NSKnowledge::InvokeTab --
#
#	Called when a tab is clicked.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InvokeTab {oop tabsId tabId} {

	variable Priv
	set index [lsearch -exact [NSTabs::Info $tabsId id] $tabId]
	SetHook $oop [lindex $Priv(hook) [expr $index * 2]]
}


# NSKnowledge::Find --
#
#	Simple search routine to look for a member by name.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Find {oop again} {

	variable Priv

	# Repeat the last search
	if {$again && [string length $Priv(find,string)]} {
		set string $Priv(find,string)
		incr Priv(find,member)

	# Enter a string to find, start from the beginning
	} else {

		# Ask the user for a name
		set string [NSUtils::StringBox "Find" $Priv(find,string) \
			Name "Cancel Find" [Info $oop win]]
		if {$string == ""} return

		# Clean up after the dialog, give message
		StatusBar $oop "Searching..." 1
		update

		# Reset search parameters
		set Priv(find,string) $string
		set Priv(find,group) 0
		set Priv(find,member) 0
	}

	set group $Priv(find,group)
	set member $Priv(find,member)
	set max [NSCanvist::Info [Info $oop group,canvistId] count]

	# Compare lowercase
	set string [string tolower $string]

	for {set i $group} {$i < $max} {incr i} {

		set match [lrange [CallHook $oop member_list $i] $member end]

		foreach index $match {
	
			# Get the member name
			set name2 [CallHook $oop member_name $index]

			# Compare lowercase
			set name2 [string tolower $name2]
	
			# Found a match
			if {[string first $string $name2] != -1} {
	
				set canvistId [Info $oop group,canvistId]
	
				# The new group is not displayed
				if {![NSCanvist::IsRowSelected $canvistId $i]} {

					# Select the new group. As a side effect, the
					# SetList_Member() is called to display the monsters in
					# the group.
					NSCanvist::RemoveSelection $canvistId
					NSCanvist::UpdateSelection $canvistId $i {}
					NSCanvist::See $canvistId $i

				# The new group is already selected
				} else {

					# The found monster is in the currently-displayed
					# group, so remove the current selection
					NSCanvist::RemoveSelection [Info $oop member,canvistId]
				}
	
				set canvistId [Info $oop member,canvistId]
	
				# Select the matching member
				NSCanvist::UpdateSelection $canvistId $member {}
				NSCanvist::See $canvistId $member
	
				set Priv(find,group) $i
				set Priv(find,member) $member

				# Clear "Searching..." message
				StatusBar $oop "" 0

				# Done
				return
			}
			incr member
		}
		set member 0
	}

	set Priv(find,group) 0
	set Priv(find,member) 0

	# If we aren't searching from the start, then wrap around if
	# a match isn't found
	if {$group || $member} {
		Find $oop 1
		return
	}

	StatusBar $oop "No match for \"$string\"." 1
}

# NSKnowledge::Verify --
#
#	Check that all the objects, monsters, and artifacts in the
#	are accounted for in the groups,x_info lists.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Verify oop {

	foreach info {a_info k_info r_info} {
		set max [angband $info max]
		for {set idx 1} {$idx < $max} {incr idx} {
			set match($idx) 0
		}
		foreach {label findSpec} [Global groups,$info] {
			if {$info == "r_info"} {set findSpec "-unique no $findSpec"}
			foreach idx [eval angband $info find $findSpec] {
				if $match($idx) {
					error "$info #$idx in more that one group"
				}
				set match($idx) 1
			}
		}
		for {set idx 1} {$idx < $max} {incr idx} {
			if {[angband $info exists $idx] && !$match($idx)} {
				error "$info #$idx not in any group"
			}
		}
	}
}

proc know_hook_artifact {oop message args} {

	global Windows

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]

			# Keep a list of matching indexes
			set match {}

			# Count total number of (known) artifacts
			set total 0
			
			set index 0
			foreach {title findSpec} [Global groups,a_info] {

if 1 {
				set color White
				
				# I want to visually indicate whether an artifact in this
				# group is currently in the dungeon. Currently I get all
				# the (known) artifacts in the group and see if any one is
				# in the dungeon. I could add a search parameter such as
				# "-location XXX". This method works, but might become
				# slow when many artifacts are known.
				
				set match2 [eval angband a_info find -known yes $findSpec]
				foreach a_idx $match2 {
					if {[angband a_info location $a_idx] == "dungeon"} {
						set color LawnGreen
						break
					}
				}

				incr total [llength $match2]
} else {
				# Find the last artifact in the group
				set match2 [eval angband a_info find -known yes \
					-limit 1 -backwards $findSpec]
}
				# The group is not empty
				if {[llength $match2]} {

					# Get the icon
					angband a_info info [lindex $match2 0] attrib
					set icon $attrib(icon)
			
					# Add this group to the list
					NSCanvist::Insert $canvistId end $icon $title $color
	
					# Keep a list of matching indexes
					lappend match $index
				}

				incr index
			}

			# Keep a list of matching indexes
			Info $oop group,match $match

			# Display
			StatusBar $oop "$total Artifacts" 2
		}

		set_list_member {

			set canvistId [Info $oop member,canvistId]

			set group [lindex [Info $oop group,match] [lindex $args 0]]
			set findSpec [lindex [Global groups,a_info] [expr $group * 2 + 1]]

			# Get a list of monsters in the group
			set match [eval angband a_info find -known yes $findSpec]

			# Add each match to the list
			foreach index $match {
		
				# Get the icon and name
				angband a_info info $index attrib

				# Get the location
				set location [angband a_info location $index]

				# If it's in the dungeon, colorize it!
				if {$location == "dungeon"} {
					set color LawnGreen
				} else {
					set color White
				}
				
				# Append match to the list
				NSCanvist::Insert $canvistId end $attrib(icon) $attrib(name) \
					$color
		
				# Hack -- Keep a list of matching indexes
				lappend Priv(index) $index
			}

			# Keep a list of matching indexes
			Info $oop member,match $match

			# Display number of members
			set num [llength $match]
			if {!$num || $num > 1} {
				set s s
			} else {
				set s ""
			}
			[Info $oop win].statusBar.label2 configure -text "$num artifact$s"
		}

		select_member {
			set row [lindex $args 0]
			set index [lindex [Info $oop member,match] $row]

			# Display memory for the selected artifact
			NSRecall::Recall $Windows(recall,oop) a_info $index

			# Hack -- Where is this artifact?
			set location [angband a_info location $index]
			switch -- $location {
				equipment -
				inventory {set s "This artifact is in your $location."}
				dungeon {set s "This artifact is in the dungeon."}
				home {set s "This artifact is in your home."}
				unknown {set s "You don't know where this artifact is."}
				default {set s "Unknown location \"$location\"."}
			}
			StatusBar $oop $s 0
		}

		member_name {
			angband a_info info [lindex $args 0] attrib
			return $attrib(name)
		}

		member_list {
			set group [lindex [Info $oop group,match] [lindex $args 0]]
			set findSpec [lindex [Global groups,a_info] [expr $group * 2 + 1]]
			return [eval angband a_info find -known yes $findSpec]
		}
	}	
}

proc know_hook_monster {oop message args} {

	global Windows

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]

			# Keep a list of matching indexes
			set match {}

			set index 0
			foreach {title findSpec} [Global groups,r_info] {

				# Find the last monster in the group
				set match2 [eval angband r_info find -known yes -unique no \
					-limit 1 -backwards $findSpec]

				# The group is not empty
				if {[llength $match2]} {

					# Get the icon
					angband r_info info [lindex $match2 0] attrib
					set icon $attrib(icon)
			
					# Add this group to the list
					NSCanvist::Insert $canvistId end $icon $title
	
					# Keep a list of matching indexes
					lappend match $index
				}

				incr index
			}

			# Keep a list of matching indexes
			Info $oop group,match $match
		}

		set_list_member {

			set canvistId [Info $oop member,canvistId]

			set group [lindex [Info $oop group,match] [lindex $args 0]]
			set findSpec [lindex [Global groups,r_info] [expr $group * 2 + 1]]

			# Get a list of monsters in the group
			set match [eval angband r_info find -known yes -unique no $findSpec]

			# Add each match to the list
			foreach index $match {
		
				# Get the icon and name
				angband r_info info $index attrib
		
				# Append match to the list
				NSCanvist::Insert $canvistId end $attrib(icon) $attrib(name)
		
				# Hack -- Keep a list of matching indexes
				lappend Priv(index) $index
			}

			# Keep a list of matching indexes
			Info $oop member,match $match

			# Display number of members
			set num [llength $match]
			if {!$num || $num > 1} {
				set s s
			} else {
				set s ""
			}
			[Info $oop win].statusBar.label2 configure -text "$num monster$s"
		}

		select_member {
			set row [lindex $args 0]
			set index [lindex [Info $oop member,match] $row]

			# Display memory for the selected monster
			NSRecall::Recall $Windows(recall,oop) r_info $index
		}

		member_name {
			angband r_info info [lindex $args 0] attrib
			return $attrib(name)
		}

		member_list {
			set group [lindex [Info $oop group,match] [lindex $args 0]]
			set findSpec [lindex [Global groups,r_info] [expr $group * 2 + 1]]
			return [eval angband r_info find -known yes -unique no $findSpec]
		}
	}	
}

proc know_hook_mutation {oop message args} {

	global Windows

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]

			# Use character icon
			set icon [icon assign -to character -toindex 0]

			# Add this group to the list
			NSCanvist::Insert $canvistId end $icon Mutations

			# Keep a list of matching indexes
			Info $oop group,match 0

			# Don't show icons in the member list
			ConfigureList_Member $oop 0
		}

		set_list_member {

			set canvistId [Info $oop member,canvistId]

			set num 0

			# Add each mutation to the list
			foreach desc [angband player mutations] {
		
				# Append mutation to the list
				NSCanvist::Insert $canvistId end "" $desc

				incr num
			}

			# Keep a list of matching indexes
			Info $oop member,match {}

			# Display number of members
			if {!$num || $num > 1} {
				set s s
			} else {
				set s ""
			}

			[Info $oop win].statusBar.label2 configure -text "$num mutation$s"
		}

		select_member {
		}

		member_name {
			return ""
		}

		member_list {
			return {}
		}
	}	
}

proc know_hook_pet {oop message args} {

	global Windows

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]

			# Create a list of m_idx/r_idx pairs
			set match2 {}
			foreach m_idx [angband player pets] {
				set r_idx [debug dumptype monster_type $m_idx r_idx]
				lappend match2 [list $m_idx $attrib(r_idx)]
			}

			# Sort based on increasing r_idx, then break up into
			# m_idx/r_idx lists

			set match [set r_match {}]
			if {[llength $match2]} {
				set match2 [lsort -integer -index 1 $match2]
				foreach elem $match2 {
					lappend match [lindex $elem 0]
					lappend r_match [lindex $elem 1]
				}
			}
			Info $oop member,match $match
			Info $oop member,r_match $r_match

			# Use icon of last pet
			if {[llength [Info $oop member,r_match]]} {
				set r_idx [lindex [Info $oop member,r_match] end]
				set icon [icon assign -to monster -toindex $r_idx]

			# Use character icon if no pets
			} else {
				set icon [icon assign -to character -toindex 0]
			}

			# Add this group to the list
			NSCanvist::Insert $canvistId end $icon Pets

			# Keep a list of matching indexes
			Info $oop group,match 0

			# Set the Hook Menu
			set hookMenu [Info $oop hookMenu,menu]
			$hookMenu add command -label "Dismiss" \
				-command "NSKnowledge::CallHook $oop menu_cmd dismiss"
			$hookMenu add command -label "Dismiss All" \
				-command "NSKnowledge::CallHook $oop menu_cmd dismiss_all"
		}

		set_list_member {

			set canvistId [Info $oop member,canvistId]

			# Add each pet to the list
			foreach r_idx [Info $oop member,r_match] {

				# Get the icon and name
				angband r_info info $r_idx attrib

				# Append pet to the list
				NSCanvist::Insert $canvistId end $attrib(icon) $attrib(name)
			}

			# Display number of members
			set num [llength [Info $oop member,r_match]]
			if {!$num || $num > 1} {
				set s s
			} else {
				set s ""
			}
			[Info $oop win].statusBar.label2 configure -text  "$num pet$s"
		}

		select_member {
			set row [lindex $args 0]
			set r_idx [lindex [Info $oop member,r_match] $row]
			NSRecall::Recall $Windows(recall,oop) r_info $r_idx
		}

		member_name {
			set m_idx [lindex $args 0]
			set r_idx [debug dumptype monster_type $m_idx r_idx]
			angband r_info info $r_idx attrib
			return $attrib(name)
		}

		member_list {
			return [Info $oop member,match]
		}

		menu_setup {
			set hookMenu [Info $oop hookMenu,menu]
			if {[Info $oop member,current] != -1} {
				$hookMenu entryconfigure 0 -state normal
			}
			if {[llength [Info $oop member,match]]} {
				$hookMenu entryconfigure 1 -state normal
			}
		}

		menu_cmd {
			switch -- [lindex $args 0] {
				dismiss {
					set canvistId [Info $oop member,canvistId]
					angband keypress dn
					foreach row [NSCanvist::Selection $canvistId] {
						set m_idx [lindex [Info $oop member,match] $row]
						angband keypress $m_idx\n
					}
					angband keypress \033
				}
				dismiss_all {
					angband keypress d
				}
			}
		}
	}	
}

proc know_hook_quest {oop message args} {

	switch -- $message {

		set_list_group {

			set canvistId [Info $oop group,canvistId]

			# Use character icon
			set icon [icon assign -to character -toindex 0]

			# Add this group to the list
			NSCanvist::Insert $canvistId end $icon Quests

			# Keep a list of matching indexes
			Info $oop group,match 0

			# Don't show icons in the member list
			ConfigureList_Member $oop 0
		}

		set_list_member {

			set canvistId [Info $oop member,canvistId]

			# XXX Hack -- Getting the name of a quest may involve
			# parsing the "q_info.txt" file. Since there are 1000
			# possible quests, and most are empty, I don't want
			# to parse "q_info.txt" for them. So I find all the
			# empty quests here and ignore them.

			# Sort all the quests by level
			set q_match {}
			
			foreach q_idx [debug find quest_type -field type != 0] {

				# Set array with field/value pairs
				array set attrib [debug dumptype quest_type $q_idx]

				# Not a quest
#				if {$attrib(type) == 0} continue

				# No info from "silent" quests
				if {$attrib(silent)} continue
				
				# Not assigned
				if {$attrib(status) == 0} continue

				# Finished
				### It seems that "rewarded" quests are set to
				### "finished", and after you load the savefile
				### most of the quest info is not loaded.
				### It might be nice to display all the rewarded
				### quests, no?
				if {$attrib(status) == 4} continue
				
				# Random quests are always assigned, but we ignore
				# any the character can't know about
				if {($attrib(type) == 7) && \
					($attrib(level) > [angband player max_depth])} continue

				# Sort all the quests by level
				lappend q_match [list $q_idx $attrib(level)]
			}

			# Sort all the quests by level
			set q_match [lsort -integer -index 1 $q_match]

			# Keep a list of matching indexes
			set match {}
			
			foreach elem $q_match {

				set q_idx [lindex $elem 0]
				
				# Set array with field/value pairs
				array set attrib [debug dumptype quest_type $q_idx]

				switch -- $attrib(type) {
					1 -
					2 {
						angband r_info info $attrib(r_idx) r_attrib
						if {$r_attrib(unique)} {
							set string "Kill $r_attrib(name)"
						} else {
							set string "Kill $attrib(max_num) $r_attrib(name)"
							if {$attrib(cur_num) < $attrib(max_num)} {
								append string ", have killed $attrib(cur_num)"
							}
						}
					}
					3 {
						set string "$attrib(name) (Danger level: $attrib(level)) - find object"
					}
					4 {
						set string "$attrib(name) (Danger level: $attrib(level)) - find exit"
					}
					5 {
						set string "$attrib(name): Kill $attrib(num_mon) creatures"
						if {$attrib(cur_num) < $attrib(max_num)} {
							append string ", have killed $attrib(cur_num)"
						}
					}
					6 {
						set string "$attrib(name): Kill all monsters"
					}
					7 {
						angband r_info info $attrib(r_idx) r_attrib
						if {$r_attrib(unique)} {
							set string "Kill $r_attrib(name)"
						} else {
							set string "Kill $attrib(max_num) $r_attrib(name)"
							if {$attrib(cur_num) < $attrib(max_num)} {
								append string ", have killed $attrib(cur_num)"
							}
						}
					}
				}

				if {$attrib(status) == 1} {
					append string " (incomplete)"
				} elseif {$attrib(status) == 2} {
					append string " (unrewarded)"
				} elseif {$attrib(status) == 3} {
					append string " (rewarded)"
				} elseif {$attrib(status) == 4} {
					append string " (completed)"
				}
				
				# Append to list
				NSCanvist::Insert $canvistId end "" $string

				# Keep a list of matching indexes
				lappend match $q_idx
			}

			# Keep a list of matching indexes
			Info $oop member,match $match
		}

		select_member {
			set row [lindex $args 0]
			set q_idx [lindex [Info $oop member,match] $row]
			array set attrib [debug dumptype quest_type $q_idx]
			set icon "none 0"
			set color Yellow
			set name $attrib(name)
			if {$attrib(r_idx)} {
				angband r_info info $attrib(r_idx) r_attrib
				set icon $r_attrib(icon)
			}
			append name " - Danger level $attrib(level)"
			set text [debug info quest_type $q_idx text]
			if {![string length $text]} {
				if {$attrib(r_idx)} {
					set name $r_attrib(name)
					set text [angband r_info memory $attrib(r_idx)]
					set color Red
				} else {
					set $name "" ; # Random quests have no description
				}
			}
			NSRecall::SetText $::Windows(recall,oop) \
				$icon $color $name $text
		}
	}
}

# namespace eval NSKnowledge
}
