// Syslog RFC 5424 and GELF format support

// RFC 5424 style: version timestamp hostname appname procid msgid [sd] msg
// Simplified: timestamp appname msg [fields]

///|
pub fn format_syslog(record : LogRecord, app_name : String) -> String {
  let pri = syslog_priority(record.level)
  let ts = record.timestamp.to_string()
  let fields_str = build_syslog_sd(record.fields, 0, "")
  "<" +
  pri.to_string() +
  ">" +
  "1 " +
  ts +
  " " +
  sanitize_syslog_token(app_name, "moonlogtrace") +
  " - - - " +
  fields_str +
  " " +
  sanitize_syslog_message(record.message)
}

///|
fn syslog_priority(level : Level) -> Int {
  // Facility 1 (user) + severity
  let facility = 1
  let severity = match level {
    Level::TRACE => 7
    Level::DEBUG => 7
    Level::INFO => 6
    Level::WARN => 4
    Level::ERROR => 3
  }
  facility * 8 + severity
}

///|
fn build_syslog_sd(
  fields : Array[(String, String)],
  idx : Int,
  result : String,
) -> String {
  if idx >= fields.length() {
    if result == "" {
      "-"
    } else {
      "[" + result + "]"
    }
  } else {
    let (k, v) = fields[idx]
    let pair = sanitize_syslog_token(k, "field") +
      "=\"" +
      escape_syslog_param(v) +
      "\""
    let sep = if idx > 0 { " " } else { "" }
    build_syslog_sd(fields, idx + 1, result + sep + pair)
  }
}

///|
fn sanitize_syslog_token(text : String, fallback : String) -> String {
  let chars = text.to_array()
  if chars.length() == 0 {
    fallback
  } else {
    sanitize_syslog_token_chars(chars, 0, "")
  }
}

///|
fn sanitize_syslog_token_chars(
  chars : Array[Char],
  idx : Int,
  result : String,
) -> String {
  if idx >= chars.length() {
    result
  } else {
    let ch = chars[idx]
    let safe = if ch.is_whitespace() ||
      ch.is_control() ||
      ch == '=' ||
      ch == ']' ||
      ch == '"' {
      "_"
    } else {
      ch.to_string()
    }
    sanitize_syslog_token_chars(chars, idx + 1, result + safe)
  }
}

///|
fn escape_syslog_param(text : String) -> String {
  escape_syslog_param_chars(text.to_array(), 0, "")
}

///|
fn escape_syslog_param_chars(
  chars : Array[Char],
  idx : Int,
  result : String,
) -> String {
  if idx >= chars.length() {
    result
  } else {
    let ch = chars[idx]
    let escaped = if ch == '\\' {
      "\\\\"
    } else if ch == '"' {
      "\\\""
    } else if ch == ']' {
      "\\]"
    } else if ch == '\n' {
      "\\n"
    } else if ch == '\r' {
      "\\r"
    } else if ch.is_control() {
      "_"
    } else {
      ch.to_string()
    }
    escape_syslog_param_chars(chars, idx + 1, result + escaped)
  }
}

///|
fn sanitize_syslog_message(text : String) -> String {
  escape_syslog_message_chars(text.to_array(), 0, "")
}

///|
fn escape_syslog_message_chars(
  chars : Array[Char],
  idx : Int,
  result : String,
) -> String {
  if idx >= chars.length() {
    result
  } else {
    let ch = chars[idx]
    let safe = if ch == '\n' {
      "\\n"
    } else if ch == '\r' {
      "\\r"
    } else if ch.is_control() && ch != '\t' {
      "_"
    } else {
      ch.to_string()
    }
    escape_syslog_message_chars(chars, idx + 1, result + safe)
  }
}

// GELF (Graylog Extended Log Format) — JSON-based

///|
pub fn format_gelf(record : LogRecord, host : String) -> String {
  let result = "{\"version\":\"1.1\",\"host\":" +
    json_quote(host) +
    ",\"short_message\":" +
    json_quote(record.message) +
    ",\"timestamp\":" +
    record.timestamp.to_string() +
    ",\"level\":" +
    gelf_level(record.level).to_string()
  format_gelf_fields(result, record.fields, 0)
}

///|
fn gelf_level(level : Level) -> Int {
  match level {
    Level::TRACE => 7
    Level::DEBUG => 7
    Level::INFO => 6
    Level::WARN => 4
    Level::ERROR => 3
  }
}

///|
fn format_gelf_fields(
  result : String,
  fields : Array[(String, String)],
  idx : Int,
) -> String {
  if idx >= fields.length() {
    result + "}"
  } else {
    let (k, v) = fields[idx]
    let kv = "," + json_quote("_" + k) + ":" + json_quote(v)
    format_gelf_fields(result + kv, fields, idx + 1)
  }
}