///|
/// The server-side sorting request control OID (RFC 2891 1.1).
pub const SORT_REQUEST_OID : String = "1.2.840.113556.1.4.473"

///|
/// The server-side sorting response control OID (RFC 2891 1.2).
pub const SORT_RESPONSE_OID : String = "1.2.840.113556.1.4.474"

///|
/// One sort key of the server-side sort request control (RFC 2891).
pub struct SortKey {
  attribute_type : String
  ordering_rule : String?
  reverse : Bool
} derive(Eq, @debug.Debug)

///|
pub fn SortKey::new(
  attribute_type : String,
  ordering_rule? : String,
  reverse? : Bool,
) -> SortKey {
  {
    attribute_type,
    ordering_rule,
    reverse: match reverse {
      Some(r) => r
      None => false
    },
  }
}

///|
fn encode_sort_key(key : SortKey) -> Bytes {
  let mut content = encode_ldap_string(key.attribute_type)
  match key.ordering_rule {
    Some(rule) =>
      content = content + encode_tlv(context_tag(0, false), @utf8.encode(rule))
    None => ()
  }
  if key.reverse {
    content = content + encode_boolean(true)
  }
  encode_sequence([content])
}

///|
/// Build the server-side sort request control for a key list.
pub fn sort_request_control(keys : Array[SortKey]) -> Control {
  let mut list = Bytes::new(0)
  for key in keys {
    list = list + encode_sort_key(key)
  }
  Control::new(SORT_REQUEST_OID, false, encode_sequence([list]))
}

///|
pub struct SortResponse {
  result : ResultCode
  attribute_type : String?
} derive(Eq, @debug.Debug)

///|
/// Encode a sort response control value (used by tests and mock servers).
pub fn sort_response_value(
  sort_result : Int,
  attribute_type : String?,
) -> Bytes {
  let mut content = encode_enumerated(sort_result)
  match attribute_type {
    Some(attr) =>
      content = content + encode_tlv(context_tag(0, false), @utf8.encode(attr))
    None => ()
  }
  encode_sequence([content])
}

///|
/// Extract the server-side sort response control, returning `None` when
/// absent or malformed.
pub fn parse_sort_response(controls : Array[Control]?) -> SortResponse? {
  match controls {
    Some(list) =>
      for ctrl in list {
        if ctrl.control_type == SORT_RESPONSE_OID {
          let decoded = match decode_ber(ctrl.control_value, None) {
            Ok(v) => v
            Err(_) => return None
          }
          let kids = decoded.children_or_empty()
          if kids.length() < 1 {
            return None
          }
          let code = match parse_integer(kids[0].content, 8) {
            Ok(v) => v
            Err(_) => return None
          }
          let mut attr : String? = None
          if kids.length() > 1 &&
            kids[1].tag.class == ContextSpecific &&
            kids[1].tag.number == 0 {
            attr = Some(@utf8.decode_lossy(kids[1].content[:]))
          }
          return Some({
            result: ResultCode::from_int(code),
            attribute_type: attr,
          })
        }
      }
    None => ()
  }
  None
}