///|
pub(all) struct SourceSummary {
  file_name : String
  lines : Int
  characters : Int
  blank_lines : Int
  comment_lines : Int
  code_lines : Int
  max_line_length : Int
} derive(Eq, Debug)

///|
fn is_comment_line(line : String) -> Bool {
  let value = line.trim()
  value.has_prefix("//") ||
  value.has_prefix("/*") ||
  value.has_prefix("*") ||
  value.has_prefix("*/")
}

///|
pub fn SourceFile::summary(self : SourceFile) -> SourceSummary {
  let lines = self.lines()
  let mut blank = 0
  let mut comments = 0
  let mut max_length = 0
  for line in lines {
    let length = line.to_array().length()
    if length > max_length {
      max_length = length
    }
    if line.trim().is_empty() {
      blank += 1
    } else if is_comment_line(line) {
      comments += 1
    }
  }
  {
    file_name: self.name(),
    lines: lines.length(),
    characters: self.character_count(),
    blank_lines: blank,
    comment_lines: comments,
    code_lines: lines.length() - blank - comments,
    max_line_length: max_length,
  }
}

///|
pub fn SourceFile::normalized(self : SourceFile) -> SourceFile {
  let builder = StringBuilder()
  let chars = self.text.to_array()
  let mut index = 0
  while index < chars.length() {
    if chars[index] == '\r' {
      if index + 1 < chars.length() && chars[index + 1] == '\n' {
        index += 1
      }
      builder.write_char('\n')
    } else {
      builder.write_char(chars[index])
    }
    index += 1
  }
  SourceFile::new(self.name(), builder.to_string())
}

///|
pub(all) struct SourceBundle {
  files : Array[SourceFile]
}

///|
pub fn SourceBundle::new() -> SourceBundle {
  { files: [] }
}

///|
pub fn SourceBundle::from_files(files : Array[SourceFile]) -> SourceBundle {
  let bundle = SourceBundle::new()
  for file in files {
    bundle.add(file)
  }
  bundle
}

///|
pub fn SourceBundle::add(self : SourceBundle, file : SourceFile) -> Unit {
  for existing in self.files {
    if existing.name() == file.name() {
      return
    }
  }
  self.files.push(file)
}

///|
pub fn SourceBundle::len(self : SourceBundle) -> Int {
  self.files.length()
}

///|
pub fn SourceBundle::is_empty(self : SourceBundle) -> Bool {
  self.files.length() == 0
}

///|
pub fn SourceBundle::get(self : SourceBundle, name : String) -> SourceFile? {
  for file in self.files {
    if file.name() == name {
      return Some(file)
    }
  }
  None
}

///|
pub fn SourceBundle::names(self : SourceBundle) -> Array[String] {
  let result : Array[String] = []
  for file in self.files {
    result.push(file.name())
  }
  result.sort()
  result
}

///|
pub fn SourceBundle::summaries(self : SourceBundle) -> Array[SourceSummary] {
  let result : Array[SourceSummary] = []
  for name in self.names() {
    match self.get(name) {
      Some(file) => result.push(file.summary())
      None => ()
    }
  }
  result
}

///|
pub fn SourceBundle::total_lines(self : SourceBundle) -> Int {
  let mut total = 0
  for file in self.files {
    total += file.line_count()
  }
  total
}

///|
pub fn SourceBundle::total_characters(self : SourceBundle) -> Int {
  let mut total = 0
  for file in self.files {
    total += file.character_count()
  }
  total
}

///|
pub fn SourceBundle::code_lines(self : SourceBundle) -> Int {
  let mut total = 0
  for summary in self.summaries() {
    total += summary.code_lines
  }
  total
}

///|
pub fn SourceBundle::largest_file(self : SourceBundle) -> SourceSummary? {
  let summaries = self.summaries()
  if summaries.length() == 0 {
    return None
  }
  let mut largest = summaries[0]
  for summary in summaries {
    if summary.characters > largest.characters {
      largest = summary
    }
  }
  Some(largest)
}

///|
pub fn SourceBundle::summary(self : SourceBundle) -> String {
  "files=" +
  self.len().to_string() +
  ", lines=" +
  self.total_lines().to_string() +
  ", code_lines=" +
  self.code_lines().to_string() +
  ", characters=" +
  self.total_characters().to_string()
}