// DNS Question section (RFC 1035 ยง4.1.2).

///|
pub struct Question {
  name : String
  qtype : UInt16
  qclass : UInt16
}

// Encode a single question to wire format.  Message::encode uses the internal
// compression writer; this method is the standalone, uncompressed form.

///|
pub fn Question::encode_checked(self : Question) -> Result[Array[Byte], String] {
  let name_bytes = match encode_name_checked(self.name) {
    Ok(bytes) => bytes
    Err(err) => return Err("DNS question name: " + err)
  }
  let buf = Array::make(name_bytes.length() + 4, (0).to_byte())
  let pos = Ref(0)
  for i = 0; i < name_bytes.length(); i = i + 1 {
    buf[pos.val] = name_bytes[i]
    pos.val = pos.val + 1
  }
  buf[pos.val] = ((self.qtype >> 8) & 0xFF).to_byte()
  buf[pos.val + 1] = (self.qtype & 0xFF).to_byte()
  buf[pos.val + 2] = ((self.qclass >> 8) & 0xFF).to_byte()
  buf[pos.val + 3] = (self.qclass & 0xFF).to_byte()
  Ok(buf)
}

///|
pub fn Question::encode(self : Question) -> Array[Byte] {
  match self.encode_checked() {
    Ok(bytes) => bytes
    Err(error) => abort(error)
  }
}

// Decode a single question from wire format.

///|
pub fn decode_question(
  bytes : Array[Byte],
  offset : Int,
  msg_start : Int,
) -> Result[(Question, Int), String] {
  let (name, o1) = match decode_name(bytes, offset, msg_start) {
    Ok(value) => value
    Err(err) => return Err("DNS question name: " + err)
  }
  match wire_check_range(bytes, o1, 4) {
    Err(err) => return Err("DNS question: " + err)
    Ok(_) => ()
  }
  let qtype = ((bytes[o1].to_int() << 8) | bytes[o1 + 1].to_int()).to_uint16()
  let qclass = ((bytes[o1 + 2].to_int() << 8) | bytes[o1 + 3].to_int()).to_uint16()
  Ok(({ name, qtype, qclass }, o1 + 4))
}

// Decode multiple questions in sequence.

///|
pub fn decode_questions(
  bytes : Array[Byte],
  offset : Int,
  count : UInt16,
  msg_start : Int,
) -> Result[(Array[Question], Int), String] {
  if count.to_int() > max_section_records {
    return Err("DNS question count exceeds codec limit")
  }
  let questions : Array[Question] = Array::new(capacity=count.to_int())
  let pos = Ref(offset)
  for _i in 0.. value
      Err(err) => return Err(err)
    }
    questions.push(q)
    pos.val = next
  }
  Ok((questions, pos.val))
}

// Create a question for an A record query

///|
pub fn question_a(name : String) -> Question {
  { name, qtype: qtype_a, qclass: qclass_in }
}

// Create a question for an AAAA record query

///|
pub fn question_aaaa(name : String) -> Question {
  { name, qtype: qtype_aaaa, qclass: qclass_in }
}

// Create a question for an MX record query

///|
pub fn question_mx(name : String) -> Question {
  { name, qtype: qtype_mx, qclass: qclass_in }
}

// Create a question for a NS record query

///|
pub fn question_ns(name : String) -> Question {
  { name, qtype: qtype_ns, qclass: qclass_in }
}

// Create a question for a CNAME record query

///|
pub fn question_cname(name : String) -> Question {
  { name, qtype: qtype_cname, qclass: qclass_in }
}

// Create a question for a TXT record query

///|
pub fn question_txt(name : String) -> Question {
  { name, qtype: qtype_txt, qclass: qclass_in }
}

// Create a question for a SOA record query

///|
pub fn question_soa(name : String) -> Question {
  { name, qtype: qtype_soa, qclass: qclass_in }
}

// Create a question for a PTR record query

///|
pub fn question_ptr(name : String) -> Question {
  { name, qtype: qtype_ptr, qclass: qclass_in }
}

// Create a question for an SRV record query

///|
pub fn question_srv(name : String) -> Question {
  { name, qtype: qtype_srv, qclass: qclass_in }
}

// Create a question for an ANY record query

///|
pub fn question_any(name : String) -> Question {
  { name, qtype: qtype_any, qclass: qclass_in }
}

// Create a question with custom type and class

///|
pub fn question_custom(
  name : String,
  qtype : UInt16,
  qclass? : UInt16 = qclass_in,
) -> Question {
  { name, qtype, qclass }
}

// Check if a question is for a specific record type

///|
pub fn question_is_type(q : Question, qtype : UInt16) -> Bool {
  q.qtype == qtype
}

// Check if a question uses the standard IN class

///|
pub fn question_is_internet(q : Question) -> Bool {
  q.qclass == qclass_in
}

// Get a human-readable description of a question

///|
pub fn question_description(q : Question) -> String {
  q.name + " IN " + qtype_to_string(q.qtype)
}

// Get the expected wire format size of a question

///|
pub fn question_wire_size(q : Question) -> Int {
  name_wire_size(q.name) + 4
}

// Build a batch of questions for multiple record types for the same name

///|
pub fn questions_for_types(
  name : String,
  qtypes : Array[UInt16],
) -> Array[Question] {
  let result : Array[Question] = Array::new(capacity=qtypes.length())
  for i = 0; i < qtypes.length(); i = i + 1 {
    result.push(question_custom(name, qtypes[i]))
  }
  result
}

// Encode multiple questions into a single byte array

///|
pub fn encode_questions(questions : Array[Question]) -> Array[Byte] {
  let total = Ref(0)
  for q in questions {
    total.val = total.val + question_wire_size(q)
  }
  let buf = Array::make(total.val, (0).to_byte())
  let pos = Ref(0)
  for q in questions {
    let q_bytes = q.encode()
    for i = 0; i < q_bytes.length(); i = i + 1 {
      buf[pos.val] = q_bytes[i]
      pos.val = pos.val + 1
    }
  }
  buf
}

// Clone a question (useful for building responses)

///|
pub fn question_clone(q : Question) -> Question {
  { name: q.name, qtype: q.qtype, qclass: q.qclass }
}