///|
enum Part {
  Text(String)
  Variable(String, Array[Part]?)
  Command(Array[ParsedCommand])
} derive(Debug)

///|
struct Word {
  parts : Array[Part]
  expand : Bool
} derive(Debug)

///|
struct ParsedCommand {
  words : Array[Word]
  source : String
  line : Int
} derive(Debug)

///|
struct ScriptParser {
  chars : Array[Char]
  mut pos : Int
  mut line_pos : Int
  mut line : Int
  mut command_start : Int
  mut command_line : Int
  mut trailing_continuation : Bool
} derive(Debug)

///|
fn ScriptParser::new(source : String) -> ScriptParser {
  {
    chars: source.to_array(),
    pos: 0,
    line_pos: 0,
    line: 1,
    command_start: 0,
    command_line: 1,
    trailing_continuation: false,
  }
}

///|
fn ScriptParser::peek(self : ScriptParser, offset? : Int = 0) -> Char {
  self.chars.get(self.pos + offset).unwrap_or('\u0000')
}

///|
fn namechar(c : Char) -> Bool {
  (c >= 'a' && c <= 'z') ||
  (c >= 'A' && c <= 'Z') ||
  (c >= '0' && c <= '9') ||
  c == '_' ||
  c == ':'
}

///|
fn ScriptParser::braces(self : ScriptParser) -> String raise TclError {
  self.pos += 1
  let mut level = 1
  let out = StringBuilder()
  while self.pos < self.chars.length() {
    let c = self.peek()
    self.pos += 1
    if c == '\\' && self.pos < self.chars.length() {
      if self.peek() == '\n' {
        let (text, next) = backslash(self.chars, self.pos - 1)
        out.write_string(text)
        self.pos = next
      } else {
        out.write_char(c)
        out.write_char(self.peek())
        self.pos += 1
      }
      continue
    }
    if c == '{' {
      level += 1
    }
    if c == '}' {
      level -= 1
      if level == 0 {
        return out.to_string()
      }
    }
    out.write_char(c)
  }
  raise Invalid("missing close-brace")
}

///|
fn ScriptParser::variable(
  self : ScriptParser,
  depth : Int,
) -> Part raise TclError {
  self.pos += 1
  if self.peek() == '{' {
    self.pos += 1
    let start = self.pos
    while self.pos < self.chars.length() && self.peek() != '}' {
      self.pos += 1
    }
    if self.pos == self.chars.length() {
      raise Invalid("missing variable close-brace")
    }
    let name = String::from_array(self.chars[start:self.pos])
    self.pos += 1
    return Variable(name, None)
  }
  let start = self.pos
  while namechar(self.peek()) {
    if self.peek() == ':' {
      if self.peek(offset=1) != ':' {
        break
      }
      while self.peek() == ':' {
        self.pos += 1
      }
    } else {
      self.pos += 1
    }
  }
  let name = String::from_array(self.chars[start:self.pos])
  if self.peek() == '(' {
    self.pos += 1
    return Variable(name, Some(self.parts(')', false, depth + 1)))
  }
  if name.is_empty() {
    Text("$")
  } else {
    Variable(name, None)
  }
}

///|
fn ScriptParser::parts(
  self : ScriptParser,
  stop : Char,
  raw : Bool,
  depth : Int,
  bracket? : Bool = false,
) -> Array[Part] raise TclError {
  if depth > 64 {
    raise Invalid("substitution depth")
  }
  let parts = []
  let mut text = StringBuilder()
  while self.pos < self.chars.length() {
    let c = self.peek()
    if stop != '\u0000' && c == stop {
      self.pos += 1
      parts.push(Text(text.to_string()))
      return parts
    }
    if raw && (list_space(c) || c == ';' || (bracket && c == ']')) {
      break
    }
    if raw && c == '\\' && self.peek(offset=1) == '\n' {
      break
    }
    if c == '$' || c == '[' {
      parts.push(Text(text.to_string()))
      text = StringBuilder()
      if c == '$' {
        parts.push(self.variable(depth))
      } else {
        self.pos += 1
        parts.push(Command(self.script(true, depth + 1)))
      }
    } else if c == '\\' {
      let (value, next) = backslash(self.chars, self.pos)
      text.write_string(value)
      self.pos = next
    } else {
      text.write_char(c)
      self.pos += 1
    }
  }
  if stop != '\u0000' {
    raise Invalid("missing closing substitution delimiter")
  }
  parts.push(Text(text.to_string()))
  parts
}

///|
fn ScriptParser::word(
  self : ScriptParser,
  depth : Int,
  bracket : Bool,
) -> Word raise TclError {
  let mut expand = false
  if self.peek() == '{' &&
    self.peek(offset=1) == '*' &&
    self.peek(offset=2) == '}' &&
    self.pos + 3 < self.chars.length() &&
    !list_space(self.peek(offset=3)) &&
    self.peek(offset=3) != ';' &&
    (!bracket || self.peek(offset=3) != ']') {
    expand = true
    self.pos += 3
  }
  let grouped = self.peek() == '{' || self.peek() == '"'
  let parts = if self.peek() == '{' {
    [Text(self.braces())]
  } else if self.peek() == '"' {
    self.pos += 1
    self.parts('"', false, depth)
  } else {
    self.parts('\u0000', true, depth, bracket~)
  }
  if grouped &&
    self.pos < self.chars.length() &&
    !list_space(self.peek()) &&
    self.peek() != ';' &&
    !(self.peek() == '\\' && self.peek(offset=1) == '\n') &&
    (!bracket || self.peek() != ']') {
    raise Invalid("extra characters after grouped word")
  }
  { parts, expand, }
}

///|
fn ScriptParser::script(
  self : ScriptParser,
  bracket : Bool,
  depth : Int,
  one? : Bool = false,
) -> Array[ParsedCommand] raise TclError {
  if depth > 64 {
    raise Invalid("script nesting limit")
  }
  let commands = []
  let mut words = []
  let mut start = self.pos
  let mut line = self.source_line()
  while self.pos < self.chars.length() {
    let c = self.peek()
    if c == ']' && bracket {
      self.pos += 1
      if !words.is_empty() {
        commands.push(self.parsed_command(words, start, line, self.pos - 1))
      }
      return commands
    }
    if c == '\\' && self.peek(offset=1) == '\n' {
      if self.pos + 2 == self.chars.length() {
        self.trailing_continuation = true
      }
      let (_, next) = backslash(self.chars, self.pos)
      self.pos = next
      continue
    }
    if c == ';' || c == '\n' {
      self.pos += 1
      if !words.is_empty() {
        commands.push(self.parsed_command(words, start, line, self.pos - 1))
        words = []
      }
      if one && !commands.is_empty() {
        return commands
      }
    } else if list_space(c) {
      self.pos += 1
    } else if c == '#' && words.is_empty() {
      while self.pos < self.chars.length() && self.peek() != '\n' {
        if self.peek() == '\\' && self.peek(offset=1) == '\n' {
          if self.pos + 2 == self.chars.length() {
            self.trailing_continuation = true
          }
          let (_, next) = backslash(self.chars, self.pos)
          self.pos = next
        } else if self.peek() == '\\' && self.pos + 1 < self.chars.length() {
          self.pos += 2
        } else {
          self.pos += 1
        }
      }
    } else {
      if words.is_empty() {
        start = self.pos
        line = self.source_line()
        self.command_start = start
        self.command_line = line
      }
      words.push(self.word(depth, bracket))
      if words.length() > 4096 {
        raise Invalid("command argument limit")
      }
    }
    if commands.length() > 10000 {
      raise Invalid("script command limit")
    }
  }
  if bracket {
    raise Invalid("missing close-bracket")
  }
  if !words.is_empty() {
    commands.push(self.parsed_command(words, start, line, self.pos))
  }
  commands
}

///|
fn Interpreter::expand_values(
  self : Interpreter,
  parts : Array[Part],
  depth : Int,
) -> TclValue raise TclError {
  if depth > 64 {
    raise Invalid("substitution depth")
  }
  let mut result : TclValue? = None
  let mut size = 0
  let mut builder : StringBuilder? = None
  for part in parts {
    self.tick()
    let value = match part {
      Text(text) if text.is_empty() => continue
      Text(text) => text_value(text)
      Variable(name, index) => {
        let name = match index {
          Some(parts) => name + "(" + self.expand_parts(parts, depth + 1) + ")"
          None => name
        }
        self.read_value(name)
      }
      Command(commands) => self.execute_command_values(commands, depth + 1)
    }
    size += value.text.length()
    if size > 1000000 {
      raise Invalid("substitution result size limit")
    }
    match result {
      None => result = Some(value)
      Some(first) =>
        if !value.text.is_empty() {
          match builder {
            Some(out) => out.write_string(value.text)
            None => {
              let out = StringBuilder()
              out.write_string(first.text)
              out.write_string(value.text)
              builder = Some(out)
            }
          }
        }
    }
  }
  match builder {
    Some(out) => text_value(out.to_string())
    None =>
      match result {
        Some(value) => value
        None => text_value("")
      }
  }
}

///|
// None denotes a command whose words all expanded away. The caller chooses
// the surrounding script's existing empty-command result behavior.
fn Interpreter::execute_parsed_command(
  self : Interpreter,
  parsed : ParsedCommand,
  depth : Int,
  discard_result? : Bool = false,
) -> TclValue? raise TclError {
  if depth > 64 {
    raise Invalid("execution nesting limit")
  }
  let args = []
  try {
    for word in parsed.words {
      let value = self.expand_values(word.parts, depth + 1)
      if word.expand {
        for item in value.as_list() {
          args.push(item)
        }
      } else {
        args.push(value)
      }
      if args.length() > 4096 {
        raise Invalid("expanded argument limit")
      }
    }
    if args.is_empty() {
      return None
    }
    let result = self.command_value(args, depth, discard_result~)
    if result.text.length() > 1000000 {
      raise Invalid("command result size limit")
    }
    Some(result)
  } catch {
    error =>
      raise Signal(
        self.annotate_error(
          outcome(error),
          parsed.source,
          parsed.line,
          args.map(v => v.text),
        ),
      )
  }
}

///|
fn Interpreter::execute_command_values(
  self : Interpreter,
  commands : Array[ParsedCommand],
  depth : Int,
  discard_result? : Bool = false,
) -> TclValue raise TclError {
  if depth > 64 {
    raise Invalid("execution nesting limit")
  }
  let mut result : TclValue? = None
  for parsed in commands {
    if self.execute_parsed_command(parsed, depth, discard_result~)
      is Some(value) {
      result = Some(value)
    }
  }
  match result {
    Some(value) => value
    None => text_value("")
  }
}

///|
fn Interpreter::execute_value(
  self : Interpreter,
  source : String,
  depth : Int,
  discard_result? : Bool = false,
) -> TclValue raise TclError {
  if source.length() > 100000 {
    raise Invalid("script size limit")
  }
  let program = self.cached_script(source)
  let mut result : TclValue? = None
  let mut index = 0
  while true {
    if index == program.commands.length() {
      if program.failure is Some(message) {
        raise Signal(
          self.annotate_error(
            completion_error(message),
            String::from_array(
              program.parser.chars[program.parser.command_start:],
            ),
            program.parser.command_line,
            [],
          ),
        )
      }
      if program.parser.pos == program.parser.chars.length() {
        break
      }
      try {
        let commands = program.parser.script(false, 0, one=true)
        for command in commands {
          program.commands.push(command)
        }
      } catch {
        Invalid(message) => {
          program.failure = Some(message)
          raise Signal(
            self.annotate_error(
              completion_error(message),
              String::from_array(
                program.parser.chars[program.parser.command_start:],
              ),
              program.parser.command_line,
              [],
            ),
          )
        }
        error => raise error
      }
      if index == program.commands.length() {
        continue
      }
    }
    result = self.execute_parsed_command(
      program.commands[index],
      depth,
      discard_result~,
    )
    index += 1
  }
  match result {
    Some(value) => value
    None => text_value("")
  }
}

///|
fn ScriptParser::source_line(self : ScriptParser) -> Int {
  while self.line_pos < self.pos {
    if self.chars[self.line_pos] == '\n' {
      self.line += 1
    }
    self.line_pos += 1
  }
  self.line
}

///|
fn ScriptParser::parsed_command(
  self : ScriptParser,
  words : Array[Word],
  start : Int,
  line : Int,
  end : Int,
) -> ParsedCommand {
  {
    words,
    source: String::from_array(self.chars[start:end]).trim_end().to_owned(),
    line,
  }
}

///|
fn Interpreter::expand_parts(
  self : Interpreter,
  parts : Array[Part],
  depth : Int,
) -> String raise TclError {
  self.expand_values(parts, depth).text
}

///|
fn Interpreter::execute_commands(
  self : Interpreter,
  commands : Array[ParsedCommand],
  depth : Int,
) -> String raise TclError {
  self.execute_command_values(commands, depth).text
}