///|
fn table_cell_halign(s : String) -> String? {
  match s {
    "<" => Some("left")
    ">" => Some("right")
    "^" => Some("center")
    _ => None
  }
}

///|
fn table_cell_valign(s : String) -> String? {
  match s {
    "<" => Some("top")
    ">" => Some("bottom")
    "^" => Some("middle")
    _ => None
  }
}

///|
fn table_cell_style(s : String) -> String? {
  match s {
    "d" => Some("none")
    "s" => Some("strong")
    "e" => Some("emphasis")
    "m" => Some("monospaced")
    "h" => Some("header")
    "l" => Some("literal")
    "a" => Some("asciidoc")
    _ => None
  }
}

///|
/// Parses a table (Ruby `Parser.parse_table`).
fn parse_table(
  table_reader : Reader,
  parent : Node,
  attributes : Attributes,
) -> Node {
  let table = Node::new_table(parent, attributes)
  let td = table.table_data()
  let mut explicit_colspecs = false
  match attributes.str("cols") {
    Some(cols) if attributes.contains("cols") => {
      let colspecs = parse_colspecs(cols)
      if !colspecs.is_empty() {
        table.create_columns(colspecs)
        explicit_colspecs = true
      }
    }
    _ => ()
  }
  let skipped = table_reader.skip_blank_lines().unwrap_or(0)
  let mut implicit_header = false
  if attributes.truthy("header-option") {
    td.has_header_option = ExplicitHeader
  } else if skipped == 0 && !attributes.truthy("noheader-option") {
    td.has_header_option = ImplicitHeader
    implicit_header = true
  }
  let parser_ctx = TableParserContext::new(table_reader, table, attributes)
  let format = parser_ctx.format
  let mut loop_idx = -1
  let mut implicit_header_boundary : Int? = None
  while table_reader.read_line() is Some(line0) {
    let mut line : String? = Some(line0)
    loop_idx += 1
    let beyond_first = loop_idx > 0
    if beyond_first && line0 == "" {
      line = None
      match implicit_header_boundary {
        Some(b) => implicit_header_boundary = Some(b + 1)
        None => ()
      }
    } else if format == "psv" {
      let l = line0
      if l.has_prefix(parser_ctx.delimiter) {
        line = Some(@rb.from(l, parser_ctx.delimiter.length()))
        parser_ctx.close_open_cell()
        if implicit_header_boundary is Some(_) {
          implicit_header_boundary = None
        }
      } else {
        let (next_cellspec, rest) = parse_cellspec(
          l,
          start=true,
          delimiter=parser_ctx.delimiter,
        )
        line = Some(rest)
        match next_cellspec {
          Some(spec) => {
            parser_ctx.close_open_cell(next_cellspec=spec)
            if implicit_header_boundary is Some(_) {
              implicit_header_boundary = None
            }
          }
          None =>
            if implicit_header_boundary == Some(loop_idx) {
              td.has_header_option = UnsetHeader
              implicit_header = false
              implicit_header_boundary = None
            }
        }
      }
    }
    if !beyond_first {
      table_reader.mark() |> ignore
      if implicit_header {
        if table_reader.has_more_lines() && table_reader.peek_line() == Some("") {
          implicit_header_boundary = Some(1)
        } else {
          td.has_header_option = UnsetHeader
          implicit_header = false
        }
      }
    }
    while true {
      let m = match line {
        Some(l) => parser_ctx.delimiter_rx.find(l)
        None => None
      }
      match m {
        Some(m) => {
          let pre_match = m.pre_match()
          let post_match = m.post_match()
          match format {
            "csv" => {
              if parser_ctx.buffer_has_unclosed_quotes(append=pre_match) {
                parser_ctx.skip_past_delimiter(pre_match)
                if post_match == "" {
                  break
                }
                line = Some(post_match)
                continue
              }
              parser_ctx.buffer = parser_ctx.buffer + pre_match
            }
            "dsv" => {
              if pre_match.has_suffix("\\") {
                parser_ctx.skip_past_escaped_delimiter(pre_match)
                if post_match == "" {
                  parser_ctx.buffer = parser_ctx.buffer + "\n"
                  parser_ctx.cell_open = true
                  break
                }
                line = Some(post_match)
                continue
              }
              parser_ctx.buffer = parser_ctx.buffer + pre_match
            }
            _ => {
              if pre_match.has_suffix("\\") {
                parser_ctx.skip_past_escaped_delimiter(pre_match)
                if post_match == "" {
                  parser_ctx.buffer = parser_ctx.buffer + "\n"
                  parser_ctx.cell_open = true
                  break
                }
                line = Some(post_match)
                continue
              }
              let (next_cellspec, cell_text) = parse_cellspec(pre_match)
              parser_ctx.push_cellspec(next_cellspec)
              parser_ctx.buffer = parser_ctx.buffer + cell_text
            }
          }
          line = if post_match == "" { None } else { Some(post_match) }
          parser_ctx.close_cell()
        }
        None => {
          parser_ctx.buffer = "\{parser_ctx.buffer}\{line.unwrap_or("")}\n"
          match format {
            "csv" =>
              if parser_ctx.buffer_has_unclosed_quotes() {
                if implicit_header_boundary is Some(_) && loop_idx == 0 {
                  td.has_header_option = UnsetHeader
                  implicit_header = false
                  implicit_header_boundary = None
                }
                parser_ctx.cell_open = true
              } else {
                parser_ctx.close_cell(eol=true)
              }
            "dsv" => parser_ctx.close_cell(eol=true)
            _ => parser_ctx.cell_open = true
          }
          break
        }
      }
    }
    if parser_ctx.cell_open {
      if !table_reader.has_more_lines() {
        parser_ctx.close_cell(eol=true)
      }
    } else if table_reader.skip_blank_lines() is None {
      break
    }
  }
  parser_ctx.close_table()
  let colcount = match table.attributes.get("colcount") {
    Some(v) if v.truthy() => v.to_i()
    _ => {
      table.attributes.set("colcount", Int(td.columns.length()))
      td.columns.length()
    }
  }
  if !(colcount == 0 || explicit_colspecs) {
    table.assign_column_widths()
  }
  if implicit_header {
    td.has_header_option = ExplicitHeader
  }
  table.partition_header_footer(attributes)
  table
}

///|
/// Parses the cols attribute (Ruby `parse_colspecs`).
fn parse_colspecs(records0 : String) -> Array[ColSpec] {
  let records = if records0.contains(" ") {
    @rb.delete_chars(records0, " ")
  } else {
    records0
  }
  if records == @rb.to_i(records).to_string() {
    let n = @rb.to_i(records)
    return Array::makei(n, _ => {
      width: 1,
      halign: None,
      valign: None,
      style: None,
    })
  }
  let specs = []
  let parts = if records.contains(",") {
    @rb.split(records, ",", limit=-1)
  } else {
    @rb.split(records, ";", limit=-1)
  }
  for record in parts {
    if record == "" {
      specs.push({ width: 1, halign: None, valign: None, style: None, })
    } else {
      match column_spec_rx.find(record) {
        Some(m) => {
          let spec : ColSpec = {
            width: 1,
            halign: None,
            valign: None,
            style: None,
          }
          match m.group(2) {
            Some(g2) => {
              let parts2 = @rb.split(g2, ".")
              match parts2.get(0) {
                Some(colspec) if colspec != "" =>
                  spec.halign = table_cell_halign(colspec)
                _ => ()
              }
              match parts2.get(1) {
                Some(rowspec) if rowspec != "" =>
                  spec.valign = table_cell_valign(rowspec)
                _ => ()
              }
            }
            None => ()
          }
          match m.group(3) {
            Some(width) =>
              spec.width = if width == "~" { -1 } else { @rb.to_i(width) }
            None => spec.width = 1
          }
          match m.group(4) {
            Some(s) => spec.style = table_cell_style(s)
            None => ()
          }
          match m.group(1) {
            Some(repeat) =>
              for _ in 0..<@rb.to_i(repeat) {
                specs.push({ ..spec, })
              }
            None => specs.push(spec)
          }
        }
        None => ()
      }
    }
  }
  specs
}

///|
/// Parses a cell spec (Ruby `parse_cellspec`): returns (spec or None, rest).
fn parse_cellspec(
  line : String,
  start? : Bool = false,
  delimiter? : String = "",
) -> (Attributes?, String) {
  let mut m : @regex.MatchData? = None
  let mut rest = ""
  if start {
    if !line.contains(delimiter) {
      return (None, line)
    }
    let (spec_part, _, r) = @rb.partition(line, delimiter)
    rest = r
    match cell_spec_start_rx.find(spec_part) {
      Some(mm) => {
        if mm.matched() == "" {
          return (Some(Attributes::new()), rest)
        }
        m = Some(mm)
      }
      None => return (None, line)
    }
  } else {
    match cell_spec_end_rx.find(line) {
      Some(mm) => {
        if @rb.lstrip(mm.matched()) == "" {
          return (Some(Attributes::new()), @rb.rstrip(line))
        }
        rest = mm.pre_match()
        m = Some(mm)
      }
      None => return (Some(Attributes::new()), line)
    }
  }
  let m = m.unwrap()
  let spec = Attributes::new()
  match m.group(1) {
    Some(g1) => {
      let parts = @rb.split(g1, ".")
      let colspec = match parts.get(0) {
        Some(c) if c != "" => @rb.to_i(c)
        _ => 1
      }
      let rowspec = match parts.get(1) {
        Some(r) if r != "" => @rb.to_i(r)
        _ => 1
      }
      match m.group(2) {
        Some("+") => {
          if colspec != 1 {
            spec.set("colspan", Int(colspec))
          }
          if rowspec != 1 {
            spec.set("rowspan", Int(rowspec))
          }
        }
        Some("*") => if colspec != 1 { spec.set("repeatcol", Int(colspec)) }
        _ => ()
      }
    }
    None => ()
  }
  match m.group(3) {
    Some(g3) => {
      let parts = @rb.split(g3, ".")
      match parts.get(0) {
        Some(c) if c != "" =>
          match table_cell_halign(c) {
            Some(h) => spec.set_str("halign", h)
            None => ()
          }
        _ => ()
      }
      match parts.get(1) {
        Some(r) if r != "" =>
          match table_cell_valign(r) {
            Some(v) => spec.set_str("valign", v)
            None => ()
          }
        _ => ()
      }
    }
    None => ()
  }
  match m.group(4) {
    Some(g4) =>
      match table_cell_style(g4) {
        Some(s) => spec.set_str("style", s)
        None => ()
      }
    None => ()
  }
  (Some(spec), rest)
}