///|
pub struct PreparedQuery {
  priv query : Query
  priv limits : Limits
}

///|
pub fn PreparedQuery::form(self : PreparedQuery) -> String {
  match self.query.form {
    Select => "SELECT"
    Ask => "ASK"
    Construct => "CONSTRUCT"
  }
}

///|
fn Parser::triples(self : Parser) -> Array[Pattern] raise QueryError {
  let s = self.slot()
  match s {
    Fixed(t) =>
      if self.template_mode && t.kind == LiteralKind {
        raise Syntax(self.position(), "literal template subject")
      }
    _ => ()
  }
  let result = []
  while true {
    let p = self.read_path()
    result.push(Triple(s, p, self.slot()))
    while self.sym(",") {
      result.push(Triple(s, p, self.slot()))
    }
    if !self.sym(";") {
      break
    }
    while self.sym(";") {
      ()
    }
    if self.current() == Symbol("}") || self.current() == Symbol(".") {
      break
    }
  }
  result
}

///|
fn Parser::values(self : Parser) -> Pattern raise QueryError {
  let list = self.sym("(")
  let names = []
  if list {
    while !self.sym(")") {
      names.push(self.variable())
    }
  } else {
    names.push(self.variable())
  }
  let seen : Map[String, Bool] = Map([])
  for name in names {
    if seen.get(name) is Some(_) {
      raise Syntax(self.position(), "duplicate VALUES variable")
    }
    seen[name] = true
  }
  self.expect("{")
  let rows = []
  while !self.sym("}") {
    if list {
      self.expect("(")
    }
    let values = []
    for _ in names {
      values.push(
        if self.word("UNDEF") {
          None
        } else {
          Some(self.read_term())
        },
      )
    }
    if list {
      self.expect(")")
    }
    rows.push(values)
    if rows.length() > self.limits.max_bindings {
      raise Resource("VALUES rows")
    }
  }
  Values(names, rows)
}

///|
fn Parser::group(self : Parser) -> Group raise QueryError {
  self.enter()
  self.expect("{")
  let patterns = []
  while !self.sym("}") {
    let pat = if self.word("OPTIONAL") {
      Optional(self.group())
    } else if self.word("MINUS") {
      Minus(self.group())
    } else if self.word("FILTER") {
      Filter(self.expression())
    } else if self.word("BIND") {
      self.expect("(")
      let expression = self.expression()
      self.require_word("AS")
      let name = self.variable()
      self.expect(")")
      Bind(expression, name)
    } else if self.word("VALUES") {
      self.values()
    } else if self.word("GRAPH") {
      let graph = self.slot()
      Graph(graph, self.group())
    } else if self.current() == Symbol("{") {
      let inner = self.group()
      let alternatives = [inner]
      while self.word("UNION") {
        alternatives.push(self.group())
      }
      if alternatives.length() == 1 {
        Nested(inner)
      } else {
        Union(alternatives)
      }
    } else {
      if self.current() == EndToken {
        raise Syntax(self.position(), "unclosed group")
      }
      match self.current() {
        Word(w) if ["SERVICE", "SELECT", "DESCRIBE"].contains(w.to_upper()) =>
          raise Unsupported(self.position(), w)
        _ => ()
      }
      let triples = self.triples()
      patterns.append(triples)
      if self.sym(".") {
        continue
      }
      let permitted = match self.current() {
        Symbol("}" | "{") => true
        Word(w) =>
          ["OPTIONAL", "FILTER", "BIND", "VALUES", "GRAPH", "MINUS"].contains(
            w.to_upper(),
          )
        _ => false
      }
      if !permitted {
        raise Syntax(self.position(), "expected triple separator")
      }
      continue
    }
    patterns.push(pat)
    ignore(self.sym("."))
  }
  self.depth -= 1
  { patterns, }
}

///|
fn Parser::nonnegative(self : Parser) -> Int32 raise QueryError {
  let text = match self.advance() {
    NumToken(n) => n
    _ => raise Syntax(self.position(), "expected nonnegative integer")
  }
  let mut value = 0
  for c in text.to_array() {
    if !digit(c) {
      raise Syntax(self.position(), "expected nonnegative integer")
    }
    let n = c.to_int() - 48
    if value > (2147483647 - n) / 10 {
      raise Syntax(self.position(), "integer too large")
    }
    value = value * 10 + n
  }
  value
}

///|
fn Parser::modifier(self : Parser) -> Bool {
  match self.current() {
    EndToken => true
    Word(w) =>
      ["GROUP", "HAVING", "ORDER", "LIMIT", "OFFSET", "VALUES"].contains(
        w.to_upper(),
      )
    _ => false
  }
}

///|
pub fn prepare(
  source : String,
  limits~ : Limits = Limits::default(),
) -> PreparedQuery raise QueryError {
  let p = Parser::new(source, limits)
  p.prefixes()
  let form = if p.word("SELECT") {
    Select
  } else if p.word("ASK") {
    Ask
  } else if p.word("CONSTRUCT") {
    Construct
  } else {
    raise Unsupported(p.position(), "expected SELECT, ASK or CONSTRUCT")
  }
  let distinct = form == Select && p.word("DISTINCT")
  let all = form == Select && p.sym("*")
  let projections = []
  let names : Map[String, Bool] = Map([])
  if form == Select && !all {
    while p.current() is Var(_) || p.current() == Symbol("(") {
      let item = if p.sym("(") {
        let expression = p.expression()
        p.require_word("AS")
        let variable = p.variable()
        p.expect(")")
        Projection::{ expression, variable, }
      } else {
        let variable = p.variable()
        Projection::{ expression: Reference(variable), variable, }
      }
      if names.get(item.variable) is Some(_) {
        raise Syntax(p.position(), "duplicate projected variable")
      }
      names[item.variable] = true
      projections.push(item)
    }
    if projections.length() == 0 {
      raise Syntax(p.position(), "empty projection")
    }
  }
  let template = []
  if form == Construct {
    p.set_template_mode(true)
    let t = p.group()
    p.set_template_mode(false)
    for pat in t.patterns {
      match pat {
        Triple(s, path, o) =>
          match path {
            Predicate(_) | PathVar(_) => template.push((s, path, o))
            _ => raise Syntax(p.position(), "paths cannot appear in a template")
          }
        _ => raise Syntax(p.position(), "template only accepts triples")
      }
    }
  }
  ignore(p.word("WHERE"))
  let group = p.group()
  let group_by = []
  let having = []
  let order = []
  let mut offset = 0
  let mut limit : Int32? = None
  let mut saw_offset = false
  if p.word("GROUP") {
    p.require_word("BY")
    while !p.modifier() {
      group_by.push(p.expression())
    }
    if group_by.length() == 0 {
      raise Syntax(p.position(), "empty GROUP BY")
    }
  }
  if p.word("HAVING") {
    while !p.modifier() {
      having.push(p.expression())
    }
    if having.length() == 0 {
      raise Syntax(p.position(), "empty HAVING")
    }
  }
  if p.word("ORDER") {
    p.require_word("BY")
    while !p.modifier() {
      let descending = p.word("DESC")
      if descending || p.word("ASC") {
        p.expect("(")
        let expression = p.expression()
        p.expect(")")
        order.push((expression, descending))
      } else {
        order.push((p.expression(), false))
      }
    }
    if order.length() == 0 {
      raise Syntax(p.position(), "empty ORDER BY")
    }
  }
  while p.current() != EndToken {
    if p.word("LIMIT") && limit is None {
      limit = Some(p.nonnegative())
    } else if p.word("OFFSET") && !saw_offset {
      offset = p.nonnegative()
      saw_offset = true
    } else {
      raise Unsupported(p.position(), "query modifier or trailing input")
    }
  }
  let query = Query::{
    form,
    projections,
    all,
    distinct,
    group,
    group_by,
    having,
    order,
    offset,
    limit,
    template,
  }
  validate_depth(query, limits.max_depth)
  validate_query(query)
  { query, limits, }
}