///|
/// Canonical encoding class for a VCD value.
pub enum ValueKind {
  ScalarKind
  VectorKind
  RealKind
} derive(Debug, Eq)

///|
/// Return a value's encoding class.
pub fn value_kind(value : Value) -> ValueKind {
  match value {
    Scalar(_) => ScalarKind
    Vector(_) => VectorKind
    Real(_) => RealKind
  }
}

///|
/// Encode a value exactly as a VCD assignment token, excluding the id.
pub fn value_token(value : Value) -> String {
  match value {
    Scalar(value) => value.to_string()
    Vector(bits) => "b" + bits
    Real(number) => "r" + number.to_string()
  }
}

///|
/// Decode a value token together with its identifier.
pub fn decode_value_token(
  token : String,
  signal_id : String,
  time : Int64,
) -> Result[Change, ParseError] {
  if token == "" {
    return Err(InvalidValue(token))
  }
  let prefix = token.get_char(0).unwrap()
  if prefix == '0' ||
    prefix == '1' ||
    prefix == 'x' ||
    prefix == 'X' ||
    prefix == 'z' ||
    prefix == 'Z' {
    if token.length() != 1 {
      return Err(InvalidValue(token))
    }
    Ok({ time, id: signal_id, value: Scalar(prefix) })
  } else if prefix == 'b' || prefix == 'B' {
    if token.length() <= 1 {
      return Err(UnsupportedValue(token))
    }
    let bits = token.get_view(start=1).unwrap().to_owned()
    if !bits.to_array().all(fn(bit) { is_logic_char(bit) }) {
      return Err(InvalidValue(token))
    }
    Ok({ time, id: signal_id, value: Vector(bits) })
  } else if prefix == 'r' || prefix == 'R' {
    if token.length() <= 1 {
      return Err(UnsupportedValue(token))
    }
    match parse_real(token.get_view(start=1).unwrap().to_owned()) {
      Some(number) => Ok({ time, id: signal_id, value: Real(number) })
      None => Err(InvalidValue(token))
    }
  } else {
    Err(UnsupportedValue(token))
  }
}

///|
/// Return whether a character is a legal four-state VCD bit.
pub fn is_logic_char(value : Char) -> Bool {
  value == '0' ||
  value == '1' ||
  value == 'x' ||
  value == 'X' ||
  value == 'z' ||
  value == 'Z'
}

///|
/// Replace all unknown vector bits with a chosen stable value.
pub fn replace_unknown(value : Value, replacement : LogicBit) -> Value {
  let bits = value
    .bits()
    .map(fn(bit) {
      match bit {
        Unknown | HighImpedance => replacement
        _ => bit
      }
    })
  bits_value(bits)
}

///|
/// Coerce a vector to a fixed width by truncating the most significant bits.
pub fn truncate_value(value : Value, width : Int) -> Value {
  let bits = value.bits()
  let target = Int::max(1, width)
  if bits.length() <= target {
    value_zero_extend(value, target)
  } else {
    bits_value(bits[bits.length() - target:].to_owned())
  }
}

///|
/// Convert a value to a fixed-width binary string.
pub fn value_binary_string(value : Value, width : Int) -> String {
  bits_to_string(truncate_value(value, width).bits())
}

///|
/// Convert a known binary value to hexadecimal text.
pub fn value_hex(value : Value) -> String? {
  if value.is_unknown() || value.bits().length() == 0 {
    return None
  }
  let bits = value.bits()
  let padding = (4 - bits.length() % 4) % 4
  let padded : Array[LogicBit] = []
  for _ in 0.. 1
          _ => 0
        }
        total * 2 + bit_value
      })
    out.push(hex_digit(number))
  }
  Some(out.join(""))
}

///|
/// Return a VCD-compatible string with lower-case state markers.
pub fn canonical_value_text(value : Value) -> String {
  value.normalized().display()
}

///|
fn hex_digit(value : Int) -> String {
  match value {
    0 => "0"
    1 => "1"
    2 => "2"
    3 => "3"
    4 => "4"
    5 => "5"
    6 => "6"
    7 => "7"
    8 => "8"
    9 => "9"
    10 => "a"
    11 => "b"
    12 => "c"
    13 => "d"
    14 => "e"
    15 => "f"
    _ => "?"
  }
}