///|
/// Scalar samples are exposed as Double; all supported integers are exact.
pub(all) enum Scalar {
  I8
  U8
  I16
  U16
  I32
  U32
  F32
  F64
} derive(Debug, Eq)

///|
pub(all) enum Encoding {
  Raw
  Ascii
  Hex
} derive(Debug, Eq)

///|
pub(all) enum Endian {
  Little
  Big
} derive(Debug, Eq)

///|
pub fn Scalar::width(self : Scalar) -> Int32 {
  match self {
    I8 | U8 => 1
    I16 | U16 => 2
    I32 | U32 | F32 => 4
    F64 => 8
  }
}

///|
pub fn Scalar::name(self : Scalar) -> String {
  match self {
    I8 => "int8"
    U8 => "uint8"
    I16 => "int16"
    U16 => "uint16"
    I32 => "int32"
    U32 => "uint32"
    F32 => "float"
    F64 => "double"
  }
}

///|
pub fn parse_scalar(text : String) -> Scalar raise NrrdError {
  match text.to_lower() {
    "signed char" | "int8" | "int8_t" => I8
    "uchar" | "unsigned char" | "uint8" | "uint8_t" => U8
    "short"
    | "short int"
    | "signed short"
    | "signed short int"
    | "int16"
    | "int16_t" => I16
    "ushort"
    | "unsigned short"
    | "unsigned short int"
    | "uint16"
    | "uint16_t" => U16
    "int" | "signed int" | "int32" | "int32_t" => I32
    "uint" | "unsigned int" | "uint32" | "uint32_t" => U32
    "float" => F32
    "double" => F64
    _ => raise Unsupported("scalar type: " + text)
  }
}

///|
fn parse_encoding(text : String) -> Encoding raise NrrdError {
  match text.to_lower() {
    "raw" => Raw
    "ascii" | "text" | "txt" => Ascii
    "hex" => Hex
    _ => raise Unsupported("encoding: " + text)
  }
}

///|
/// Reject inexact integers and float32 overflow, instead of saturating casts.
fn Scalar::canonical(self : Scalar, value : Double) -> Double raise NrrdError {
  if self == F64 {
    return value
  }
  if self == F32 {
    let rounded = @float.Float::from_double(value).to_double()
    if !value.is_inf() && !value.is_nan() && rounded.is_inf() {
      raise Invalid("float32 overflow")
    }
    return rounded
  }
  if value.is_nan() || value.is_inf() || value.trunc() != value {
    raise Invalid("integer sample must be finite and integral")
  }
  let (low, high) = match self {
    I8 => (-128.0, 127.0)
    U8 => (0.0, 255.0)
    I16 => (-32768.0, 32767.0)
    U16 => (0.0, 65535.0)
    I32 => (-2147483648.0, 2147483647.0)
    U32 => (0.0, 4294967295.0)
    _ => (0.0, 0.0)
  }
  if value < low || value > high {
    raise Invalid("integer sample out of range")
  }
  value
}