///|
/// Address mapping rule for a protocol gateway.
pub(all) struct GatewayRule {
  source_unit : Byte
  target_unit : Byte
  function : Byte
  source_start : UInt16
  target_start : UInt16
  span : Int
  mut enabled : Bool
}

///|
pub fn GatewayRule::new(
  source_unit : Byte,
  target_unit : Byte,
  function : Byte,
  source_start : UInt16,
  target_start : UInt16,
  span : Int,
) -> Result[GatewayRule, ModbusError] {
  if !is_valid_unit_id(source_unit) ||
    !is_valid_unit_id(target_unit, broadcast=false) {
    return Err(InvalidUnitId)
  }
  if span < 1 ||
    !valid_address_range(source_start, span) ||
    !valid_address_range(target_start, span) {
    return Err(InvalidAddress)
  }
  Ok({
    source_unit,
    target_unit,
    function,
    source_start,
    target_start,
    span,
    enabled: true,
  })
}

///|
pub fn GatewayRule::matches(self : GatewayRule, frame : Frame) -> Bool {
  if !self.enabled ||
    frame.unit_id != self.source_unit ||
    ordinary_function(frame.pdu.function) != self.function {
    return false
  }
  match first_address(frame) {
    Some(address) =>
      address.to_int() >= self.source_start.to_int() &&
      address.to_int() < self.source_start.to_int() + self.span
    None => true
  }
}

///|
pub fn GatewayRule::enable(self : GatewayRule, enabled : Bool) -> Unit {
  self.enabled = enabled
}

///|
fn first_address(frame : Frame) -> UInt16? {
  if frame.pdu.data.length() < 2 {
    None
  } else {
    Some((frame.pdu.data[0].to_uint16() << 8) | frame.pdu.data[1].to_uint16())
  }
}

///|
/// A deterministic frame gateway that rewrites unit ids and register/coil addresses.
pub struct Gateway {
  rules : Array[GatewayRule]
  mut forwarded : Int
  mut rejected : Int
  mut translated : Int
}

///|
pub fn Gateway::new() -> Gateway {
  { rules: [], forwarded: 0, rejected: 0, translated: 0 }
}

///|
pub fn Gateway::add_rule(
  self : Gateway,
  rule : GatewayRule,
) -> Result[Unit, ModbusError] {
  if self.rules.length() >= 256 {
    Err(CapacityExceeded)
  } else {
    self.rules.push(rule)
    Ok(())
  }
}

///|
pub fn Gateway::rule_count(self : Gateway) -> Int {
  self.rules.length()
}

///|
pub fn Gateway::rules(self : Gateway) -> Array[GatewayRule] {
  let out : Array[GatewayRule] = []
  for rule in self.rules {
    out.push(rule)
  }
  out
}

///|
/// Translate one request according to the first matching rule.
pub fn Gateway::translate(
  self : Gateway,
  frame : Frame,
) -> Result[Frame, ModbusError] {
  for rule in self.rules {
    if rule.matches(frame) {
      self.forwarded += 1
      match translate_with_rule(frame, rule) {
        Ok(value) => {
          self.translated += 1
          return Ok(value)
        }
        Err(error) => {
          self.rejected += 1
          return Err(error)
        }
      }
    }
  }
  self.rejected += 1
  Err(InvalidAddress)
}

///|
fn translate_with_rule(
  frame : Frame,
  rule : GatewayRule,
) -> Result[Frame, ModbusError] {
  let data = copy_bytes(frame.pdu.data)
  match first_address(frame) {
    None => Ok(with_unit(frame, rule.target_unit))
    Some(address) => {
      let offset = address.to_int() - rule.source_start.to_int()
      let target = rule.target_start.to_int() + offset
      if target < 0 || target > 65535 {
        Err(InvalidAddress)
      } else {
        data[0] = (target / 256).to_byte()
        data[1] = target.to_byte()
        Ok({
          unit_id: rule.target_unit,
          pdu: { function: frame.pdu.function, data },
        })
      }
    }
  }
}

///|
/// Translate a response back to the source address space.
pub fn Gateway::reverse(
  self : Gateway,
  request : Frame,
  response : Frame,
) -> Result[Frame, ModbusError] {
  if response.unit_id != request.unit_id {
    return Err(UnitMismatch)
  }
  for rule in self.rules {
    if rule.source_unit == request.unit_id &&
      rule.target_unit == response.unit_id &&
      rule.function == ordinary_function(request.pdu.function) {
      return Ok(with_unit(response, rule.source_unit))
    }
  }
  Ok(response)
}

///|
pub fn Gateway::forwarded_count(self : Gateway) -> Int {
  self.forwarded
}

///|
pub fn Gateway::rejected_count(self : Gateway) -> Int {
  self.rejected
}

///|
pub fn Gateway::translated_count(self : Gateway) -> Int {
  self.translated
}

///|
pub fn Gateway::reset_metrics(self : Gateway) -> Unit {
  self.forwarded = 0
  self.rejected = 0
  self.translated = 0
}

///|
/// A gateway route that combines a rule set, client, and virtual device.
pub(all) struct GatewayRoute {
  name : String
  gateway : Gateway
  device : Device
  client : Client
}

///|
pub fn GatewayRoute::new(
  name : String,
  mode : Mode,
  device : Device,
) -> GatewayRoute {
  { name, gateway: Gateway::new(), device, client: Client::new(mode) }
}

///|
pub fn GatewayRoute::add_rule(
  self : GatewayRoute,
  rule : GatewayRule,
) -> Result[Unit, ModbusError] {
  self.gateway.add_rule(rule)
}

///|
pub fn GatewayRoute::exchange(
  self : GatewayRoute,
  request : Frame,
) -> Result[Frame, ModbusError] {
  let translated = match self.gateway.translate(request) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let prepared = match self.client.begin(translated) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let response = match self.device.handle(translated) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  self.client.accept_frame(prepared, 0, response)
}

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

///|
pub fn GatewayRoute::metrics(self : GatewayRoute) -> (Int, Int, Int) {
  (
    self.gateway.forwarded_count(),
    self.gateway.rejected_count(),
    self.gateway.translated_count(),
  )
}

///|
/// Return whether a gateway rule can safely carry a function code.
pub fn gateway_function_supported(function : Byte) -> Bool {
  function != 0 && supported_function(function)
}