///|
/// Invalid input, unsupported format or unsafe operation. Messages include context.
pub(all) suberror WellError {
  WellError(String)
}

///|
pub(all) struct Curve {
  api_code : String
  name : String
  unit : String
  description : String
}

///|
pub(all) struct Header {
  name : String
  unit : String
  value : String
  description : String
}

///|
/// An uninterpreted customer section. Title includes the leading tilde.
pub(all) struct LasSection {
  title : String
  lines : Array[String]
}

///|
/// A rectangular table; column zero is non-null strictly monotonic depth.
/// Arrays are owned snapshots at construction. Call validate after external mutation.
pub struct Log {
  curves : Array[Curve]
  rows : Array[Array[Double?]]
  headers : Array[Header]
  parameters : Array[Header]
  other : Array[String]
  version_headers : Array[Header]
  extra_sections : Array[LasSection]
  null_value : Double
}

///|
fn fail(message : String) -> Unit raise WellError {
  raise WellError(message)
}

///|
fn finite(value : Double) -> Bool {
  !value.is_nan() && !value.is_inf()
}

///|
fn canonical(text : String) -> String {
  text.trim().to_owned().to_upper()
}

///|
fn safe_text(text : String) -> Bool {
  !text.contains("\n") && !text.contains("\r")
}

///|
/// LAS 2.0 section 5.2: printable ASCII except internal spaces, dots and colons.
fn valid_name(name : String) -> Bool {
  if name.length() == 0 || name.has_prefix("#") || name.has_prefix("~") {
    return false
  }
  for c in name.iter() {
    let n = c.to_int()
    if n < 33 || n > 126 || c == '.' || c == ':' {
      return false
    }
  }
  true
}

///|
pub fn make_log(
  curves : Array[Curve],
  rows : Array[Array[Double?]],
  headers? : Array[Header] = [],
  parameters? : Array[Header] = [],
  other? : Array[String] = [],
  null_value? : Double = -999.25,
  version_headers? : Array[Header] = [],
  extra_sections? : Array[LasSection] = [],
) -> Log raise WellError {
  let log = Log::{
    curves: curves.copy(),
    rows: rows.map(row => row.copy()),
    headers: headers.copy(),
    parameters: parameters.copy(),
    other: other.copy(),
    version_headers: version_headers.copy(),
    extra_sections: extra_sections.map(s => {
      title: s.title,
      lines: s.lines.copy(),
    }),
    null_value,
  }
  validate(log)
  log
}

///|
pub fn validate(log : Log) -> Unit raise WellError {
  if log.curves.length() == 0 {
    fail("at least one depth curve is required")
  }
  if log.curves.length() > 512 {
    fail("curve limit is 512")
  }
  if log.rows.length() > 1000000 {
    fail("row limit is 1000000")
  }
  if log.rows.length().to_double() * log.curves.length().to_double() >
    10000000.0 {
    fail("log cell budget is 10000000")
  }
  if !finite(log.null_value) {
    fail("null sentinel must be finite")
  }
  for i in 0.. c.to_int() < 32 || c.to_int() > 126) {
      fail("unsafe curve metadata: " + c.name)
    }
    for j in 0.. v
      None => {
        fail("missing depth at row " + r.to_string())
        0.0
      }
    }
    for value in row {
      if value is Some(v) && !finite(v) {
        fail("nonfinite sample at row " + r.to_string())
      }
    }
    if r > 0 {
      if depth == previous {
        fail("duplicate depth at row " + r.to_string())
      }
      let sign = if depth > previous { 1 } else { -1 }
      if direction != 0 && sign != direction {
        fail("depth reversal at row " + r.to_string())
      }
      direction = sign
    }
    previous = depth
  }
  let all_headers = log.headers.copy()
  for h in log.parameters {
    all_headers.push(h)
  }
  for h in log.version_headers {
    let key = canonical(h.name)
    if key == "VERS" || key == "WRAP" {
      fail("version extensions cannot override VERS or WRAP")
    }
    all_headers.push(h)
  }
  for h in all_headers {
    if !valid_name(h.name) ||
      !safe_text(h.value) ||
      !safe_text(h.unit) ||
      h.unit.contains(" ") ||
      h.unit.contains("\t") ||
      h.unit.contains(":") ||
      !safe_text(h.description) ||
      h.description.contains(":") {
      fail("unsafe header: " + h.name)
    }
  }
  for collection in [log.headers, log.parameters, log.version_headers] {
    for i in 0.. Int raise WellError {
  for i in 0.. Array[Double] raise WellError {
  validate(log)
  log.rows.map(row => row[0].unwrap())
}