// Pipe tables. A table starts when the line after a paragraph's last line is a
// delimiter row of the same width, and grows one row per following line until
// a blank line or a block that cannot live inside a table.

///|
/// One row of a table as it was written: the cells, with the escapes of pipes
/// resolved but nothing else, and where the row ended.
priv struct TableRow {
  cells : Array[String]
}

///|
/// Turn `\|` back into `|`, which is how a cell holds a pipe of its own.
fn unescape_pipes(text : StringView) -> String {
  if !text.contains_char('\\') {
    return text.to_owned()
  }
  let buf = StringBuilder()
  let n = text.length()
  let mut i = 0
  while i < n {
    if cu(text, i) == 0x5C && cu(text, i + 1) == 0x7C {
      i = i + 1
    }
    buf.write_char(char_at(text, i))
    i = i + char_at(text, i).utf16_len()
  }
  buf.to_string()
}

///|
/// Parse one row of a table starting at `start`. The row has to reach the end
/// of the text, which is one line.
fn parse_table_row(text : StringView, start : Int32) -> TableRow? {
  let n = text.length()
  let mut offset = start + scan_table_cell_end(text, start)
  let cells : Array[String] = []
  let mut expect_more = true
  while offset < n && expect_more {
    let cell_len = scan_table_cell(text, offset)
    let pipe_len = scan_table_cell_end(text, offset + cell_len)
    if cell_len > 0 || pipe_len > 0 {
      let raw = text.clamped_view(start=offset, end=offset + cell_len)
      cells.push(trim_view(unescape_pipes(raw)[:]).to_owned())
    }
    offset = offset + cell_len + pipe_len
    if pipe_len > 0 {
      expect_more = true
    } else {
      offset = offset + scan_table_row_end(text, offset)
      expect_more = false
    }
  }
  if offset != n || cells.length() == 0 {
    return None
  }
  Some({ cells, })
}

///|
/// The alignment a delimiter cell asks for.
fn alignment_of(cell : StringView) -> Align {
  if cell.is_empty() {
    return Default
  }
  let left = cell.at(0).to_int() == 0x3A
  let right = cell.at(cell.length() - 1).to_int() == 0x3A
  if left && right {
    Center
  } else if left {
    Left
  } else if right {
    Right
  } else {
    Default
  }
}

///|
/// Where the last line of a paragraph starts, and the paragraph without it.
fn split_last_line(text : String) -> (Int32, String) {
  let mut line_start = 0
  let n = text.length()
  let mut i = 0
  while i < n {
    if cu(text[:], i) == 0x0A && i + 1 < n {
      line_start = i + 1
    }
    i = i + 1
  }
  let prefix = if line_start == 0 {
    ""
  } else {
    text.clamped_view(start=0, end=line_start).to_owned()
  }
  (line_start, prefix)
}

///|
/// Turn the open paragraph into a table when the line is a delimiter row of the
/// same width as the paragraph's last line. Returns the table, or `-1` when the
/// line is not one.
fn BlockParser::open_table(self : BlockParser, index : Int32) -> Int32 {
  let node = self.tree.nodes[index]
  if node.kind != Paragraph {
    return -1
  }
  if node.visited {
    return -1
  }
  let delimiter = match parse_table_row(self.line[:], self.first_nonspace) {
    Some(row) => row
    None => return -1
  }
  let content = node.content.to_string()
  let (line_start, prefix) = split_last_line(content)
  let header = match parse_table_row(content[:], line_start) {
    Some(row) => row
    None => return -1
  }
  if header.cells.length() != delimiter.cells.length() {
    node.visited = true
    return -1
  }
  let align : Array[Align] = []
  for cell in delimiter.cells {
    align.push(alignment_of(cell[:]))
  }
  if !is_blank(prefix[:]) {
    let paragraph = self.tree.make(Paragraph, node.start_line)
    self.tree.nodes[paragraph].content.write_string(prefix)
    self.tree.insert_before(index, paragraph)
  }
  node.kind = Table
  node.align = align
  node.content.reset()
  let header_row = self.tree.make(TableRow, node.start_line)
  self.tree.nodes[header_row].open = false
  self.tree.nodes[header_row].header = true
  self.tree.append_child(index, header_row)
  for cell in header.cells {
    let cell_node = self.tree.make(TableCell, node.start_line)
    self.tree.nodes[cell_node].open = false
    self.tree.nodes[cell_node].content.write_string(cell)
    self.tree.append_child(header_row, cell_node)
  }
  self.advance_offset(self.line.length() - 1 - self.offset, false)
  index
}

///|
/// Add a body row to the table at `index`. Returns the row, or `-1` when the
/// line is not a row.
fn BlockParser::open_table_row(self : BlockParser, index : Int32) -> Int32 {
  if self.blank {
    return -1
  }
  match parse_table_row(self.line[:], self.first_nonspace) {
    None => -1
    Some(row) => {
      let columns = self.tree.nodes[index].align.length()
      let row_node = self.tree.make(TableRow, self.line_number - 1)
      self.tree.nodes[row_node].open = false
      self.tree.append_child(index, row_node)
      let mut i = 0
      while i < columns {
        let cell_node = self.tree.make(TableCell, self.line_number - 1)
        self.tree.nodes[cell_node].open = false
        if i < row.cells.length() {
          self.tree.nodes[cell_node].content.write_string(row.cells[i])
        }
        self.tree.append_child(row_node, cell_node)
        i = i + 1
      }
      self.advance_offset(self.line.length() - 1 - self.offset, false)
      row_node
    }
  }
}