///|
pub fn render_calendar(calendar : Calendar) -> String {
  let b = StringBuilder::new()
  b.write_string("BEGIN:VCALENDAR\r\n")
  for prop in calendar.properties {
    b.write_string(fold_line(render_property(prop)))
    b.write_string("\r\n")
  }
  for component in calendar.components {
    render_component(component, b)
  }
  b.write_string("END:VCALENDAR\r\n")
  b.to_string()
}

///|
fn render_component(component : Component, b : StringBuilder) -> Unit {
  b.write_string("BEGIN:")
  b.write_string(component.name)
  b.write_string("\r\n")
  for prop in component.properties {
    b.write_string(fold_line(render_property(prop)))
    b.write_string("\r\n")
  }
  for child in component.components {
    render_component(child, b)
  }
  b.write_string("END:")
  b.write_string(component.name)
  b.write_string("\r\n")
}

///|
pub fn render_property(prop : Property) -> String {
  let b = StringBuilder::new()
  b.write_string(prop.name)
  for param in prop.params {
    b.write_string(";")
    b.write_string(param.name)
    b.write_string("=")
    let mut first = true
    for value in param.values {
      if first {
        first = false
      } else {
        b.write_string(",")
      }
      b.write_string(render_param_value(value))
    }
  }
  b.write_string(":")
  if prop.name == "SUMMARY" ||
    prop.name == "DESCRIPTION" ||
    prop.name == "LOCATION" {
    b.write_string(escape_text(unescape_text(prop.value)))
  } else {
    b.write_string(prop.value)
  }
  b.to_string()
}

///|
fn render_param_value(value : String) -> String {
  let mut needs_quotes = false
  let mut i = 0
  while i < value.length() {
    let code = value[i]
    if code == 44 || code == 59 || code == 58 || code == 34 {
      needs_quotes = true
    }
    i += 1
  }
  if !needs_quotes {
    return value
  }
  let b = StringBuilder::new()
  b.write_string("\"")
  i = 0
  while i < value.length() {
    let code = value[i]
    if code == 34 {
      b.write_string("\\\"")
    } else {
      b.write_char(code.unsafe_to_char())
    }
    i += 1
  }
  b.write_string("\"")
  b.to_string()
}

///|
pub fn fold_line(line : String) -> String {
  if line.length() <= 73 {
    return line
  }
  let b = StringBuilder::new()
  let mut start = 0
  while start < line.length() {
    let end = if start + 73 < line.length() {
      start + 73
    } else {
      line.length()
    }
    if start != 0 {
      b.write_string("\r\n ")
    }
    b.write_string(line[start:end].to_owned())
    start = end
  }
  b.to_string()
}

///|
pub fn render_validation(report : ValidationReport) -> String {
  let b = StringBuilder::new()
  b.write_string("# MoonCalGuard validation\n\n")
  b.write_string("- accepted: ")
  b.write_string(report.accepted.to_string())
  b.write_string("\n- findings: ")
  b.write_string(report.findings.length().to_string())
  b.write_string("\n\n")
  for finding in report.findings {
    b.write_string("- [")
    b.write_string(finding.severity.name())
    b.write_string("] ")
    b.write_string(finding.code)
    b.write_string(": ")
    b.write_string(finding.message)
    b.write_string("\n")
  }
  b.to_string()
}