///|
/// Metadata used by configuration UIs and validation tools.
pub(all) struct FunctionDescriptor {
  function : Byte
  name : String
  request_min : Int
  request_max : Int
  response_variable : Bool
  writable : Bool
  description : String
}

///|
fn descriptor(
  function : Byte,
  name : String,
  request_min : Int,
  request_max : Int,
  response_variable : Bool,
  writable : Bool,
  description : String,
) -> FunctionDescriptor {
  {
    function,
    name,
    request_min,
    request_max,
    response_variable,
    writable,
    description,
  }
}

///|
pub fn function_descriptor(function : Byte) -> FunctionDescriptor? {
  match function_code(function) {
    ReadCoils =>
      Some(
        descriptor(
          1, "read-coils", 4, 4, true, false, "Read packed coil states.",
        ),
      )
    ReadDiscreteInputs =>
      Some(
        descriptor(
          2, "read-discrete-inputs", 4, 4, true, false, "Read packed discrete inputs.",
        ),
      )
    ReadHoldingRegisters =>
      Some(
        descriptor(
          3, "read-holding-registers", 4, 4, true, false, "Read holding registers.",
        ),
      )
    ReadInputRegisters =>
      Some(
        descriptor(
          4, "read-input-registers", 4, 4, true, false, "Read read-only input registers.",
        ),
      )
    WriteSingleCoil =>
      Some(
        descriptor(5, "write-single-coil", 4, 4, false, true, "Write one coil."),
      )
    WriteSingleRegister =>
      Some(
        descriptor(
          6, "write-single-register", 4, 4, false, true, "Write one holding register.",
        ),
      )
    ReadExceptionStatus =>
      Some(
        descriptor(
          7, "read-exception-status", 0, 0, false, false, "Read the device exception status.",
        ),
      )
    Diagnostics =>
      Some(
        descriptor(
          8, "diagnostics", 2, 252, true, false, "Run a diagnostics subfunction.",
        ),
      )
    GetCommEventCounter =>
      Some(
        descriptor(
          11, "get-comm-event-counter", 0, 0, false, false, "Read communication event counters.",
        ),
      )
    GetCommEventLog =>
      Some(
        descriptor(
          12, "get-comm-event-log", 0, 0, true, false, "Read the communication event log.",
        ),
      )
    ReportServerId =>
      Some(
        descriptor(
          17, "report-server-id", 0, 0, true, false, "Read server identification bytes.",
        ),
      )
    WriteMultipleCoils =>
      Some(
        descriptor(
          15, "write-multiple-coils", 6, 253, false, true, "Write packed coil states.",
        ),
      )
    WriteMultipleRegisters =>
      Some(
        descriptor(
          16, "write-multiple-registers", 6, 251, false, true, "Write a register block.",
        ),
      )
    ReadFileRecord =>
      Some(
        descriptor(
          20, "read-file-record", 8, 246, true, false, "Read file records.",
        ),
      )
    WriteFileRecord =>
      Some(
        descriptor(
          21, "write-file-record", 9, 253, false, true, "Write file records.",
        ),
      )
    MaskWriteRegister =>
      Some(
        descriptor(
          22, "mask-write-register", 6, 6, false, true, "Apply an AND/OR mask.",
        ),
      )
    ReadWriteMultipleRegisters =>
      Some(
        descriptor(
          23, "read-write-multiple-registers", 9, 251, true, true, "Write then read register blocks.",
        ),
      )
    ReadFifoQueue =>
      Some(
        descriptor(
          24, "read-fifo-queue", 2, 2, true, false, "Read a FIFO queue.",
        ),
      )
    EncapsulatedInterface =>
      Some(
        descriptor(
          43, "encapsulated-interface", 1, 253, true, false, "Access a MEI subprotocol.",
        ),
      )
    _ => None
  }
}

///|
/// Return every descriptor in numeric order.
pub fn function_catalog() -> Array[FunctionDescriptor] {
  let out : Array[FunctionDescriptor] = []
  let codes : Array[Byte] = [
    1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 15, 16, 17, 20, 21, 22, 23, 24, 43,
  ]
  for code in codes {
    match function_descriptor(code) {
      Some(value) => out.push(value)
      None => ()
    }
  }
  out
}

///|
pub fn FunctionDescriptor::supports_request(
  self : FunctionDescriptor,
  length : Int,
) -> Bool {
  length >= self.request_min && length <= self.request_max
}

///|
pub fn FunctionDescriptor::is_write(self : FunctionDescriptor) -> Bool {
  self.writable
}

///|
pub fn FunctionDescriptor::name(self : FunctionDescriptor) -> String {
  self.name
}

///|
pub fn FunctionDescriptor::function(self : FunctionDescriptor) -> Byte {
  self.function
}

///|
pub fn FunctionDescriptor::description(self : FunctionDescriptor) -> String {
  self.description
}

///|
/// Validate a request against the catalog before invoking a builder.
pub fn validate_catalog_request(frame : Frame) -> Result[Unit, ModbusError] {
  let descriptor = match function_descriptor(frame.pdu.function) {
    Some(value) => value
    None => return Err(Unsupported)
  }
  if descriptor.supports_request(frame.pdu.data.length()) {
    validate_pdu(frame.pdu)
  } else {
    Err(InvalidLength)
  }
}

///|
/// Return the names of all writable functions.
pub fn writable_function_names() -> Array[String] {
  let out : Array[String] = []
  for descriptor in function_catalog() {
    if descriptor.writable {
      out.push(descriptor.name)
    }
  }
  out
}

///|
/// Return the names of all functions whose response contains a byte count.
pub fn variable_response_function_names() -> Array[String] {
  let out : Array[String] = []
  for descriptor in function_catalog() {
    if descriptor.response_variable {
      out.push(descriptor.name)
    }
  }
  out
}