 
 SECONDS_PER_GUESS = SINGLE_GUESS / NUM_ATTACKERS
 
-# average number of guesses before cracking, not total: add a .5 term
-entropy_to_crack_time = (entropy) -> .5 * Math.pow(2, entropy) * SECONDS_PER_GUESS
+entropy_to_crack_time = (entropy) -> .5 * Math.pow(2, entropy) * SECONDS_PER_GUESS # average, not total
 
 # ------------------------------------------------------------------------------
 # entropy calcs -- one function per match pattern ------------------------------

# ------------------------------------------------------------------------------

minimum_entropy_match_sequence = (password, matches) ->
  bruteforce_cardinality = calc_bruteforce_cardinality password
  up_to_k = [] # min entropy up to k
  backpointers = []
  k = 0
  while k < password.length
    prev_entropy = up_to_k[k-1] or 0
    # worst-case scenario to beat: min entropy sequence at k-1, plus a brute-force char
    up_to_k[k] = prev_entropy + log2 bruteforce_cardinality
    backpointers[k] = null
    for match in matches
      [i, j] = [match.i, match.j]
      if i > k
        break
      if j > k
        continue
      candidate_entropy = (up_to_k[i-1] or 0) + calc_entropy(match)
      if candidate_entropy < up_to_k[j]
        up_to_k[j] = candidate_entropy
        backpointers[j] = match
    k += 1

  # walk backwards and decode
  k = password.length - 1
  min_match = []
  min_entropy = up_to_k[k]
  while k > 0
    match = backpointers[k]
    if match
      min_match.push match
      k = match.i - 1
    else
      k -= 1
  min_match.reverse()

  # fill in the blanks between matches with bruteforce matches
  start_i = 0
  augmented = []
  for match in min_match
    [i, j] = [match.i, match.j]
    if i - start_i > 0
      augmented.push
        pattern: 'bruteforce'
        i: start_i # the start of the gap.
        j: i - 1   # ends one before the start of the following match.
        token: password[start_i...i]
        cardinality: bruteforce_cardinality
    start_i = j + 1
    augmented.push match

  if start_i < password.length
    augmented.push
      pattern: 'bruteforce'
      i: start_i
      j: password.length - 1
      token: password[start_i...password.length]
      cardinality: bruteforce_cardinality

  min_match = augmented

  password: password
  crack_time: display_time entropy_to_crack_time min_entropy
  min_entropy: Math.round min_entropy
  min_match: min_match

# ------------------------------------------------------------------------------
# threat model -- conservative apocalypse scenario -----------------------------
# ------------------------------------------------------------------------------
#
# assumes:

  year = month * 12
  century = year * 100
  if seconds < minute
    'instant'
  else if seconds < hour
    "#{1 + Math.ceil(seconds / minute)} minutes"
  else if seconds < day
    "#{1 + Math.ceil(seconds / hour)} hours"
  else if seconds < month
    "#{1 + Math.ceil(seconds / day)} days"
  else if seconds < year
    "#{1 + Math.ceil(seconds / month)} months"
  else if seconds < century
    "#{1 + Math.ceil(seconds / year)} years"
  else
    'centuries'
