///|
pub fn allowed(text : String, agent : String, path : String) -> Bool {
  decide(parse(text), agent, path).allowed
}

///|
pub fn decide(robots : Robots, agent : String, path : String) -> Decision {
  let normalized = normalize_path(path)
  match best_group(robots, agent) {
    Some(group) => decide_group(group, agent, normalized)
    None =>
      {
        allowed: true,
        agent,
        path: normalized,
        matched_kind: "none",
        matched_pattern: "",
      }
  }
}

///|
pub fn best_group(robots : Robots, agent : String) -> Group? {
  let mut best : Group? = None
  let mut best_score = -1
  for group in robots.groups {
    let score = group_agent_score(group, agent)
    if score > best_score {
      best = Some(group)
      best_score = score
    }
  }
  if best_score >= 0 {
    best
  } else {
    None
  }
}

///|
pub fn decide_group(group : Group, agent : String, path : String) -> Decision {
  let mut matched : Rule? = None
  let mut best_len = -1
  for rule in group.rules {
    if rule_matches(rule, path) {
      let weight = rule_weight(rule)
      if weight > best_len {
        matched = Some(rule)
        best_len = weight
      } else if weight == best_len &&
        matched is Some(old) &&
        is_allow(rule) &&
        !is_allow(old) {
        matched = Some(rule)
      }
    }
  }
  match matched {
    Some(rule) =>
      {
        allowed: is_allow(rule),
        agent,
        path,
        matched_kind: rule.kind,
        matched_pattern: rule.pattern,
      }
    None =>
      { allowed: true, agent, path, matched_kind: "none", matched_pattern: "" }
  }
}

///|
pub fn rule_matches(rule : Rule, path : String) -> Bool {
  pattern_matches(rule.pattern, path)
}

///|
pub fn pattern_matches(pattern : String, path : String) -> Bool {
  if pattern == "" {
    return false
  }
  if pattern == "/" {
    return true
  }
  if pattern.has_suffix("$") {
    let base = pattern.unsafe_substring(start=0, end=pattern.length() - 1)
    return wildcard_prefix_match(base, path) && wildcard_suffix_ok(base, path)
  }
  wildcard_prefix_match(pattern, path)
}

///|
pub fn wildcard_prefix_match(pattern : String, path : String) -> Bool {
  if pattern.find("*") is None {
    return path.has_prefix(pattern)
  }
  let pieces = split_char(pattern, '*')
  let mut offset = 0
  let mut first = true
  for piece in pieces {
    if piece == "" {
      first = false
      continue
    }
    if first {
      if !path.has_prefix(piece) {
        return false
      }
      offset = piece.length()
    } else {
      match path.unsafe_substring(start=offset, end=path.length()).find(piece) {
        Some(found) => offset = offset + found + piece.length()
        None => return false
      }
    }
    first = false
  }
  true
}

///|
pub fn wildcard_suffix_ok(pattern : String, path : String) -> Bool {
  let pieces = split_char(pattern, '*')
  for index = pieces.length() - 1; index >= 0; index = index - 1 {
    let piece = pieces[index]
    if piece != "" {
      return path.has_suffix(piece)
    }
  }
  true
}

///|
pub fn decision_line(decision : Decision) -> String {
  let verdict = if decision.allowed { "allow" } else { "disallow" }
  verdict +
  " " +
  decision.path +
  " by " +
  decision.matched_kind +
  ":" +
  decision.matched_pattern
}