# highscore.tcl --
#
#	Implements the High Scores Window and score-file reading.
#

namespace eval NSHighScore {

# The high score read by highscore_read
variable highscore

# The score for the current character
variable highscore_index = -1

# List of name/value pairs
variable highscore_list {}

variable Priv

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

proc InitModule {} {

	variable Priv

	set Priv(find,string) ""
	set Priv(find,index) -1

	# Create the High Scores Window
	NSObject::New NSHighScore
}

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

proc NSHighScore oop {

	global Windows

	# Assume we will be called from C
	Info $oop interactive 1

	InitWindow $oop

	NSWindowManager::RegisterWindow highscore [Info $oop win] \
		"NSHighScore::GeometryCmd $oop" "" "NSHighScore::DisplayCmd $oop"

	#
	# Global list of application windows
	#

	set Windows(highscore,win) [Info $oop win]
	set Windows(highscore,class) NSHighScore
	set Windows(highscore,oop) $oop
}

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

proc Info {oop info args} {

	global NSHighScore

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

	# Get info
	} else {
		switch -- $info {
			default {
				return $NSHighScore($oop,$info)
			}
		}
	}
}

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

proc InitWindow oop {

	global Angband
	global Windows

	set win .highscore$oop
	toplevel $win
	wm title $win "$Angband(name) Hall of Fame"

	# Hack -- see "angband_display highscore"
	if {![winfo exists .tomb]} {
		wm transient $win $Windows(main,win)
	}

	# Start out withdrawn (hidden)
	wm withdraw $win

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

	Info $oop win $win

	InitMenus $oop

	#
	# High Score List
	#

	set font [Global font,fixed,normal]
	set cw [font measure $font "W"]
	set fh [font metrics $font -linespace]
	set width [expr $cw * 80]
	set rowHeight [expr $fh * 4]

	frame $win.frameList \
		-borderwidth 1 -relief sunken
	set canvistId [NSObject::New NSCanvist $win.frameList $rowHeight $width 300 \
		"NSHighScore::NewItemCmd $oop" "NSHighScore::HighlightItemCmd $oop"]
	set canvas [NSCanvist::Info $canvistId canvas]
	$canvas configure -background Black
	$canvas configure -yscrollcommand "$win.frameList.yscroll set"
	scrollbar $win.frameList.yscroll \
		-borderwidth 0 -command "$canvas yview" -orient vertical

	Info $oop canvistId $canvistId

	#
	# Geometry
	#

	grid rowconfigure $win 0 -weight 1 -minsize 0
	grid columnconfig $win 0 -weight 1 -minsize 0

	grid rowconfigure $win.frameList 0 -weight 1 -minsize 0
	grid columnconfig $win.frameList 0 -weight 1 -minsize 0
	grid columnconfig $win.frameList 1 -weight 0 -minsize 0

	grid $win.frameList \
		-row 0 -column 0 -rowspan 1 -columnspan 1 -sticky news
	grid $canvas \
		-row 0 -column 0 -rowspan 1 -columnspan 1 -sticky news
	grid $win.frameList.yscroll \
		-row 0 -column 1 -rowspan 1 -columnspan 1 -sticky ns

	# Close window when Escape typed
	bind $win <KeyPress-Escape> "NSHighScore::Close $oop ; break"

	Term_KeyPress_Bind $win
	Term_KeyPress_Bind $canvas

	bind $win <KeyPress-f> "NSHighScore::Find $oop 0 ; break"
	bind $win <KeyPress-g> "NSHighScore::Find $oop 1 ; break"

	# Synch the scrollbars when window is shown.
	bind $win.frameList.yscroll <Map> "NSHighScore::SynchScrollBars $oop"
}

# NSHighScore::InitMenus --
#
#	Create the menus associated with the window.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc InitMenus oop {

	set win [Info $oop win]
	set mod "Ctrl"

	#
	# Menu bar
	#

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

	#
	# Message Menu
	#

	NSObject::New NSMenu $mbar {-tearoff 0 -identifier MENU_MESSAGE}
	NSMenu::MenuInsertEntry $mbar -end MENUBAR {-type cascade \
		-menu MENU_MESSAGE -label "Score" -underline 0 \
		-identifier M_MESSAGE}

	set entries {}
	lappend entries "-type command -label \"Dump Scores\" \
		-command \"NSHighScore::Dump $oop $win\" \
		-underline 0 -identifier E_DUMP"
	lappend entries "-type separator"
	lappend entries "-type command -label \"Find...\" \
		-command \"NSHighScore::Find $oop 0\" \
		-accelerator f -underline 0 -identifier E_FIND"
	lappend entries "-type command -label \"Find Again\" \
		-command \"NSHighScore::Find $oop 1\" \
		-accelerator g -underline 6 -identifier E_FIND_AGAIN"
	lappend entries "-type separator"
	lappend entries "-type command -label \"Close\" \
		-command \"NSHighScore::Close $oop\" -underline 0 \
		-accelerator $mod+W -identifier E_CLOSE"
	
	NSMenu::MenuInsertEntries $mbar -end MENU_MESSAGE $entries
}

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

proc SetupMenus {oop mbarId} {

	variable Priv

	lappend identList E_DUMP E_FIND E_FIND_AGAIN E_CLOSE
		
	NSMenu::MenuEnable $mbarId $identList
}

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

proc DisplayCmd {oop message first} {

	global Angband

	switch -- $message {
		preDisplay {
			if {![Info $oop interactive]} {
				wm title [Info $oop win] "$Angband(name) Hall of Fame"
			}
			SetList $oop
		}
		postDisplay {
		}
		reDisplay {
			SetList $oop
		}
	}
}

# NSHighScore::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
}

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

proc Close oop {

	# If the character is dead, then we call "tkwait window"
	if {[angband player is_dead]} {
		destroy [Info $oop win]
		return
	}

	if {![Info $oop interactive]} {
		NSWindowManager::Undisplay highscore
		Info $oop interactive 1
		return
	}

	angband keypress \033
}

# NSHighScore::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]
	set canvistId [Info $oop canvistId]
	set canvas [NSCanvist::Info $canvistId canvas]

	eval $win.frameList.yscroll set [$canvas yview]
}

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

proc NewItemCmd {oop canvistId y index} {

	variable highscore

	set c [NSCanvist::Info $canvistId canvas]
	set lineHeight [NSCanvist::Info $canvistId rowHgt]
	set font [Global font,fixed,normal]

	set text [FormatScore $oop $index]

	# Text
	lappend itemIdList [$c create text 0 $y \
		-text $text -anchor nw -font $font -fill White \
		-tags enabled]

	return $itemIdList
}

# NSHighScore::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 itemId [lindex $args 0]
	if $state {
		$canvas itemconfigure $itemId -fill LawnGreen
	} else {
		$canvas itemconfigure $itemId -fill White
	}
}

# NSHighScore::total_points --
#
#	Calculate the character's total points.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc total_points {} {
	set max_exp [lindex [angband player exp] 1]
	set max_dlv [angband player max_depth]
	return [expr $max_exp + (100 * $max_dlv)]
}

# NSHighScore::SetList --
#
#	Fill the list with scores.
#
# Arguments:
#	arg1					about arg1
#
# Results:
#	What happened.

proc SetList oop {

	global Angband
	variable highscore
	variable highscore_id
	variable highscore_index
	variable highscore_list

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

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

	# List of scores
	GetTheScores $oop

	set index 0
	foreach score $highscore_list {

		array set highscore $score

		# Append the high score, getting info from highscore[] variable
		NSCanvist::Insert $canvistId end [incr index]
	}

	if {$highscore_index != -1} {
		NSCanvist::UpdateSelection $canvistId $highscore_index all
		NSCanvist::See $canvistId $highscore_index
	}
}

# NSHighScore::GetTheScores --
#
#	Return a list of high scores. Read the high score file if present.
#	Also create an entry for the character, if not dead yet. 
#
# Arguments:
#	oop					OOP ID. See above.
#
# Results:
#	What happened.

proc GetTheScores oop {

	global Angband
	variable highscore
	variable highscore_index
	variable highscore_list

	set highscore_index -1

	set highscore_list {}

	# Predict the character' score
	set predict [angband highscore predict -force highscore_predict]

	# If we are showing all the scores, then always predict the character's score.
	if {![Info $oop interactive]} {set predict 1}

	# Build the filename
	set buf [file join $Angband(dir) lib apex scores.raw]

	# See if the file exists
	if {[file exists $buf]} {

		# Read the high scores
		set index 0
		foreach score [angband highscore find -tester yes] {
			angband highscore info $score highscore
			if {[angband player is_dead]} {
				if {![string compare $highscore(who) $highscore_predict(who)] &&
					($highscore(turns) == $highscore_predict(turns)) &&
					($highscore(cur_dun) == $highscore_predict(cur_dun)) &&
					($highscore(max_dun) == $highscore_predict(max_dun)) &&
					($highscore(cur_lev) == $highscore_predict(cur_lev)) &&
					($highscore(max_lev) == $highscore_predict(max_lev)) &&
					($highscore(p_c) == $highscore_predict(p_c)) &&
					($highscore(p_r) == $highscore_predict(p_r)) &&
					($highscore(pts) == $highscore_predict(pts))} {

					set highscore_index $index
				}
			}

			lappend highscore_list [array get highscore]
			incr index
		}
	}

	# Living character may get an entry
	if {![angband player is_dead] && $predict} {
		set index 0
		foreach score $highscore_list {
			array set highscore $score
			if {$highscore(pts) < $highscore_predict(pts)} {
				break
			}
			incr index
		}
		set highscore_list [linsert $highscore_list $index [array get highscore_predict]]

		# Remember the index so we can scroll to it
		set highscore_index $index
	}
}

# NSHighScore::FormatScore --
#
#	Return a formatted string for the current high score. 
#
# Arguments:
#	oop					OOP ID. See above.
#
# Results:
#	What happened.

proc FormatScore {oop index} {

	variable highscore

	append text [format "%3d.%9s  %s the %s %s, Level %d" \
		$index $highscore(pts) $highscore(who) \
		[lindex [angband info race_name] $highscore(p_r)] \
		[lindex [angband info class_name] $highscore(p_c)] \
		$highscore(cur_lev)]

	# Append a "maximum level"
	if {$highscore(max_lev) > $highscore(cur_lev)} {
		append text " (Max $highscore(max_lev))"
	}

	if {$highscore(cur_dun)} {
		append text [format "\n               Killed by %s on %s %d" \
			$highscore(how) "Dungeon Level" $highscore(cur_dun)]
	} else {
		append text [format "\n               Killed by %s in the Town" \
			$highscore(how)]
	}
	
	# Append a "maximum level"
	if {$highscore(max_dun) > $highscore(cur_dun)} {
		append text " (Max $highscore(max_dun))"
	}

	# Hack -- extract the gold and such
	set user [string trimleft $highscore(uid)]
	set when [string trimleft $highscore(day)]
	set gold [string trimleft $highscore(gold)]
	set turn [string trimleft $highscore(turns)]

	# And still another line of info
	append text \
		[format "\n               (User %s, Date %s, Gold %s, Turn %s)." \
		$user $when $gold $turn]

	return $text
}

# NSHighScore::Dump --
#
#	Dump a list of high scores to a text file. 
#
# Arguments:
#	oop					OOP ID. See above.
#
# Results:
#	What happened.

proc Dump {oop parent} {

	global Angband
	variable highscore
	variable highscore_list

	set win [Info $oop win]

	set fileName [tk_getSaveFile -initialfile scores.txt \
		-initialdir [file join $Angband(dir) lib user] -parent $parent]
	if {![string compare $fileName ""]} return

	if {[catch {open $fileName w} fileId]} {
		set msg "The following error occurred while attempting to open "
		append msg "the score-dump file for writing:\n\n$fileId"
		tk_messageBox -title Oops -message $msg
		return
	}

	puts $fileId "  \[$Angband(name) $Angband(vers) Hall of Fame\]\n"

	GetTheScores $oop

	set index 0
	foreach score $highscore_list {
		array set highscore $score
		puts $fileId [FormatScore $oop [incr index]]\n
	}

	close $fileId
}

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

proc Find {oop again} {

	variable highscore
	variable highscore_list
	variable Priv
	
	set canvistId [Info $oop canvistId]

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

		set string $Priv(find,string)

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

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

	set row $Priv(find,index)
	foreach score [lrange $highscore_list $row end] {
		array set highscore $score
		set text [FormatScore $oop [expr $row + 1]]
		if {[string first $string $text] != -1} {
			NSCanvist::UpdateSelection $canvistId $row all
			NSCanvist::See $canvistId $row
			set Priv(find,index) $row
			return
		}
		incr row
	}

	set max [incr row -1]
	set row 0
	foreach score [lrange $highscore_list 0 $max] {
		array set highscore $score
		set text [FormatScore $oop [expr $row + 1]]
		if {[string first $string $text] != -1} {
			NSCanvist::UpdateSelection $canvistId $row all
			NSCanvist::See $canvistId $row
			set Priv(find,index) $row
			return
		}
		incr row
	}
}

# namespace eval NSHighScore
}

proc RebootHighscore {} {
	catch {
		destroy $::Windows(highscore,win)
		NSObject::Delete NSHighScore $::Windows(highscore,oop)
		namespace delete NSHighScore
	}
	uplevel #0 source [file join $::Angband(dirTK) highscore.tcl]
	NSHighScore::InitModule
}
