///|
/// Named transport choices used by adapters.
pub(all) enum TransportKind {
  SerialRtu
  SerialAscii
  ModbusTcp
} derive(Debug, Eq)

///|
pub fn transport_kind_mode(kind : TransportKind) -> Mode {
  match kind {
    SerialRtu => Rtu
    SerialAscii => Ascii
    ModbusTcp => Tcp
  }
}

///|
pub fn transport_kind_name(kind : TransportKind) -> String {
  match kind {
    SerialRtu => "serial-rtu"
    SerialAscii => "serial-ascii"
    ModbusTcp => "modbus-tcp"
  }
}

///|
/// An encoded ADU that can be handed to a socket or serial adapter.
pub(all) struct EncodedAdu {
  mode : Mode
  transaction_id : UInt16
  bytes : Array[Byte]
}

///|
pub fn EncodedAdu::new(
  mode : Mode,
  transaction_id : UInt16,
  frame : Frame,
) -> Result[EncodedAdu, ModbusError] {
  match try_encode_mode(mode, transaction_id, frame) {
    Ok(bytes) => Ok({ mode, transaction_id, bytes })
    Err(error) => Err(error)
  }
}

///|
pub fn EncodedAdu::bytes(self : EncodedAdu) -> Array[Byte] {
  copy_bytes(self.bytes)
}

///|
pub fn EncodedAdu::mode(self : EncodedAdu) -> Mode {
  self.mode
}

///|
pub fn EncodedAdu::transaction_id(self : EncodedAdu) -> UInt16 {
  self.transaction_id
}

///|
pub fn EncodedAdu::decode(self : EncodedAdu) -> Result[Frame, ModbusError] {
  match decode_transaction(self.mode, self.bytes) {
    Ok((transaction_id, frame)) =>
      if self.mode == Tcp && transaction_id != self.transaction_id {
        Err(InvalidTransaction)
      } else {
        Ok(frame)
      }
    Err(error) => Err(error)
  }
}

///|
/// A transport-neutral request envelope.
pub(all) struct ExchangeEnvelope {
  source : String
  destination : String
  adu : EncodedAdu
}

///|
pub fn ExchangeEnvelope::new(
  source : String,
  destination : String,
  adu : EncodedAdu,
) -> ExchangeEnvelope {
  { source, destination, adu }
}

///|
pub fn ExchangeEnvelope::size(self : ExchangeEnvelope) -> Int {
  self.adu.bytes.length()
}

///|
/// A deterministic in-memory transport used by tests, demos, and simulators.
pub struct VirtualLink {
  mode : Mode
  max_queue : Int
  master_to_device : Array[Array[Byte]]
  device_to_master : Array[Array[Byte]]
  mut sent : Int
  mut received : Int
  mut dropped : Int
}

///|
pub fn VirtualLink::new(
  mode : Mode,
  max_queue? : Int = 64,
) -> Result[VirtualLink, ModbusError] {
  if max_queue < 1 {
    Err(CapacityExceeded)
  } else {
    Ok({
      mode,
      max_queue,
      master_to_device: [],
      device_to_master: [],
      sent: 0,
      received: 0,
      dropped: 0,
    })
  }
}

///|
pub fn VirtualLink::mode(self : VirtualLink) -> Mode {
  self.mode
}

///|
pub fn VirtualLink::send_request(
  self : VirtualLink,
  transaction_id : UInt16,
  frame : Frame,
) -> Result[Unit, ModbusError] {
  let adu = match EncodedAdu::new(self.mode, transaction_id, frame) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if self.master_to_device.length() >= self.max_queue {
    self.dropped += 1
    Err(Busy)
  } else {
    self.master_to_device.push(adu.bytes())
    self.sent += 1
    Ok(())
  }
}

///|
pub fn VirtualLink::receive_request(
  self : VirtualLink,
) -> Result[Frame, ModbusError] {
  match take_queue_head(self.master_to_device) {
    None => Err(NoResponse)
    Some(bytes) =>
      match decode_mode(self.mode, bytes) {
        Ok(frame) => Ok(frame)
        Err(error) => Err(error)
      }
  }
}

///|
pub fn VirtualLink::send_response(
  self : VirtualLink,
  transaction_id : UInt16,
  frame : Frame,
) -> Result[Unit, ModbusError] {
  let adu = match EncodedAdu::new(self.mode, transaction_id, frame) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if self.device_to_master.length() >= self.max_queue {
    self.dropped += 1
    Err(Busy)
  } else {
    self.device_to_master.push(adu.bytes())
    Ok(())
  }
}

///|
pub fn VirtualLink::receive_response(
  self : VirtualLink,
) -> Result[Array[Byte], ModbusError] {
  match take_queue_head(self.device_to_master) {
    None => Err(NoResponse)
    Some(bytes) => {
      self.received += 1
      Ok(bytes)
    }
  }
}

///|
/// Execute one request against a device and enqueue its response.
pub fn VirtualLink::round_trip(
  self : VirtualLink,
  device : Device,
  transaction_id : UInt16,
  request : Frame,
) -> Result[DeviceResponse, ModbusError] {
  match device.serve(request) {
    Err(error) => Err(error)
    Ok(NoReply) => Ok(NoReply)
    Ok(Reply(response)) =>
      match self.send_response(transaction_id, response) {
        Ok(_) => Ok(Reply(response))
        Err(error) => Err(error)
      }
  }
}

///|
pub fn VirtualLink::sent_count(self : VirtualLink) -> Int {
  self.sent
}

///|
pub fn VirtualLink::received_count(self : VirtualLink) -> Int {
  self.received
}

///|
pub fn VirtualLink::dropped_count(self : VirtualLink) -> Int {
  self.dropped
}

///|
pub fn VirtualLink::queued_requests(self : VirtualLink) -> Int {
  self.master_to_device.length()
}

///|
pub fn VirtualLink::queued_responses(self : VirtualLink) -> Int {
  self.device_to_master.length()
}

///|
pub fn VirtualLink::clear(self : VirtualLink) -> Unit {
  self.master_to_device.clear()
  self.device_to_master.clear()
}

///|
fn take_queue_head(queue : Array[Array[Byte]]) -> Array[Byte]? {
  if queue.length() == 0 {
    None
  } else {
    let head = queue[0]
    discard_byte_queue_prefix(queue, 1)
    Some(head)
  }
}

///|
fn discard_byte_queue_prefix(queue : Array[Array[Byte]], count : Int) -> Unit {
  let remaining : Array[Array[Byte]] = []
  for index in count.. TransportCapabilities {
  {
    mode,
    max_adu: limits_for(mode).max_adu,
    supports_broadcast: mode != Tcp,
    preserves_transaction_id: mode == Tcp,
  }
}

///|
pub fn transport_is_serial(mode : Mode) -> Bool {
  mode == Rtu || mode == Ascii
}

///|
pub fn transport_is_network(mode : Mode) -> Bool {
  mode == Tcp
}