///|
pub(all) enum Format {
  Edf
  Bdf
} derive(Eq, Debug, ToJson)

///|
pub(all) enum Continuity {
  Plain
  Continuous
  Discontinuous
} derive(Eq, Debug, ToJson)

///|
pub suberror EdfError {
  Invalid(String)
  Unsupported(String)
  Limit(String)
} derive(Debug)

///|
pub(all) struct Signal {
  label : String
  transducer : String
  unit : String
  physical_min : Double
  physical_max : Double
  digital_min : Int
  digital_max : Int
  prefilter : String
  samples_per_record : Int
  reserved : String
} derive(Debug, ToJson)

///|
pub struct Header {
  format : Format
  continuity : Continuity
  patient : String
  recording : String
  date : String
  time : String
  reserved : String
  declared_records : Int
  duration : Double
  signals : Array[Signal]
  bytes : Int
  record_bytes : Int
  offsets : Array[Int]
} derive(Debug)

///|
pub(all) struct Tal {
  onset : String
  duration : String?
  texts : Array[String]
} derive(Debug, ToJson)

///|
pub struct Annotation {
  record : Int
  signal : Int
  onset : Double
  duration : Double?
  text : String
} derive(Debug, ToJson)

///|
pub struct Recording {
  header : Header
  data : Bytes
  records : Int
  starts : Array[Double]
  annotations : Array[Annotation]
} derive(Debug)

///|
pub(all) struct Event {
  onset : Double
  duration : Double?
  text : String
} derive(Debug, ToJson, FromJson)

///|
/// samples contains ordinary channels only, in the order supplied to create.
pub(all) struct Record {
  onset : Double
  samples : Array[Array[Int]]
  events : Array[Event]
} derive(Debug)

///|
pub fn Format::width(self : Format) -> Int {
  if self == Edf {
    2
  } else {
    3
  }
}

///|
pub fn Signal::is_annotation(self : Signal) -> Bool {
  self.label == "EDF Annotations" || self.label == "BDF Annotations"
}

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

///|
fn need(b : Bytes, p : Int, n : Int) -> Unit raise EdfError {
  if p < 0 || n < 0 || p > b.length() || n > b.length() - p {
    raise Invalid("truncated input at \{p}")
  }
}

///|
fn part(b : Bytes, p : Int, n : Int) -> Bytes {
  Bytes::makei(n, i => b[p + i])
}

///|
fn text_field(b : Bytes, p : Int, n : Int) -> String raise EdfError {
  need(b, p, n)
  let out = []
  for i = 0; i < n; i = i + 1 {
    let v = b[p + i].to_int()
    if v < 32 || v > 126 {
      raise Invalid("nonprintable ASCII in header at \{p+i}")
    }
    out.push(v.to_char().unwrap())
  }
  String::from_iter(out.iter()).trim().to_owned()
}

///|
fn integer(s : String) -> Int raise EdfError {
  if s.is_empty() {
    raise Invalid("empty integer field")
  }
  for i = 0; i < s.length(); i = i + 1 {
    let c = s[i]
    if (c < '0' || c > '9') && !(i == 0 && (c == '-' || c == '+')) {
      raise Invalid("invalid decimal integer")
    }
  }
  @string.parse_int(s, base=10) catch {
    _ => raise Invalid("integer field out of range")
  }
}

///|
fn number(s : String) -> Double raise EdfError {
  if s.is_empty() {
    raise Invalid("empty numeric field")
  }
  for c in s.iter() {
    if !(c >= '0' && c <= '9') &&
      c != '+' &&
      c != '-' &&
      c != '.' &&
      c != 'e' &&
      c != 'E' {
      raise Invalid("invalid decimal number")
    }
  }
  let v = @string.parse_double(s) catch {
    _ => raise Invalid("malformed decimal number")
  }
  if !finite(v) {
    raise Invalid("nonfinite numeric field")
  }
  v
}

///|
fn put_text(
  out : Array[Byte],
  p : Int,
  n : Int,
  s : String,
) -> Unit raise EdfError {
  if s.length() > n {
    raise Invalid("header text exceeds field width \{n}: \{s}")
  }
  for i = 0; i < n; i = i + 1 {
    out[p + i] = b' '
  }
  for i = 0; i < s.length(); i = i + 1 {
    let v = s[i].to_int()
    if v < 32 || v > 126 {
      raise Invalid("header text must be printable ASCII")
    }
    out[p + i] = v.to_byte()
  }
}

///|
fn copy_bytes(
  out : Array[Byte],
  p : Int,
  b : Bytes,
  start : Int,
  n : Int,
) -> Unit {
  for i = 0; i < n; i = i + 1 {
    out[p + i] = b[start + i]
  }
}

///|
fn read_sample(b : Bytes, p : Int, w : Int) -> Int raise EdfError {
  need(b, p, w)
  let mut v = 0
  for i = 0; i < w; i = i + 1 {
    v = v | (b[p + i].to_int() << (8 * i))
  }
  let shift = 32 - w * 8
  v << shift >> shift
}

///|
fn put_sample(out : Array[Byte], p : Int, w : Int, v : Int) -> Unit {
  for i = 0; i < w; i = i + 1 {
    out[p + i] = (v >> (8 * i)).to_byte()
  }
}