package jgo.tools.compiler
package lexer

import scala.collection.mutable.Map
import scala.util.parsing.input.Reader

object LexicalTrie {
  //this class is mutable, but LexicalTrie's public interface prevents modification
  private class TrieNode {
    private[LexicalTrie] val branches:  Map[Char, TrieNode] = Map()
    private[LexicalTrie] var accepting: Option[String]      = None
  }
  
  def apply(elems: String*) = {
    val ret = new LexicalTrie
    elems foreach { ret += _ }
    ret
  }
}

final class LexicalTrie {
  import LexicalTrie.TrieNode
  
  private val root = new TrieNode
  
  private def += (str: String): LexicalTrie = {
    var cur = root
    var i = 0
    while (i < str.length) {
      cur = cur.branches.getOrElseUpdate(str(i), new TrieNode)
      i += 1
    }
    cur.accepting = Some(str)
    this
  }
  
  def contains(str: String): Boolean = {
    var cur = root
    var i = 0
    while (i < str.length) {
      if (cur.branches contains str(i)) {
        cur = cur.branches(str(i))
        i += 1
      }
      else
        return false
    }
    assert(if (cur.accepting.isDefined) cur.accepting.get == str else true)
    return cur.accepting.isDefined
  }
  
  def matchingPrefixOf(r: Reader[Char]): (Option[String], Reader[Char]) =
    prefixOfFromNode(root, r)
  
  private def prefixOfFromNode(cur: TrieNode, r: Reader[Char]): (Option[String], Reader[Char]) =
    if (cur.branches contains r.first) {
      val fromNextNode = prefixOfFromNode(cur.branches get r.first get, r.rest)
      if (fromNextNode._1.isDefined) //_1 refers to the first (there is no zeroth) term of the pair
        fromNextNode
      else
        (cur.accepting, r)
    }
    else
      (cur.accepting, r)
}