///|
/// A practical subset of an OPC UA Variant.
///
/// One- and multi-dimensional arrays are supported. Recursive built-in types
/// and nested arrays are rejected explicitly.
pub(all) enum VariantValue {
  Null
  BoolValue(Bool)
  SByteValue(Int32)
  ByteValue(UInt32)
  Int16Value(Int32)
  UInt16Value(UInt16)
  Int32Value(Int32)
  UInt32Value(UInt32)
  Int64Value(Int64)
  UInt64Value(UInt64)
  FloatValue(Float)
  DoubleValue(Double)
  StringValue(String?)
  DateTimeValue(UaDateTime)
  GuidValue(Bytes)
  ByteStringValue(Bytes?)
  XmlElementValue(String?)
  NodeIdValue(NodeId)
  ExpandedNodeIdValue(ExpandedNodeId)
  StatusCodeValue(StatusCode)
  QualifiedNameValue(QualifiedName)
  LocalizedTextValue(LocalizedText)
  ExtensionObjectValue(ExtensionObject)
  ArrayValue(VariantArray)
}

///|
/// A homogeneous Variant array with optional dimensions.
pub(all) struct VariantArray {
  element_type : UInt32
  values : Array[VariantValue]
  dimensions : Array[Int32]?
}

///|
/// Construct an array and infer its element type from the first value.
pub fn VariantArray::from_values(
  values : Array[VariantValue],
) -> VariantArray raise UaError {
  if values.is_empty() {
    raise InvalidValue("empty VariantArray requires an explicit element type")
  }
  let element_type = values[0].type_id()
  if element_type == 0U {
    raise InvalidValue("Variant arrays cannot contain Null values")
  }
  for value in values {
    match value {
      ArrayValue(_) =>
        raise InvalidValue("nested Variant arrays are not supported")
      Null => raise InvalidValue("Variant arrays cannot contain Null values")
      _ => ()
    }
    if value.type_id() != element_type {
      raise InvalidValue("Variant array elements must have the same type")
    }
  }
  { element_type, values, dimensions: None, }
}

///|
/// Construct an empty array with an explicit built-in element type.
pub fn VariantArray::empty(element_type : UInt32) -> VariantArray raise UaError {
  if element_type == 0U || element_type > 22U {
    raise UnsupportedType(element_type)
  }
  { element_type, values: [], dimensions: None, }
}

///|
/// Construct a multi-dimensional array from flattened values and dimensions.
pub fn VariantArray::from_dimensions(
  values : Array[VariantValue],
  dimensions : Array[Int32],
) -> VariantArray raise UaError {
  let array = VariantArray::from_values(values)
  validate_variant_dimensions(array.values.length(), dimensions)
  { ..array, dimensions: Some(dimensions), }
}

///|
/// Number of elements.
pub fn VariantArray::length(self : VariantArray) -> Int32 {
  self.values.length()
}

///|
/// Whether this array declares multiple dimensions.
pub fn VariantArray::is_multidimensional(self : VariantArray) -> Bool {
  match self.dimensions {
    Some(_) => true
    None => false
  }
}

///|
fn validate_variant_dimensions(
  value_count : Int32,
  dimensions : Array[Int32],
) -> Unit raise UaError {
  if dimensions.length() < 2 {
    raise InvalidValue("ArrayDimensions requires at least two dimensions")
  }
  let mut product = 1L
  for dimension in dimensions {
    if dimension <= 0 {
      raise InvalidValue("ArrayDimensions entries must be greater than zero")
    }
    product = product * dimension.to_int64()
    if product > value_count.to_int64() {
      raise InvalidValue("ArrayDimensions exceed the encoded value count")
    }
  }
  if product != value_count.to_int64() {
    raise InvalidValue("ArrayDimensions do not match the encoded value count")
  }
}

///|
/// OPC UA built-in type id for this Variant value.
pub fn VariantValue::type_id(self : VariantValue) -> UInt32 {
  match self {
    Null => 0
    BoolValue(_) => 1
    SByteValue(_) => 2
    ByteValue(_) => 3
    Int16Value(_) => 4
    UInt16Value(_) => 5
    Int32Value(_) => 6
    UInt32Value(_) => 7
    Int64Value(_) => 8
    UInt64Value(_) => 9
    FloatValue(_) => 10
    DoubleValue(_) => 11
    StringValue(_) => 12
    DateTimeValue(_) => 13
    GuidValue(_) => 14
    ByteStringValue(_) => 15
    XmlElementValue(_) => 16
    NodeIdValue(_) => 17
    ExpandedNodeIdValue(_) => 18
    StatusCodeValue(_) => 19
    QualifiedNameValue(_) => 20
    LocalizedTextValue(_) => 21
    ExtensionObjectValue(_) => 22
    ArrayValue(value) => value.element_type
  }
}

///|
/// Human-readable built-in type name.
pub fn VariantValue::type_name(self : VariantValue) -> String {
  match self {
    Null => "Null"
    BoolValue(_) => "Boolean"
    SByteValue(_) => "SByte"
    ByteValue(_) => "Byte"
    Int16Value(_) => "Int16"
    UInt16Value(_) => "UInt16"
    Int32Value(_) => "Int32"
    UInt32Value(_) => "UInt32"
    Int64Value(_) => "Int64"
    UInt64Value(_) => "UInt64"
    FloatValue(_) => "Float"
    DoubleValue(_) => "Double"
    StringValue(_) => "String"
    DateTimeValue(_) => "DateTime"
    GuidValue(_) => "Guid"
    ByteStringValue(_) => "ByteString"
    XmlElementValue(_) => "XmlElement"
    NodeIdValue(_) => "NodeId"
    ExpandedNodeIdValue(_) => "ExpandedNodeId"
    StatusCodeValue(_) => "StatusCode"
    QualifiedNameValue(_) => "QualifiedName"
    LocalizedTextValue(_) => "LocalizedText"
    ExtensionObjectValue(_) => "ExtensionObject"
    ArrayValue(value) => "Array[\{value.element_type}]"
  }
}

///|
/// Encode a Variant with its built-in type id.
pub fn UaWriter::write_variant(
  self : UaWriter,
  value : VariantValue,
) -> Unit raise UaError {
  match value {
    ArrayValue(array) => {
      let dimensions_flag = match array.dimensions {
        Some(_) => 0x40U
        None => 0U
      }
      self.write_u8(array.element_type | 0x80U | dimensions_flag)
      self.write_int32(array.length())
      for item in array.values {
        self.write_variant_scalar_payload(item)
      }
      match array.dimensions {
        Some(dimensions) => {
          self.write_int32(dimensions.length())
          for dimension in dimensions {
            self.write_int32(dimension)
          }
        }
        None => ()
      }
    }
    _ => {
      self.write_u8(value.type_id())
      self.write_variant_scalar_payload(value)
    }
  }
}

///|
fn UaWriter::write_variant_scalar_payload(
  self : UaWriter,
  value : VariantValue,
) -> Unit raise UaError {
  match value {
    Null => ()
    BoolValue(item) => self.write_bool(item)
    SByteValue(item) => self.write_sbyte(item)
    ByteValue(item) => self.write_u8(item)
    Int16Value(item) => self.write_int16(item)
    UInt16Value(item) => self.write_uint16(item)
    Int32Value(item) => self.write_int32(item)
    UInt32Value(item) => self.write_uint32(item)
    Int64Value(item) => self.write_int64(item)
    UInt64Value(item) => self.write_uint64(item)
    FloatValue(item) => self.write_float(item)
    DoubleValue(item) => self.write_double(item)
    StringValue(item) => self.write_nullable_string(item)
    DateTimeValue(item) => self.write_datetime(item)
    GuidValue(item) => {
      if item.length() != 16 {
        raise InvalidGuidLength(item.length())
      }
      self.buffer.write_bytes(item.exact_view())
    }
    ByteStringValue(item) => self.write_nullable_bytes(item)
    XmlElementValue(item) => self.write_nullable_string(item)
    NodeIdValue(item) => self.write_node_id(item)
    ExpandedNodeIdValue(item) => self.write_expanded_node_id(item)
    StatusCodeValue(item) => self.write_status_code(item)
    QualifiedNameValue(item) => self.write_qualified_name(item)
    LocalizedTextValue(item) => self.write_localized_text(item)
    ExtensionObjectValue(item) => self.write_extension_object(item)
    ArrayValue(_) =>
      raise InvalidValue("nested Variant arrays are not supported")
  }
}

///|
/// Decode a scalar Variant or a one-/multi-dimensional Variant array.
pub fn UaReader::read_variant(self : UaReader) -> VariantValue raise UaError {
  let encoding_mask = self.read_u8()
  let has_dimensions = (encoding_mask & 0x40U) != 0U
  let is_array = (encoding_mask & 0x80U) != 0U
  let type_id = encoding_mask & 0x3FU
  if has_dimensions && !is_array {
    raise UnsupportedType(encoding_mask)
  }
  if !is_array {
    return self.read_variant_scalar(type_id)
  }
  if type_id == 0U {
    raise UnsupportedType(encoding_mask)
  }
  let length = self.read_int32()
  if length < 0 {
    raise InvalidLength(length)
  }
  if length > self.limits.max_array_length {
    raise LimitExceeded(length, self.limits.max_array_length)
  }
  let values : Array[VariantValue] = []
  for _ in 0.. self.limits.max_array_length {
      raise LimitExceeded(dimension_count, self.limits.max_array_length)
    }
    let dimensions : Array[Int32] = []
    for _ in 0.. VariantValue raise UaError {
  match type_id {
    0 => Null
    1 => BoolValue(self.read_bool())
    2 => SByteValue(self.read_sbyte())
    3 => ByteValue(self.read_u8())
    4 => Int16Value(self.read_int16())
    5 => UInt16Value(self.read_uint16())
    6 => Int32Value(self.read_int32())
    7 => UInt32Value(self.read_uint32())
    8 => Int64Value(self.read_int64())
    9 => UInt64Value(self.read_uint64())
    10 => FloatValue(self.read_float())
    11 => DoubleValue(self.read_double())
    12 => StringValue(self.read_string())
    13 => DateTimeValue(self.read_datetime())
    14 => GuidValue(self.take(16).to_owned())
    15 => ByteStringValue(self.read_bytes())
    16 => XmlElementValue(self.read_string())
    17 => NodeIdValue(self.read_node_id())
    18 => ExpandedNodeIdValue(self.read_expanded_node_id())
    19 => StatusCodeValue(self.read_status_code())
    20 => QualifiedNameValue(self.read_qualified_name())
    21 => LocalizedTextValue(self.read_localized_text())
    22 => ExtensionObjectValue(self.read_extension_object())
    other => raise UnsupportedType(other)
  }
}