///|
fn escape_xml(text : String) -> String {
  let sb = StringBuilder::new()
  for c in text {
    match c {
      '&' => sb.write_view("&")
      '<' => sb.write_view("<")
      '>' => sb.write_view(">")
      '"' => sb.write_view(""")
      '\'' => sb.write_view("'")
      _ => sb.write_char(c)
    }
  }
  sb.to_string()
}

///|
fn arrow_marker_defs(color : String) -> String {
  "\n  \n    \n  \n\n"
}

///|
fn to_px_x(x : Int) -> Double {
  x.to_double() * cell_px
}

///|
fn to_px_y(y : Int) -> Double {
  y.to_double() * cell_py
}

///|
fn svg_edge_path(start : Point, end_ : Point, direction : Direction) -> String {
  let x1 = to_px_x(start.x)
  let y1 = to_px_y(start.y)
  let x2 = to_px_x(end_.x)
  let y2 = to_px_y(end_.y)
  if x1 == x2 || y1 == y2 {
    "M \{x1} \{y1} L \{x2} \{y2}"
  } else {
    match direction {
      Direction::TD | Direction::BT => {
        let mid_y = (y1 + y2) / 2.0
        "M \{x1} \{y1} C \{x1} \{mid_y}, \{x2} \{mid_y}, \{x2} \{y2}"
      }
      Direction::LR | Direction::RL => {
        let mid_x = (x1 + x2) / 2.0
        "M \{x1} \{y1} C \{mid_x} \{y1}, \{mid_x} \{y2}, \{x2} \{y2}"
      }
    }
  }
}

///|
fn svg_open(
  width_px : Double,
  height_px : Double,
  transparent : Bool,
  bg : String,
) -> String {
  let sb = StringBuilder::new()
  sb.write_view(
    "\n",
  )
  if not(transparent) {
    sb.write_view(
      "\n",
    )
  }
  sb.to_string()
}

///|
fn svg_state_circle(
  cx : Double,
  cy : Double,
  r : Double,
  accent : String,
  filled : Bool,
) -> String {
  let sb = StringBuilder::new()
  if filled {
    sb.write_view(
      "\n",
    )
  } else {
    sb.write_view(
      "\n",
    )
    sb.write_view(
      "\n",
    )
  }
  sb.to_string()
}

///|
fn render_graph_svg(diagram : GraphDiagram, options : SvgOptions) -> String {
  let ascii_opts = default_ascii_options()
  let layout = layout_graph(diagram, ascii_opts)
  let colors = build_colors(options)
  let font = resolve_font(options)
  let transparent = resolve_transparent(options)
  let pad = 20.0
  let width_px = to_px_x(layout.width) + pad * 2
  let height_px = to_px_y(layout.height) + pad * 2
  let font_size = cell_py * 0.8
  let sb = StringBuilder::new()
  sb.write_view(svg_open(width_px, height_px, transparent, colors.bg))
  sb.write_view(arrow_marker_defs(colors.accent))
  sb.write_view("\n")
  sb.write_view("\n")
  for edge in layout.edges {
    let path_d = svg_edge_path(edge.start, edge.end, layout.direction)
    sb.write_view(
      "\n",
    )
    match edge.edge.label {
      Some(label) => {
        let lx = (to_px_x(edge.start.x) + to_px_x(edge.end.x)) / 2.0
        let ly = (to_px_y(edge.start.y) + to_px_y(edge.end.y)) / 2.0
        let text_w = label.length().to_double() * font_size * 0.55
        let text_h = font_size * 1.2
        sb.write_view(
          "\n",
        )
        sb.write_view(
          "\{escape_xml(label)}\n",
        )
      }
      None => ()
    }
  }
  sb.write_view("\n")
  sb.write_view("\n")
  for pnode in layout.nodes {
    let b = pnode.box
    let x = to_px_x(b.x)
    let y = to_px_y(b.y)
    let w = to_px_x(b.width)
    let h = to_px_y(b.height)
    sb.write_view("\n")
    match pnode.node.shape {
      NodeShape::StateStart => {
        let cx = x + w / 2.0
        let cy = y + h / 2.0
        let r = (if w < h { w } else { h }) / 2.2
        sb.write_view(svg_state_circle(cx, cy, r, colors.accent, true))
      }
      NodeShape::StateEnd => {
        let cx = x + w / 2.0
        let cy = y + h / 2.0
        let r = (if w < h { w } else { h }) / 2.2
        sb.write_view(svg_state_circle(cx, cy, r, colors.accent, false))
      }
      _ => {
        let rx = match pnode.node.shape {
          NodeShape::Rounded => 12
          NodeShape::Circle => (if w < h { w } else { h }).to_int() / 2
          _ => 4
        }
        sb.write_view(
          "\n",
        )
        let mut ty = y + ascii_opts.padding_y.to_double() * cell_py + font_size
        let tx = x + ascii_opts.padding_x.to_double() * cell_px
        let mut section_index = 0
        for section in pnode.node.sections {
          for line in section {
            sb.write_view(
              "\{escape_xml(line)}\n",
            )
            ty += cell_py
          }
          if section_index < pnode.node.sections.length() - 1 {
            sb.write_view(
              "\n",
            )
            ty += cell_py * 0.2
          }
          section_index += 1
        }
      }
    }
    sb.write_view("\n")
  }
  sb.write_view("\n")
  sb.write_view("\n")
  sb.write_view("")
  sb.to_string()
}

///|
fn render_sequence_svg(
  diagram : SequenceDiagram,
  options : SvgOptions,
) -> String {
  let ascii_opts = default_ascii_options()
  let layout = layout_sequence(diagram, ascii_opts)
  let colors = build_colors(options)
  let font = resolve_font(options)
  let transparent = resolve_transparent(options)
  let pad = 20.0
  let width_px = to_px_x(layout.width) + pad * 2
  let height_px = to_px_y(layout.height) + pad * 2
  let font_size = cell_py * 0.8
  let sb = StringBuilder::new()
  sb.write_view(svg_open(width_px, height_px, transparent, colors.bg))
  sb.write_view(arrow_marker_defs(colors.accent))
  sb.write_view("\n")
  sb.write_view("\n")
  for actor in layout.actors {
    let b = actor.box
    let cx = to_px_x(b.x) + to_px_x(b.width) / 2.0
    let y_start = to_px_y(b.y) + to_px_y(b.height)
    let y_end = to_px_y(layout.height)
    sb.write_view(
      "\n",
    )
  }
  sb.write_view("\n")
  sb.write_view("\n")
  for actor in layout.actors {
    let b = actor.box
    let x = to_px_x(b.x)
    let y = to_px_y(b.y)
    let w = to_px_x(b.width)
    let h = to_px_y(b.height)
    sb.write_view(
      "\n",
    )
    let tx = x + w / 2.0
    let ty = y + h / 2.0
    sb.write_view(
      "\{escape_xml(actor.actor.label)}\n",
    )
  }
  sb.write_view("\n")
  sb.write_view("\n")
  for msg in layout.messages {
    let x1 = to_px_x(msg.start.x)
    let y1 = to_px_y(msg.start.y)
    let x2 = to_px_x(msg.end.x)
    let y2 = to_px_y(msg.end.y)
    let dash = if msg.message.dashed { " stroke-dasharray=\"5,3\"" } else { "" }
    sb.write_view(
      "\n",
    )
    if msg.message.text.length() > 0 {
      let tx = (x1 + x2) / 2.0
      let ty = y1 - font_size * 0.5
      let text_w = msg.message.text.length().to_double() * font_size * 0.55
      sb.write_view(
        "\n",
      )
      sb.write_view(
        "\{escape_xml(msg.message.text)}\n",
      )
    }
  }
  sb.write_view("\n")
  sb.write_view("\n")
  sb.write_view("")
  sb.to_string()
}