#
# high score routines
#
# high score format:
#   {score keys ...}
#
# score is any real type value. score is sorted increasing order.
# so minimum value is the highest score.
# if you want to store decreasing order, store score as negative value.
# keys are any string.
# "date" key is automaticaly added. format is "date:second". second is the
# time from Jan, 01 1970
#

namespace eval score {
  set hiscores {}
  set hiscore_read_time 0
  set hiscore_file "$GRACER_USER/hiscores"

  namespace export match match_any search add save load \
  			hiscore_search hiscore_add
}

proc score::match {score keys} {
# expired data is ignored if "expired" is not specified in keys.
  if {[lsearch -exact $keys "expired"] < 0 && \
    	[lsearch -exact $score "expired"] >= 0} {
    return 0
  }
  foreach key $keys {
    if {[lsearch -glob [lrange $score 1 end] $key] < 0} {
      return 0
    }
  }
  return 1
}

#
# proc score::search num keys
#
# num : how many entries may be returned
# keys: entries which contains specified keys returned. 
#

proc score::search {scores num keys} {
  set res {}
  foreach score $scores {
    if [score::match $score $keys] {
      lappend res $score
    }
  }

  return [lrange [lsort -real -index 0 $res] 0 [expr $num - 1]]
}

#
# proc score::add num val keys
#
# num : how many entries remained in high score list if there are entries which #       has same key list.
#	0 means no restriction.
# val : score
# keys: keys 
#
# time stamp is automatically added.
#

proc score::add {scores num val keys} {
# add new score
  lappend scores [concat $val $keys "date:[clock seconds]"]

# remove lesser scores
  if {$num > 0} {
    set res {}
    set length [llength $scores]
    for {set i 0} {$i < $length} {incr i} {
      set score [lindex $scores $i]
      if [score::match $score $keys] {
	lappend res [list [lindex $score 0] $i]
      }
    }

    set indice {}
    foreach entry [lrange [lsort -real -index 0 $res] $num end] {
      lappend indice [lindex $entry 1]
    }
    foreach i [lsort -decreasing -integer $indice] {
      set scores [lreplace $scores $i $i]
    }
  }

  return $scores
}

proc score::check_ranking {ranknum myscore scores} {
  set length [llength $scores]
  for {set i 0} {$i < $length} {incr i} {
    set score [lindex [lindex $scores $i] 0]
    if {$score > $myscore} {
      break
    }
  }
  if {$i < $ranknum} {
    return [expr $i + 1]
  }
  return 0
}

proc score::save {scores fname} {
  set file [open $fname "w"]
  foreach entry $scores {
    puts $file $entry
  }
  close $file
}

proc score::load {fname} {
  set scores {}
  if [file readable $fname] {
    set file [open $fname "r"]
    while {![eof $file]} {
      gets $file entry
      if [string length $entry] {
	lappend scores $entry
      }
    }
    close $file
  }
  return $scores
}

proc score::expire {scores keys} {
  set res {}

  set length [llength $scores]
  foreach score $scores  {
    if [score::match $score $keys] {
      lappend score "expired"
    }
    lappend res $score
  }

  return $res
}

proc score::hiscore_search {num keys} {
  variable hiscores
  variable hiscore_read_time
  variable hiscore_file

# prevent for if hiscore file does not exists
  catch {
    set time [file mtime $hiscore_file]
    if {$time > $hiscore_read_time} {
      set hiscores [score::load $hiscore_file]
      set hiscore_read_time $time
    }
  }

  return [score::search $hiscores $num $keys]
}

proc score::hiscore_add {num val keys} {
  variable hiscores
  variable hiscore_read_time
  variable hiscore_file

# prevent for if hiscore file does not exists
  catch {
    set time [file mtime $hiscore_file]
    if {$time > $hiscore_read_time} {
      set hiscores [score::load $hiscore_file]
      set hiscore_read_time $time
    }
  }

  set hiscores [score::add $hiscores $num $val $keys]

  score::save $hiscores $hiscore_file
  set $hiscore_read_time [file mtime $hiscore_file]
}

proc score::hiscore_expire {keys} {
  variable hiscores
  variable hiscore_read_time
  variable hiscore_file

# prevent for if hiscore file does not exists
  catch {
    set time [file mtime $hiscore_file]
    if {$time > $hiscore_read_time} {
      set hiscores [score::load $hiscore_file]
      set hiscore_read_time $time
    }
  }

  set hiscores [score::expire $hiscores $keys]

  score::save $hiscores $hiscore_file
  set $hiscore_read_time [file mtime $hiscore_file]
}
