///|
/// JSON value representation
pub(all) enum JsonValue {
  Object(Map[String, JsonValue])
  Array(Array[JsonValue])
  String(String)
  Number(Double)
  Boolean(Bool)
  Null
} derive(Eq, Show)

///|
/// Token types for JSON lexer
enum Token {
  LeftBrace // {
  RightBrace // }
  LeftBracket // [
  RightBracket // ]
  Colon // :
  Comma // ,
  String(String)
  Number(Double)
  True
  False
  Null
  Eof
} derive(Eq, Show)

///|
/// Position in source for error reporting
struct Position {
  line : Int
  column : Int
} derive(Show, Eq)

///|
/// Parser errors
pub(all) suberror ParseError {
  UnexpectedToken(Position, Token, String)
  UnexpectedChar(Position, Char)
  UnexpectedEof(Position)
  InvalidNumber(Position, String)
  InvalidEscape(Position, Char)
  InvalidUnicode(Position, String)
} derive(Show, Eq)

///|
/// Lexer state
priv struct Lexer {
  input : String
  mut pos : Int
  mut line : Int
  mut column : Int
}

///|
/// Create a new lexer
fn Lexer::new(input : String) -> Lexer {
  { input, pos: 0, line: 1, column: 1 }
}

///|
/// Get current position
fn Lexer::position(self : Lexer) -> Position {
  { line: self.line, column: self.column }
}

///|
/// Peek current character without consuming
fn Lexer::peek(self : Lexer) -> Char? {
  if self.pos < self.input.length() {
    self.input.get_char(self.pos)
  } else {
    None
  }
}

///|
/// Advance to next character
fn Lexer::advance(self : Lexer) -> Unit {
  if self.pos < self.input.length() {
    match self.input.get_char(self.pos) {
      Some('\n') => {
        self.line += 1
        self.column = 1
      }
      Some(_) => self.column += 1
      None => ()
    }
    self.pos += 1
  }
}

///|
/// Skip whitespace
fn Lexer::skip_whitespace(self : Lexer) -> Unit {
  while true {
    match self.peek() {
      Some(c) if c is (' ' | '\t' | '\n' | '\r') => self.advance()
      _ => break
    }
  }
}

///|
/// Parse a string literal
fn Lexer::parse_string(self : Lexer) -> String raise ParseError {
  let start_pos = self.position()
  self.advance() // Skip opening quote
  let buf = @buffer.new()
  let mut done = false
  while not(done) {
    match self.peek() {
      None => raise ParseError::UnexpectedEof(start_pos)
      Some('"') => {
        self.advance()
        done = true
      }
      Some('\\') => {
        self.advance()
        match self.peek() {
          None => raise ParseError::UnexpectedEof(self.position())
          Some('\"') => {
            buf.write_char('"')
            self.advance()
          }
          Some('\\') => {
            buf.write_char('\\')
            self.advance()
          }
          Some('/') => {
            buf.write_char('/')
            self.advance()
          }
          Some('b') => {
            buf.write_char('\b')
            self.advance()
          }
          Some('f') => {
            buf.write_char('\u000C')
            self.advance()
          }
          Some('n') => {
            buf.write_char('\n')
            self.advance()
          }
          Some('r') => {
            buf.write_char('\r')
            self.advance()
          }
          Some('t') => {
            buf.write_char('\t')
            self.advance()
          }
          Some('u') => {
            self.advance()
            let hex_start = self.pos
            let mut hex_value = 0
            for i = 0; i < 4; i = i + 1 {
              match self.peek() {
                Some(c) if c is ('0'..='9') => {
                  hex_value = hex_value * 16 + (c.to_int() - '0'.to_int())
                  self.advance()
                }
                Some(c) if c is ('a'..='f') => {
                  hex_value = hex_value * 16 + (c.to_int() - 'a'.to_int() + 10)
                  self.advance()
                }
                Some(c) if c is ('A'..='F') => {
                  hex_value = hex_value * 16 + (c.to_int() - 'A'.to_int() + 10)
                  self.advance()
                }
                _ => {
                  let hex_str = try! self.input[hex_start:self.pos].to_string()
                  raise ParseError::InvalidUnicode(self.position(), hex_str)
                }
              }
            }
            buf.write_char(hex_value.unsafe_to_char())
          }
          Some(c) => raise ParseError::InvalidEscape(self.position(), c)
        }
      }
      Some(c) => {
        buf.write_char(c)
        self.advance()
      }
    }
  }
  buf.to_string()
}

///|
/// Parse a number literal
fn Lexer::parse_number(self : Lexer) -> Double raise ParseError {
  let start_pos = self.position()
  let start = self.pos

  // Optional minus
  if self.peek() is Some('-') {
    self.advance()
  }

  // Integer part
  match self.peek() {
    Some('0') => self.advance()
    Some(c) if c is ('1'..='9') => {
      self.advance()
      while true {
        match self.peek() {
          Some(c) if c is ('0'..='9') => self.advance()
          _ => break
        }
      }
    }
    _ => raise ParseError::InvalidNumber(start_pos, "Expected digit")
  }

  // Fractional part
  if self.peek() is Some('.') {
    self.advance()
    let mut has_digit = false
    while true {
      match self.peek() {
        Some(c) if c is ('0'..='9') => {
          has_digit = true
          self.advance()
        }
        _ => break
      }
    }
    if not(has_digit) {
      raise ParseError::InvalidNumber(
        start_pos, "Expected digit after decimal point",
      )
    }
  }

  // Exponent part
  if self.peek() is Some('e' | 'E') {
    self.advance()
    if self.peek() is Some('+' | '-') {
      self.advance()
    }
    let mut has_digit = false
    while true {
      match self.peek() {
        Some(c) if c is ('0'..='9') => {
          has_digit = true
          self.advance()
        }
        _ => break
      }
    }
    if not(has_digit) {
      raise ParseError::InvalidNumber(start_pos, "Expected digit in exponent")
    }
  }
  let num_str = try! self.input[start:self.pos].to_string()
  @strconv.parse_double(num_str) catch {
    _ => raise ParseError::InvalidNumber(start_pos, num_str)
  }
}

///|
/// Get next token
fn Lexer::next_token(self : Lexer) -> Token raise ParseError {
  self.skip_whitespace()
  match self.peek() {
    None => Token::Eof
    Some('{') => {
      self.advance()
      Token::LeftBrace
    }
    Some('}') => {
      self.advance()
      Token::RightBrace
    }
    Some('[') => {
      self.advance()
      Token::LeftBracket
    }
    Some(']') => {
      self.advance()
      Token::RightBracket
    }
    Some(':') => {
      self.advance()
      Token::Colon
    }
    Some(',') => {
      self.advance()
      Token::Comma
    }
    Some('"') => Token::String(self.parse_string())
    Some(c) if c is ('-' | '0'..='9') => Token::Number(self.parse_number())
    Some('t') => {
      let pos = self.position()
      self.advance()
      if self.peek() is Some('r') {
        self.advance()
        if self.peek() is Some('u') {
          self.advance()
          if self.peek() is Some('e') {
            self.advance()
            Token::True
          } else {
            raise ParseError::UnexpectedChar(pos, 'e')
          }
        } else {
          raise ParseError::UnexpectedChar(pos, 'u')
        }
      } else {
        raise ParseError::UnexpectedChar(pos, 'r')
      }
    }
    Some('f') => {
      let pos = self.position()
      self.advance()
      if self.peek() is Some('a') {
        self.advance()
        if self.peek() is Some('l') {
          self.advance()
          if self.peek() is Some('s') {
            self.advance()
            if self.peek() is Some('e') {
              self.advance()
              Token::False
            } else {
              raise ParseError::UnexpectedChar(pos, 'e')
            }
          } else {
            raise ParseError::UnexpectedChar(pos, 's')
          }
        } else {
          raise ParseError::UnexpectedChar(pos, 'l')
        }
      } else {
        raise ParseError::UnexpectedChar(pos, 'a')
      }
    }
    Some('n') => {
      let pos = self.position()
      self.advance()
      if self.peek() is Some('u') {
        self.advance()
        if self.peek() is Some('l') {
          self.advance()
          if self.peek() is Some('l') {
            self.advance()
            Token::Null
          } else {
            raise ParseError::UnexpectedChar(pos, 'l')
          }
        } else {
          raise ParseError::UnexpectedChar(pos, 'l')
        }
      } else {
        raise ParseError::UnexpectedChar(pos, 'u')
      }
    }
    Some(c) => raise ParseError::UnexpectedChar(self.position(), c)
  }
}

///|
/// Parser state
priv struct Parser {
  lexer : Lexer
  mut current : Token
}

///|
/// Create a new parser
fn Parser::new(input : String) -> Parser raise ParseError {
  let lexer = Lexer::new(input)
  let current = lexer.next_token()
  { lexer, current }
}

///|
/// Advance to next token
fn Parser::advance(self : Parser) -> Unit raise ParseError {
  self.current = self.lexer.next_token()
}

///|
/// Expect and consume a specific token
fn Parser::expect(self : Parser, expected : Token) -> Unit raise ParseError {
  if self.current == expected {
    self.advance()
  } else {
    raise ParseError::UnexpectedToken(
      self.lexer.position(),
      self.current,
      "Expected \{expected}",
    )
  }
}

///|
/// Parse a JSON value
fn Parser::parse_value(self : Parser) -> JsonValue raise ParseError {
  match self.current {
    Token::LeftBrace => self.parse_object()
    Token::LeftBracket => self.parse_array()
    Token::String(s) => {
      self.advance()
      JsonValue::String(s)
    }
    Token::Number(n) => {
      self.advance()
      JsonValue::Number(n)
    }
    Token::True => {
      self.advance()
      JsonValue::Boolean(true)
    }
    Token::False => {
      self.advance()
      JsonValue::Boolean(false)
    }
    Token::Null => {
      self.advance()
      JsonValue::Null
    }
    tok =>
      raise ParseError::UnexpectedToken(
        self.lexer.position(),
        tok,
        "Expected JSON value",
      )
  }
}

///|
/// Parse a JSON object
fn Parser::parse_object(self : Parser) -> JsonValue raise ParseError {
  self.expect(Token::LeftBrace)
  let obj : Map[String, JsonValue] = {}
  if self.current != Token::RightBrace {
    while true {
      // Parse key
      let key = match self.current {
        Token::String(s) => {
          self.advance()
          s
        }
        tok =>
          raise ParseError::UnexpectedToken(
            self.lexer.position(),
            tok,
            "Expected string key",
          )
      }

      // Expect colon
      self.expect(Token::Colon)

      // Parse value
      let value = self.parse_value()
      obj[key] = value

      // Check for continuation
      if self.current == Token::Comma {
        self.advance()
      } else {
        break
      }
    }
  }
  self.expect(Token::RightBrace)
  JsonValue::Object(obj)
}

///|
/// Parse a JSON array
fn Parser::parse_array(self : Parser) -> JsonValue raise ParseError {
  self.expect(Token::LeftBracket)
  let arr : Array[JsonValue] = []
  if self.current != Token::RightBracket {
    while true {
      arr.push(self.parse_value())
      if self.current == Token::Comma {
        self.advance()
      } else {
        break
      }
    }
  }
  self.expect(Token::RightBracket)
  JsonValue::Array(arr)
}

///|
/// Parse JSON string into a JsonValue
pub fn parse(input : String) -> JsonValue raise ParseError {
  let parser = Parser::new(input)
  let value = parser.parse_value()
  if parser.current != Token::Eof {
    raise ParseError::UnexpectedToken(
      parser.lexer.position(),
      parser.current,
      "Expected end of input",
    )
  }
  value
}

///|
/// Stringify a JsonValue with optional indentation
pub fn stringify(value : JsonValue, indent? : Int = 0) -> String {
  let buf = @buffer.new()
  stringify_impl(value, buf, 0, indent)
  buf.to_string()
}

///|
/// Internal stringify implementation
fn stringify_impl(
  value : JsonValue,
  buf : @buffer.Buffer,
  depth : Int,
  indent : Int,
) -> Unit {
  match value {
    JsonValue::Null => buf.write_string("null")
    JsonValue::Boolean(true) => buf.write_string("true")
    JsonValue::Boolean(false) => buf.write_string("false")
    JsonValue::Number(n) =>
      if n.is_inf() || n.is_nan() {
        buf.write_string("null")
      } else {
        buf.write_string(n.to_string())
      }
    JsonValue::String(s) => {
      buf.write_char('"')
      for char in s {
        match char {
          '"' => buf.write_string("\\\"")
          '\\' => buf.write_string("\\\\")
          '\b' => buf.write_string("\\b")
          '\u000C' => buf.write_string("\\f")
          '\n' => buf.write_string("\\n")
          '\r' => buf.write_string("\\r")
          '\t' => buf.write_string("\\t")
          c => buf.write_char(c)
        }
      }
      buf.write_char('"')
    }
    JsonValue::Array(arr) => {
      buf.write_char('[')
      if arr.length() > 0 {
        if indent > 0 {
          buf.write_char('\n')
        }
        for i, item in arr {
          if indent > 0 {
            let mut j = 0
            while j < (depth + 1) * indent {
              buf.write_char(' ')
              j = j + 1
            }
          }
          stringify_impl(item, buf, depth + 1, indent)
          if i < arr.length() - 1 {
            buf.write_char(',')
          }
          if indent > 0 {
            buf.write_char('\n')
          }
        }
        if indent > 0 {
          let mut j = 0
          while j < depth * indent {
            buf.write_char(' ')
            j = j + 1
          }
        }
      }
      buf.write_char(']')
    }
    JsonValue::Object(obj) => {
      buf.write_char('{')
      let keys = obj.keys().collect()
      if keys.length() > 0 {
        if indent > 0 {
          buf.write_char('\n')
        }
        for i, key in keys {
          if indent > 0 {
            let mut j = 0
            while j < (depth + 1) * indent {
              buf.write_char(' ')
              j = j + 1
            }
          }
          buf.write_char('"')
          buf.write_string(key)
          buf.write_char('"')
          buf.write_char(':')
          if indent > 0 {
            buf.write_char(' ')
          }
          match obj.get(key) {
            Some(v) => stringify_impl(v, buf, depth + 1, indent)
            None => ()
          }
          if i < keys.length() - 1 {
            buf.write_char(',')
          }
          if indent > 0 {
            buf.write_char('\n')
          }
        }
        if indent > 0 {
          let mut j = 0
          while j < depth * indent {
            buf.write_char(' ')
            j = j + 1
          }
        }
      }
      buf.write_char('}')
    }
  }
}

///|
/// Convert JsonValue to pretty-printed string
pub fn JsonValue::to_pretty_string(self : JsonValue) -> String {
  stringify(self, indent=2)
}