#import "uml.typ"

#let parse-args(data) = {
  let args = data.split(",").map(a => a.trim()).filter(a => a.len() != 0)
  let res
  if ":" in data {
    res = (:)
  } else {
    res = ()
  }

  for arg in args {
    let parts = arg.split(":")
    let arg-name = none
    let arg-type
    if parts.len() == 1 {
      if type(res) == dictionary {
        panic("Cannot mix named and unnamed arguments")
      }
      arg-type = parts.first().trim()
      res.push(arg-type)
    } else {
      arg-name = parts.first().trim()
      arg-type = parts.slice(1).join(":").trim()
      res.insert(arg-name, arg-type)
    }
  }

  return res
}

#let parse(data) = {
  let elements = ()

  if type(data) == raw or type(data) == content {
    data = data.text
  }

  let classes = data.matches(regex("(?ms)(abstract class|class|interface) (.*?)\s*\{(.*?)\}"))

  for class in classes {
    let family-name = class.captures.first()
    let family = (
      class: none,
      "abstract class": uml.abstract,
      "interface": uml.interface,
      "enum": uml.enum,
    ).at(family-name)
    
    let name = class.captures.at(1)
    let class-data = class.captures.last().trim()
    
    let members = class-data.matches(regex("(?m)^\s*(.*?)\s*(:\s*([^:]*?))?$")).filter(m => m.text.len() != 0)
    let attributes = ()
    let methods = ()
    for member in members {
      let member-name = member.captures.first()
      let val-type = member.captures.last()
      let visibility = none

      if member-name.starts-with(regex("\+|-|~|#")) {
        let vis = member-name.first()
        member-name = member-name.slice(1)
        visibility = (
          "+": uml.public,
          "-": uml.private,
          "~": uml.package,
          "#": uml.protected
        ).at(vis)
      }

      if member-name.ends-with(")") {
        let func = member-name.match(regex("^(.*?)\((.*)\)$"))
        member-name = func.captures.first()
        let args = parse-args(func.captures.last())
        methods += uml.method(member-name, visibility, val-type, args)
      } else {
        attributes += uml.attr(member-name, visibility, val-type)
      }
    }

    elements += uml.class(name, family, attributes + methods)
  }

  let defaults = (:)
  let params = data.matches(regex("(?m)^\s*@\s*(.*?)\s*=\s*(.*)\s*$"))
  for param in params {
    let key = param.captures.first()
    let value = eval(param.captures.last(), scope: (uml: uml))
    defaults.insert(key, value)
  }

  return uml.diagram(elements, defaults)
}