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

namespace eval NSInventory {

variable InventoryPriv

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

proc InitModule {} {

	# Create the Inventory Window
	NSObject::New NSInventory
}

# NSInventory::NSInventory --
#
#	Description.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc NSInventory oop {

	global NSInventory
	global Windows

	# The user should be allowed to change the font.
	set NSInventory($oop,font) [Global font,fixed,normal]

	InitWindow $oop

#	SetList $oop inventory {}

	#
	# Global list of application windows
	#

	set Windows(inventory,win) $NSInventory($oop,win)
	set Windows(inventory,class) NSInventory
	set Windows(inventory,oop) $oop
}

# NSInventory:: --
#
#	Description.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitWindow oop {

	global Notice
	global NSCanvist
	global NSInventory
	global NSToolbar
	global Windows

	set win .inventory$oop
	toplevel $win
	wm title $win Inventory

	wm transient $win $Windows(main,win)

	# Start out withdrawn (hidden)
	wm withdraw $win

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

	set NSInventory($oop,win) $win

	InitMenus $oop

	#
	# Toolbar
	#

	set toolId [NSObject::New NSToolbar 20 $win]
	NSToolbar::AddTool $toolId Image_ButtonOptions "DoUnderlyingCommand ="
	NSToolbar::AddTool $toolId Image_ButtonHelp "DoUnderlyingCommand ?"

	#
	# Divider
	#

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

	#
	# List
	#

	set cw [font measure $NSInventory($oop,font) "W"]
	set width [expr $cw * 81]

	frame $win.frame \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $win.frame 40 $width 300 \
		"NSInventory::NewItemCmd $oop" "NSInventory::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

	set NSInventory($oop,list) $canvistId

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

	# When an item is selected, recall it
	NSCanvist::Info $canvistId selectionCmd \
		"NSInventory::RecallObject $oop"

	# Double-click to select item
	NSCanvist::Info $canvistId invokeCmd \
		"NSInventory::Invoke $oop"

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

	#
	# Statusbar
	#

	frame $win.statusBar -relief flat -borderwidth 0
	label $win.statusBar.label -anchor w -relief sunken -padx 2 -width 18
	label $win.statusBar.label2 -anchor w -relief sunken -padx 2 -width 14 
	label $win.statusBar.label3 -anchor w -relief sunken -padx 2 -width 18
	label $win.statusBar.label4 -anchor w -relief sunken -padx 2 -width 15
	label $win.statusBar.label5 -anchor w -relief sunken -padx 2 -width 12

	pack $win.statusBar.label -side left -expand no
	pack $win.statusBar.label2 -side left -expand no
	pack $win.statusBar.label3 -side left -expand no
	pack $win.statusBar.label4 -side left -expand yes -fill x
	pack $win.statusBar.label5 -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 1 -minsize 0
 
	pack forget $NSToolbar($toolId,frame)
	grid $NSToolbar($toolId,frame) -in $win \
		-row 0 -column 0 -rowspan 1 -columnspan 1 -sticky ew
	grid $win.divider2 -in $win \
		-row 1 -column 0 -rowspan 1 -columnspan 1 -sticky ew
	grid $win.frame -in $win \
		-row 2 -column 0 -rowspan 1 -columnspan 1 -sticky news
	grid $win.statusBar -in $win \
		-row 3 -column 0 -rowspan 1 -columnspan 1 -sticky ew

	#
	# Context Menu
	#
	
	set m $win.context
	menu $m -tearoff 0
	bind $canvas <Button-3> "::NSInventory::ContextMenu $oop $m %X %Y"

	trace variable Notice(inventory) w "::NSInventory::InventoryChanged $oop"

	#
	# Feed Term when keys pressed
	#

	Term_KeyPress_Bind $win
	Term_KeyPress_Bind $canvas

	#
	# Synch the scrollbars when window is shown.
	#

	bind $win.frame.scroll <Map> "NSInventory::SynchScrollBars $oop"

	bind $win <FocusIn> "if !\[string compare %W $win] {focus $canvas}"
}

# NSInventory:: --
#
#	Description.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitMenus oop {

	global NSInventory

	set win $NSInventory($oop,win)
}

# NSInventory::Close --
#
#	Description.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Close oop {

	angband keypress \033
}

# NSInventory::Invoke --
#
#	When an inventory item is double-clicked, "angband keypress" the
#	char.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc Invoke {oop canvistId x y} {

	global NSInventory
	variable InventoryPriv

	if {[string compare [angband inkey_flags] INKEY_ITEM]} return

	set row [NSCanvist::PointToRow $canvistId $x $y]
	if {$row == -1} return

	angband keypress [lindex $InventoryPriv(char) $row]
}

# NSInventory::RecallObject --
#
#	Called when the list selection changes.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc RecallObject {oop canvistId select deselect} {

	global NSInventory
	global Windows
	variable InventoryPriv

	set canvas [NSCanvist::Info $canvistId canvas]

	# Do nothing if no new row was selected
	if {![llength $select]} return

	# Get the (first) row
	set row [lindex $select 0]

	# Get the object index
	set index [lindex $InventoryPriv(index) $row]

	# Get object info
	angband $NSInventory($oop,invOrEquip) info $index attrib

	# Ignore non-objects in equipment
	if {$attrib(tval) == "TV_NONE"} {
		NSRecall::SetText $Windows(recall,oop) {none 0} White "" ""
		return
	}

	# Get the icon
	set icon $attrib(icon)

	# Color
	set color [default_tval_to_attr $attrib(tval)]

	# Get the description
	set desc $attrib(name):

	# Get the memory
	set memory [angband $NSInventory($oop,invOrEquip) memory $index]

	# Set the recall text
	NSRecall::SetText $Windows(recall,oop) $icon $color $desc $memory
}

# NSInventory::ContextMenu --
#
#	When an inventory item is right-clicked, pop up a context
#	menu of actions.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc ContextMenu {oop menu x y} {

	global NSInventory
	variable InventoryPriv

	set canvistId $NSInventory($oop,list)
	set canvas [NSCanvist::Info $canvistId canvas]

	set x1 [expr $x - [winfo rootx $canvas]]
	set y1 [expr $y - [winfo rooty $canvas]]
	set row [NSCanvist::PointToRow $canvistId $x1 $y1]
	if {$row == -1} return

	set index [lindex $InventoryPriv(index) $row]
	angband $NSInventory($oop,invOrEquip) info $index attrib
	set itemTval $attrib(tval)
	set itemKey $attrib(char)

	set usageString ""
	set commandString ""
	if {![string compare $itemTval TV_POTION]} {
		set usageString Drink
		set cmdChar q
	}
	if {![string compare $itemTval TV_SCROLL]} {
		set usageString Read
		set cmdChar r
	}
	if {[string first BOOK $itemTval] != -1} {
		set usageString Browse
		set cmdChar b
	}
	if {![string compare $itemTval TV_FOOD]} {
		set usageString Eat
		set cmdChar E
	}
	if {![string compare $itemTval TV_STAFF]} {
		set usageString Use
		set cmdChar u
	}
	if {![string compare $itemTval TV_ROD]} {
		set usageString Zap
		set cmdChar z
	}
	if {![string compare $itemTval TV_WAND]} {
		set usageString Aim
		set cmdChar a
	}
	if {$NSInventory($oop,invOrEquip) == "equipment"} {
		if {$attrib(known) && $attrib(activate)} {
			set usageString Activate
			set cmdChar A
		} else {
			set usageString Remove
			set cmdChar T
		}
	}

	$menu delete 0 end
	if {[string length $usageString]} {

		# Can't call DoUnderlyingCommand because INKEY_CMD is not set
		# while looking at inventory! Can't simply prepend a slash to
		# get the underlying command because request_command() isn't
		# being called. So I have to find a keymap for the given
		# underlying command.
		
		set cmdChar [angband keymap find $cmdChar]
		$menu add command -label $usageString \
			-command "angband keypress $cmdChar$itemKey"
	}
	$menu add command -label "Drop" \
		-command "angband keypress [angband keymap find d]$itemKey"
	$menu add command -label "Inscribe" \
		-command "angband keypress \\[angband keymap find \{]$itemKey"
	$menu add command -label "Uninscribe" \
		-command "angband keypress \\[angband keymap find \}]$itemKey"
	$menu add command -label "*Destroy*" \
		-command "angband keypress [angband keymap find k]${itemKey}y"

	tk_popup $menu $x $y
}

# NSInventory::CalcLineLength --
#
#	Return the number of characters that will fit on a line. This considers
#	the object image (40 pixels).
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc CalcLineLength oop {

	global NSInventory

	set canvistId $NSInventory($oop,list)
	set canvas [NSCanvist::Info $canvistId canvas]

	set cw [font measure $NSInventory($oop,font) "W"]
	set width [winfo width $canvas]
	if {[Value show_icons]} {
		set leftBorder [expr [icon size] + 8]
	} else {
		set leftBorder 4
	}
	return [expr int([expr ($width - $leftBorder - 4) / $cw])]
}

# NSInventory::CalcDescMax --
#
#	Return the max number of characters allowed in an item description.
#	This considers the options "show_labels" and "show_weights".
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc CalcDescMax {oop lineLength} {

	global NSInventory

	set canvistId $NSInventory($oop,list)
	set canvas [NSCanvist::Info $canvistId canvas]

	set length $lineLength
	incr length -3
	if {[lindex [angband setting show_weights] 0]} {
		incr length -9
	}
	if {$NSInventory($oop,invOrEquip) == "equipment"} {
		if {[lindex [angband setting show_labels] 0]} {
			incr length -[expr 14 + 2]
		}
	}

	return $length
}

# NSInventory::SetList --
#
#	Description.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList {oop invOrEquip tval} {

	global NSInventory
	variable InventoryPriv

	set win $NSInventory($oop,win)
	set canvistId $NSInventory($oop,list)
	set canvas [NSCanvist::Info $canvistId canvas]

	if {$invOrEquip != {}} {
		set NSInventory($oop,invOrEquip) $invOrEquip
		set NSInventory($oop,tval) $tval
	} else {
		set invOrEquip $NSInventory($oop,invOrEquip)
		set tval $NSInventory($oop,tval)
	}

	if {$tval != {}} {
		set items [angband $invOrEquip find -tester yes -tval $tval]
	} else {
		set items [angband $invOrEquip find -tester yes]
	}

	# Clear the list of char's
	set InventoryPriv(char) {}

	# Clear the list of indexes
	set InventoryPriv(index) {}

	# Clear the list
	NSCanvist::Delete $canvistId 0 end
	
	# Total the weight
	set weightDisplayed 0

	# Option: Show icons in lists
	if {[Value show_icons]} {
		set rowHgt [expr [icon size] + 8]
	} else {
		set rowHgt 20
	}	
	NSCanvist::Info $canvistId rowHgt $rowHgt
	$canvas configure -yscrollincrement $rowHgt

	# Options: Show weights, Show labels (in equipment)
	set show_labels [lindex [angband setting show_labels] 0]
	set show_weights [lindex [angband setting show_weights] 0]

	# Get maximum length of item description
	set InventoryPriv(lineLen) [CalcLineLength $oop]
	set InventoryPriv(descLim) [CalcDescMax $oop $InventoryPriv(lineLen)]

	# Add each item
	foreach index $items {

		set attrib(label) ""
		angband $invOrEquip info $index attrib

		if {$invOrEquip == "floor"} {
			set attrib(char) [string index "abcdefghijklmnopqrstuvw" [lsearch -exact $items $index]]
		}

		set desc $attrib(name)
		set desc [string range $desc 0 [expr $InventoryPriv(descLim) - 1]]

		set label $attrib(label)
		if !$show_labels {set label ""}

		set weight [expr $attrib(weight) * $attrib(number)]
		if !$show_weights {set weight ""}

		set icon $attrib(icon)
		if {![Value show_icons]} {set icon ""}

		# Append the description
		NSCanvist::Insert $canvistId end $attrib(char) \
			$attrib(number) $desc $weight $label \
			$attrib(tval) $attrib(sval) $icon

		# Total the weight
		incr weightDisplayed [expr $attrib(weight) * $attrib(number)]

		# Remember the char
		lappend InventoryPriv(char) $attrib(char)

		# Remember the index
		lappend InventoryPriv(index) $index
	}

	# Display weight of inventory items, % of capacity, and weight limit
	set weightTotal [angband inventory total_weight]
	set weightLimit [angband inventory weight_limit]
	set displayed [format "%d.%d lb" [expr $weightDisplayed / 10] [expr $weightDisplayed % 10]]
	set total [format "%d.%d lb" [expr $weightTotal / 10] [expr $weightTotal % 10]]
	set capacity [format "%d.%d lb" [expr ($weightLimit / 2) / 10] [expr ($weightLimit / 2) % 10]]
	set limit [format "%d.%d lb" [expr $weightLimit / 10] [expr $weightLimit % 10]]

	$win.statusBar.label configure -text "Displayed $displayed"
	$win.statusBar.label2 configure -text "Total $total"
	$win.statusBar.label3 configure -text "Threshold $capacity"
	$win.statusBar.label4 configure -text "Limit $limit"
	$win.statusBar.label5 configure -text "[llength $items] item(s)"

	# Set window title
	set title [string toupper [string index $invOrEquip 0]][string range $invOrEquip 1 end]
	wm title $win $title
}

# NSInventory::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 {

	global NSInventory

	set win $NSInventory($oop,win)
	set canvistId $NSInventory($oop,list)
	set canvas [NSCanvist::Info $canvistId canvas]

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

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

proc NewItemCmd {oop canvistId y char number text weight label tval sval icon} {

	global NSInventory
	variable InventoryPriv

	set c [NSCanvist::Info $canvistId canvas]
	set lineHeight [NSCanvist::Info $canvistId rowHgt]
	set font $NSInventory($oop,font)

	if {[string length $label]} {
		set label [format "%-14s: " $label]
	}
	set prefix "$char) $label"

	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])]

	if {[Value show_icons]} {
		set offset [expr [icon size] + 8]
	} else {
		set offset 4
	}

	set textLeft [expr $offset + $cw * [string length $prefix]]

	# Selection rectangle inside row
	lappend itemIdList [$c create rectangle 2 [expr $y + 2] \
		[expr [winfo width $c] - 3] [expr $y + $lineHeight - 2] \
		-fill "" -outline "" -tags enabled -width 2.0]

	# Image
	if {$icon != ""} {
		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])]
		lappend itemIdList [$c create widget $xdiff [expr $y + $ydiff] \
			 -type [lindex $icon 0] -index [lindex $icon 1]]
	}

	# Prefix (char & label)
	lappend itemIdList [$c create text $offset [expr $y + $diff] \
		-text $prefix -anchor nw -font $font -fill White]

	# Description
	set fill [default_tval_to_attr $tval]
	lappend itemIdList [$c create text [expr $textLeft + 1] [expr $y + $diff] \
		-text $text -anchor nw -font $font -fill $fill -tags "enabled hilite fill:$fill"]

	# Weight
	if {[string length $weight]} {
		set lineLength $InventoryPriv(lineLen)
		set weightRight [expr $offset + $cw * $lineLength]
		lappend itemIdList [$c create text $weightRight [expr $y + $diff] \
			-text [format "%d.%d lb" [expr $weight / 10] [expr $weight % 10]] \
			-anchor ne -justify right -font $font -fill White]
	}

	return $itemIdList
}

# NSInventory::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} {

	set canvas [NSCanvist::Info $canvistId canvas]
	set itemIdList $args

	set idRect [lindex $itemIdList 0]

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

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

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

# NSInventory::PopupSelect --
#
#	Show a pop-up menu of inventory choices.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc PopupSelect {menu x y} {

	global PopupResult

	set PopupResult 0

	# Hack -- Turn on plain_descriptions
	set setting [lindex [angband setting plain_descriptions] 0]
	angband setting plain_descriptions yes

	set invOrEquip [angband inkey_other]
	$menu delete 0 end
	set num 0
	foreach item [angband $invOrEquip find -tester yes] {
		angband $invOrEquip info $item attrib
		if {$invOrEquip == "floor"} {
			set attrib(char) [string index "abcdefghijklmnopqrstuvw" $num]
		}
		set char $attrib(char)
		$menu add command -label "$char $attrib(name)" \
			-command "angband keypress $char ; set PopupResult 1" \
			-underline 0
		incr num
	}

	if $num {$menu add separator}
	$menu add command -label Cancel

	# Hack -- Restore plain_descriptions
	angband setting plain_descriptions $setting

	# Pressing and holding Button-3, poping up, then letting go selects 
	# an item, so wait a bit if it was a quick press-release
	after 100

	tk_popup $menu $x $y [expr $num / 2]

	# If the user unposts the menu without choosing an entry, then
	# I want to feed Escape into the Term. I tried binding to the <Unmap>
	# event but it isn't called on Windows(TM).
	after idle {
		if !$PopupResult {
			angband keypress \033
		}
	}
}

# NSInventory::PopupSelect_Use --
#
#	Show a heirarchical pop-up menu of inventory items we can use.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc PopupSelect_Use {menu x y} {

	$menu delete 0 end
	eval destroy [winfo children $menu]

	# Hack -- Turn on plain_descriptions
	set setting [lindex [angband setting plain_descriptions] 0]
	angband setting plain_descriptions yes

	set num 0

	set data {
		equipment "-activate yes" Activate A	
		inventory "-tval TV_FOOD" Food E
		inventory "-tval TV_POTION" Potion q
		inventory "-tval TV_SCROLL" Scroll r
		inventory "-tval TV_ROD" Rod z
		inventory "-tval TV_WAND" Wand a
		inventory "-tval TV_STAFF" Staff u
	}

	foreach {invOrEquip criteria label cmdChar} $data {
		set itemList [eval angband $invOrEquip find $criteria]
		if {[llength $itemList]} {
			set menu2 $menu.menu$num
			menu $menu2 -tearoff 0
			$menu add cascade -menu $menu2 -label $label
			foreach item $itemList {
				angband $invOrEquip info $item attrib
				$menu2 add command -label "$attrib(char) $attrib(name)" \
					-command "DoUnderlyingCommand $cmdChar$attrib(char)"
			}
			incr num
		}
	}

	if $num {$menu add separator}
	$menu add command -label Cancel

	# Hack -- Restore plain_descriptions
	angband setting plain_descriptions $setting

	# Pressing and holding Button-3, poping up, then letting go selects 
	# an item, so wait a bit if it was a quick press-release
	after 100

	tk_popup $menu $x $y [expr $num / 2]
}

# NSInventory::InventoryChanged --
#
#	Trace variable callback on Notice(inventory). Update the
#	inventory list when the inventory changes.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InventoryChanged {oop name1 name2 op} {

	global NSInventory

	if {![winfo ismapped $NSInventory($oop,win)]} return
	SetList $oop {} {}
}

# namespace eval NSInventory
}

# Get a list of tval's.
set Angband(tval) [angband info tval]

proc default_tval_to_attr tval {

	return [NSColorPreferences::Get $tval]
}

proc GetValueFromTag {prefix sep tagList} {

	set index [lsearch -glob $tagList $prefix$sep*]
	set tag [lindex $tagList $index]
	return [lindex [split $tag $sep] 1]
}
