// The progress line of a bundle that is unpacking itself.
//
// Everything here is a plain function of numbers, so that what the bar says
// can be checked without a console: what is left is the writing, which is one
// call in the launcher.

///|
/// The fraction of the work that is done, in whole percent.
///
/// A total of zero is "done": there is nothing left to do, and a bar that
/// says 0% would sit there for ever.
fn percent_done(done : Int64, total : Int64) -> Int32 {
  if total <= 0L {
    return 100
  }
  if done <= 0L {
    return 0
  }
  let p = done * 100L / total
  if p > 100L {
    return 100
  }
  p.to_int()
}

///|
/// A count of bytes as megabytes with one decimal, e.g. "44.2".
fn megabytes_text(bytes : Int64) -> String {
  if bytes <= 0L {
    return "0.0"
  }
  // Tenths of a megabyte, rounded: half a megabyte is 524288 bytes.
  let tenths = (bytes * 10L + 524288L) / 1048576L
  (tenths / 10L).to_string() + "." + (tenths % 10L).to_string()
}

///|
/// The bar itself: `width` cells, `#` for the part that is done and `-` for
/// the part that is not.
fn progress_bar(percent : Int32, width : Int32) -> String {
  if width <= 0 {
    return ""
  }
  let p = if percent < 0 { 0 } else if percent > 100 { 100 } else { percent }
  let filled = (p.to_int64() * width.to_int64() / 100L).to_int()
  let mut out = ""
  let mut i = 0
  while i < width {
    out = out + (if i < filled { "#" } else { "-" })
    i = i + 1
  }
  out
}

///|
/// Would this text survive a console that does not read UTF-8?
///
/// The file names of a package are whatever the packer put in it, and a code
/// page that cannot show them would turn the bar into noise. A name that is
/// not plain ASCII is therefore left out of the line rather than guessed at.
fn is_ascii(text : String) -> Bool {
  let mut i = 0
  while i < text.length() {
    if text[i].to_int() > 127 {
      return false
    }
    i = i + 1
  }
  true
}

///|
/// The body of one progress line, without the carriage return.
fn progress_line(
  done_files : Int32,
  total_files : Int32,
  done_bytes : Int64,
  total_bytes : Int64,
  current : String,
  width : Int32,
) -> String {
  let percent = percent_done(done_bytes, total_bytes)
  let name = if current != "" && is_ascii(current) {
    "  " + current
  } else {
    ""
  }
  "[" +
  progress_bar(percent, width) +
  "] " +
  percent.to_string() +
  "%  " +
  done_files.to_string() +
  "/" +
  total_files.to_string() +
  " files  " +
  megabytes_text(done_bytes) +
  "/" +
  megabytes_text(total_bytes) +
  " MB" +
  name
}

///|
/// `body`, cut or padded to exactly `width` characters.
///
/// The line is rewritten in place with a carriage return, so it has to be the
/// same length every time: a shorter line would leave the tail of the one
/// before it on the screen.
fn fit_line(body : String, width : Int32) -> String {
  if width <= 0 {
    return ""
  }
  let w = width
  if body.length() > w {
    return body[0:w].to_owned()
  }
  let mut out = body
  while out.length() < w {
    out = out + " "
  }
  out
}

///|
/// The number of bytes the package carries, which is what the bar counts.
fn total_bytes_of(entries : Array[Entry]) -> Int64 {
  let mut total = 0L
  for e in entries {
    total = total + e.size
  }
  total
}