///|
/// Encode a filter into its BER wire form (RFC 4511 4.5.1.7).
pub fn encode_filter(f : LdapFilter) -> Bytes {
  match f {
    And(items) => {
      let mut content = Bytes::new(0)
      for item in items {
        content = content + encode_filter(item)
      }
      encode_tlv(context_tag(0, true), content)
    }
    Or(items) => {
      let mut content = Bytes::new(0)
      for item in items {
        content = content + encode_filter(item)
      }
      encode_tlv(context_tag(1, true), content)
    }
    Not(inner) => encode_tlv(context_tag(2, true), encode_filter(inner))
    Equality(attr, value) =>
      encode_tlv(
        context_tag(3, true),
        encode_octet_string(@utf8.encode(attr)) + encode_octet_string(value),
      )
    Substrings(attr, sf) => {
      let mut sub = Bytes::new(0)
      match sf.initial {
        Some(i) => sub = sub + encode_tlv(context_tag(0, false), i)
        None => ()
      }
      for seg in sf.any {
        sub = sub + encode_tlv(context_tag(1, false), seg)
      }
      match sf.final_ {
        Some(f) => sub = sub + encode_tlv(context_tag(2, false), f)
        None => ()
      }
      let content = encode_octet_string(@utf8.encode(attr)) +
        encode_sequence([sub])
      encode_tlv(context_tag(4, true), content)
    }
    GreaterOrEqual(attr, value) =>
      encode_tlv(
        context_tag(5, true),
        encode_octet_string(@utf8.encode(attr)) + encode_octet_string(value),
      )
    LessOrEqual(attr, value) =>
      encode_tlv(
        context_tag(6, true),
        encode_octet_string(@utf8.encode(attr)) + encode_octet_string(value),
      )
    Present(attr) => encode_tlv(context_tag(7, false), @utf8.encode(attr))
    Approx(attr, value) =>
      encode_tlv(
        context_tag(8, true),
        encode_octet_string(@utf8.encode(attr)) + encode_octet_string(value),
      )
    Extensible(em) => {
      let mut content = Bytes::new(0)
      match em.matching_rule {
        Some(r) =>
          content = content + encode_tlv(context_tag(1, false), @utf8.encode(r))
        None => ()
      }
      match em.attr_type {
        Some(t) =>
          content = content + encode_tlv(context_tag(2, false), @utf8.encode(t))
        None => ()
      }
      content = content + encode_tlv(context_tag(3, false), em.match_value)
      if em.dn_attributes {
        content = content +
          encode_tlv(context_tag(4, false), Bytes::from_array([0xFF]))
      }
      encode_tlv(context_tag(9, true), content)
    }
  }
}

///|
pub fn LdapFilter::to_bytes(self : LdapFilter) -> Bytes {
  encode_filter(self)
}

///|
/// Decode a filter from its BER wire form.
pub fn decode_filter(
  bytes : Bytes,
  limits : BerLimits?,
) -> Result[LdapFilter, LdapError] {
  result_of_ldap(fn() raise LdapError {
    let value = match decode_ber(bytes, limits) {
      Ok(v) => v
      Err(e) => raise LdapError::Ber(e)
    }
    decode_filter_value(value)
  })
}

///|
fn decode_filter_value(value : BerValue) -> LdapFilter raise LdapError {
  if value.tag.class != ContextSpecific {
    raise LdapError::Decode("filter tag must be context-specific")
  }
  match value.tag.number {
    0 => {
      let items : Array[LdapFilter] = []
      for child in value.children_or_empty() {
        items.push(decode_filter_value(child))
      }
      And(items)
    }
    1 => {
      let items : Array[LdapFilter] = []
      for child in value.children_or_empty() {
        items.push(decode_filter_value(child))
      }
      Or(items)
    }
    2 => {
      let kids = value.children_or_empty()
      if kids.length() != 1 {
        raise LdapError::Decode("NOT filter must have exactly one child")
      }
      Not(decode_filter_value(kids[0]))
    }
    3 => {
      let (attr, val) = decode_ava(value)
      Equality(attr, val)
    }
    4 => {
      let kids = value.children_or_empty()
      if kids.length() != 2 {
        raise LdapError::Decode(
          "SUBSTRING filter must have type and substrings",
        )
      }
      let attr = as_ldap_string(kids[0])
      let sub_parts = kids[1].children_or_empty()
      let mut initial : Bytes? = None
      let any : Array[Bytes] = []
      let mut final_ : Bytes? = None
      for part in sub_parts {
        if part.tag.class == ContextSpecific {
          match part.tag.number {
            0 => initial = Some(part.content)
            1 => any.push(part.content)
            2 => final_ = Some(part.content)
            _ => raise LdapError::Decode("invalid substring part tag")
          }
        } else {
          raise LdapError::Decode("invalid substring part")
        }
      }
      Substrings(attr, SubstringFilter::new(initial, any, final_))
    }
    5 => {
      let (attr, val) = decode_ava(value)
      GreaterOrEqual(attr, val)
    }
    6 => {
      let (attr, val) = decode_ava(value)
      LessOrEqual(attr, val)
    }
    7 => Present(@utf8.decode_lossy(value.content[:]))
    8 => {
      let (attr, val) = decode_ava(value)
      Approx(attr, val)
    }
    9 => {
      let kids = value.children_or_empty()
      let mut rule : String? = None
      let mut attr_type : String? = None
      let mut match_value = Bytes::new(0)
      let mut dn_attrs = false
      for kid in kids {
        if kid.tag.class == ContextSpecific {
          match kid.tag.number {
            1 => rule = Some(@utf8.decode_lossy(kid.content[:]))
            2 => attr_type = Some(@utf8.decode_lossy(kid.content[:]))
            3 => match_value = kid.content
            4 =>
              dn_attrs = !kid.content.is_empty() &&
                kid.content.get(0) != Some(0)
            _ => raise LdapError::Decode("invalid extensible match part")
          }
        } else {
          raise LdapError::Decode("invalid extensible match part")
        }
      }
      Extensible(ExtensibleMatch::new(rule, attr_type, match_value, dn_attrs))
    }
    _ => raise LdapError::Decode("unknown filter tag \{value.tag.number}")
  }
}

///|
fn decode_ava(value : BerValue) -> (String, Bytes) raise LdapError {
  let kids = value.children_or_empty()
  if kids.length() != 2 {
    raise LdapError::Decode("attribute value assertion must have two elements")
  }
  (as_ldap_string(kids[0]), as_octets(kids[1]))
}