///|
/// A decoded DICOM element value.
pub(all) enum Value {
  Text(String)
  Integer(Int)
  Bytes(Bytes)
} derive(Eq, Debug)

///|
/// One DICOM data element.
pub(all) struct DataElement {
  tag : Tag
  vr : ValueRepresentation
  value : Value
} derive(Eq, Debug)

///|
/// Construct a data element.
pub fn data_element(
  tag : Tag,
  vr : ValueRepresentation,
  value : Value,
) -> DataElement {
  { tag, vr, value }
}

///|
/// The ordered elements in a DICOM data set.
pub(all) struct DataSet {
  elements : Array[DataElement]
} derive(Eq, Debug)

///|
/// Create an empty data set.
pub fn data_set() -> DataSet {
  { elements: [] }
}

///|
/// Return the number of elements.
pub fn DataSet::length(self : DataSet) -> Int {
  self.elements.length()
}

///|
/// Return all elements in file order.
pub fn DataSet::elements(self : DataSet) -> Array[DataElement] {
  self.elements.copy()
}

///|
/// Find an element by tag.
pub fn DataSet::get(self : DataSet, tag : Tag) -> DataElement? {
  for element in self.elements {
    if element.tag == tag {
      return Some(element)
    }
  }
  None
}

///|
/// Add or replace an element while preserving its position when replacing.
pub fn DataSet::set(self : DataSet, element : DataElement) -> Unit {
  for index, current in self.elements {
    if current.tag == element.tag {
      self.elements[index] = element
      return
    }
  }
  self.elements.push(element)
}

///|
/// Remove an element by tag.
pub fn DataSet::remove(self : DataSet, tag : Tag) -> Bool {
  for index, element in self.elements {
    if element.tag == tag {
      ignore(self.elements.remove(index))
      return true
    }
  }
  false
}

///|
/// Return a text value when the element contains text.
pub fn DataSet::get_text(self : DataSet, tag : Tag) -> String? {
  match self.get(tag) {
    Some({ value: Text(text), .. }) => Some(text)
    _ => None
  }
}

///|
/// Return an integer value when the element contains an integer.
pub fn DataSet::get_int(self : DataSet, tag : Tag) -> Int? {
  match self.get(tag) {
    Some({ value: Integer(value), .. }) => Some(value)
    _ => None
  }
}

///|
/// Return raw bytes when the element contains bytes.
pub fn DataSet::get_bytes(self : DataSet, tag : Tag) -> Bytes? {
  match self.get(tag) {
    Some({ value: Bytes(value), .. }) => Some(value)
    _ => None
  }
}

///|
fn trim_dicom_text(bytes : Bytes) -> String {
  let text = bytes_to_ascii(bytes)
  text.trim(chars=" ").to_owned()
}

///|
fn decode_value(vr : ValueRepresentation, bytes : Bytes) -> Value {
  if vr.is_text() {
    Text(trim_dicom_text(bytes))
  } else {
    match vr {
      US if bytes.length() >= 2 => {
        let low = bytes[0].to_int()
        let high = bytes[1].to_int()
        Integer(low + high * 256)
      }
      SS if bytes.length() >= 2 => {
        let low = bytes[0].to_int()
        let high = bytes[1].to_int()
        let raw = low + high * 256
        Integer(if raw >= 32768 { raw - 65536 } else { raw })
      }
      UL if bytes.length() >= 4 => {
        let value = bytes[0].to_int() +
          bytes[1].to_int() * 256 +
          bytes[2].to_int() * 65536 +
          bytes[3].to_int() * 16777216
        Integer(value)
      }
      SL if bytes.length() >= 4 => {
        let raw = bytes[0].to_int() +
          bytes[1].to_int() * 256 +
          bytes[2].to_int() * 65536 +
          bytes[3].to_int() * 16777216
        Integer(
          if bytes[3].to_int() >= 128 {
            raw - 2147483647 - 1 - 2147483647 - 1
          } else {
            raw
          },
        )
      }
      _ => Bytes(bytes)
    }
  }
}