///|
priv struct Parser {
  tokens : Array[Token]
  mut idx : Int
}

///|
fn Parser::peek(self : Parser) -> Token {
  self.peek_nth(0)
}

///|
fn Parser::peek_nth(self : Parser, n : Int) -> Token {
  let pos = self.idx + n
  if pos >= self.tokens.length() {
    self.tokens[self.tokens.length() - 1]
  } else {
    self.tokens[pos]
  }
}

///|
fn Parser::skip(self : Parser) -> Unit {
  self.idx += 1
}

///|
fn Parser::parse_id(self : Parser) -> String raise ParseError {
  match self.peek() {
    LIdent(_, s) => {
      self.skip()
      s
    }
    other => raise UnexpectedToken(other)
  }
}

///|
fn Parser::parse_string(self : Parser) -> String raise ParseError {
  match self.peek() {
    String(_, s) => {
      self.skip()
      s
    }
    other => raise UnexpectedToken(other)
  }
}

///|
fn Parser::parse_array(self : Parser) -> Json raise ParseError {
  match self.peek() {
    LBracket(_) => {
      let elems = self.surround_series_values(LBracket, RBracket, Comma)
      Json::array(elems)
    }
    other => raise UnexpectedToken(other)
  }
}

///|
fn Parser::surround_series_values(
  self : Parser,
  l : TokenKind,
  r : TokenKind,
  sep : TokenKind,
) -> Array[Json] raise ParseError {
  if self.peek().kind() != l {
    raise UnexpectedToken(self.peek())
  }
  self.skip() // skip l
  let elems : Array[Json] = []
  if self.peek().kind() != r {
    while true {
      let expr = self.parse_expr()
      elems.push(expr)
      let next = self.peek().kind()
      if next == r {
        break
      } else if next == sep {
        self.skip()
        if self.peek().kind() == r {
          break
        }
      } else {
        raise UnexpectedToken(self.peek())
      }
    }
  }
  self.skip() // skip r
  elems
}

///|
fn Parser::surround_series_pairs(
  self : Parser,
  l : TokenKind,
  r : TokenKind,
  sep : TokenKind,
) -> Array[(String, Json)] raise ParseError {
  if self.peek().kind() != l {
    raise UnexpectedToken(self.peek())
  }
  self.skip() // skip l
  let elems : Array[(String, Json)] = []
  if self.peek().kind() != r {
    while true {
      let pair = self.parse_map_elem()
      elems.push(pair)
      let next = self.peek().kind()
      if next == r {
        break
      } else if next == sep {
        self.skip()
        if self.peek().kind() == r {
          break
        }
      } else {
        raise UnexpectedToken(self.peek())
      }
    }
  }
  self.skip() // skip r
  elems
}

///|
fn Parser::parse_map_elem(self : Parser) -> (String, Json) raise ParseError {
  let key = match self.peek() {
    LIdent(_, s) => {
      self.skip()
      s
    }
    String(_, s) => {
      self.skip()
      s
    }
    other => raise UnexpectedToken(other)
  }
  if self.peek().kind() != Colon {
    raise UnexpectedToken(self.peek())
  }
  self.skip() // skip ':'
  let value = self.parse_expr()
  (key, value)
}

///|
fn Parser::parse_map(self : Parser) -> Json raise ParseError {
  let elems = self.surround_series_pairs(LBrace, RBrace, Comma)
  Json::object(Map::from_iter(elems.iter()))
}

///|
fn Parser::parse_expr(self : Parser) -> Json raise ParseError {
  match self.peek() {
    LBracket(_) => self.parse_array()
    LBrace(_) => self.parse_map()
    True(_) => {
      self.skip()
      Json::boolean(true)
    }
    False(_) => {
      self.skip()
      Json::boolean(false)
    }
    String(_, s) => {
      self.skip()
      Json::string(s)
    }
    Int(_, n) => {
      self.skip()
      Json::number(n.to_double())
    }
    other => raise UnexpectedToken(other)
  }
}

///|
fn Parser::parse_apply(self : Parser) -> (String, Json) raise ParseError {
  let func_name = self.parse_id()
  let args = self.surround_series_pairs(LParen, RParen, Comma)
  (func_name, Json::object(Map::from_iter(args.iter())))
}

///|
fn Parser::parse_assign(self : Parser) -> (String, Json) raise ParseError {
  let key = self.parse_id()
  if self.peek().kind() != Equal {
    raise UnexpectedToken(self.peek())
  }
  self.skip() // skip '='
  let value = self.parse_expr()
  (key, value)
}

///|
fn Parser::parse_import(self : Parser) -> (String, Json) raise ParseError {
  self.skip() // skip 'import'
  let legacy_kind : String? = match self.peek() {
    String(_, "test") => {
      self.skip()
      Some("test-import")
    }
    String(_, "wbtest") => {
      self.skip()
      Some("wbtest-import")
    }
    String(_, _) => raise UnexpectedToken(self.peek())
    _ => None
  }
  let import_items = self.parse_import_items()
  let import_kind : String = match legacy_kind {
    Some(kind) => kind
    None =>
      if self.peek().kind() == For {
        self.skip()
        match self.peek() {
          String(_, "test") => {
            self.skip()
            "test-import"
          }
          String(_, "wbtest") => {
            self.skip()
            "wbtest-import"
          }
          _ => raise UnexpectedToken(self.peek())
        }
      } else {
        "import"
      }
  }
  (import_kind, Json::array(import_items))
}

///|
fn Parser::parse_import_items(self : Parser) -> Array[Json] raise ParseError {
  if self.peek().kind() != LBrace {
    raise UnexpectedToken(self.peek())
  }
  self.skip() // skip {
  let items : Array[Json] = []
  if self.peek().kind() != RBrace {
    while true {
      let path = self.parse_string()
      let synonym = match self.peek() {
        As(_) => {
          self.skip()
          match self.peek() {
            PkgName(_, synonym) => {
              self.skip()
              Some(synonym)
            }
            other => raise UnexpectedToken(other)
          }
        }
        PkgName(_, synonym) => {
          self.skip()
          Some(synonym)
        }
        _ => None
      }
      let item = match synonym {
        None => Json::string(path)
        Some(a) => { "path": path, "alias": a }
      }
      items.push(item)
      let next = self.peek().kind()
      if next == RBrace {
        break
      } else if next == Comma {
        self.skip()
        if self.peek().kind() == RBrace {
          break
        }
      } else {
        raise UnexpectedToken(self.peek())
      }
    }
  }
  self.skip() // skip }
  items
}

///|
fn Parser::parse_statement(self : Parser) -> (String, Json) raise ParseError {
  match self.peek() {
    Import(_) => self.parse_import()
    LIdent(_, _) =>
      match self.peek_nth(1).kind() {
        Equal => self.parse_assign()
        LParen => self.parse_apply()
        _ => raise UnexpectedToken(self.peek())
      }
    other => raise UnexpectedToken(other)
  }
}

///|
fn Parser::parse_statements(self : Parser) -> Json raise ParseError {
  let statements : Array[(String, Json)] = []
  while self.peek().kind() != Eof {
    let stmt = self.parse_statement()
    statements.push(stmt)
    if self.peek().kind() == Semi {
      self.skip()
    }
  }
  Json::object(Map::from_iter(statements.iter()))
}

///|
fn parse_tokens(tokens : Array[Token]) -> Json raise ParseError {
  let parser : Parser = { tokens, idx: 0 }
  parser.parse_statements()
}

///|
pub fn parse(input : String) -> Json raise ParseError {
  let tokens = tokenize(input) catch {
    LexError(loc, msg) => raise LexError(loc, msg)
    UnexpectedToken(tok) => raise UnexpectedToken(tok)
  }
  parse_tokens(tokens)
}