///|
/// Action taken when a frame matches a gateway policy rule.
pub(all) enum BridgeAction {
  ForwardBridge
  RewriteBridge
  DenyBridge
  AuditBridge
  RateLimitBridge
} derive(Debug, Eq)

///|
/// Rule matching a source request and selecting a target route.
pub(all) struct ServiceBridgeRule {
  id : String
  source_unit : Byte
  target_unit : Byte
  function : Byte
  source_start : UInt16
  span : Int
  target_start : UInt16
  action : BridgeAction
  priority : Int
  mut enabled : Bool
  tag : String
}

///|
pub fn ServiceBridgeRule::new(
  id : String,
  source_unit : Byte,
  target_unit : Byte,
  function : Byte,
  source_start : UInt16,
  span : Int,
  target_start : UInt16,
  action : BridgeAction,
  priority? : Int = 0,
  tag? : String = "",
) -> Result[ServiceBridgeRule, ModbusError] {
  if id == "" ||
    !is_valid_unit_id(source_unit) ||
    !is_valid_unit_id(target_unit) ||
    function == 0 ||
    span < 1 ||
    priority < 0 {
    return Err(InvalidData)
  }
  if !valid_address_range(source_start, span) ||
    !valid_address_range(target_start, span) {
    return Err(InvalidAddress)
  }
  Ok({
    id,
    source_unit,
    target_unit,
    function,
    source_start,
    span,
    target_start,
    action,
    priority,
    enabled: true,
    tag,
  })
}

///|
pub fn ServiceBridgeRule::id(self : ServiceBridgeRule) -> String {
  self.id
}

///|
pub fn ServiceBridgeRule::source_unit(self : ServiceBridgeRule) -> Byte {
  self.source_unit
}

///|
pub fn ServiceBridgeRule::target_unit(self : ServiceBridgeRule) -> Byte {
  self.target_unit
}

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

///|
pub fn ServiceBridgeRule::source_start(self : ServiceBridgeRule) -> UInt16 {
  self.source_start
}

///|
pub fn ServiceBridgeRule::target_start(self : ServiceBridgeRule) -> UInt16 {
  self.target_start
}

///|
pub fn ServiceBridgeRule::span(self : ServiceBridgeRule) -> Int {
  self.span
}

///|
pub fn ServiceBridgeRule::action(self : ServiceBridgeRule) -> BridgeAction {
  self.action
}

///|
pub fn ServiceBridgeRule::priority(self : ServiceBridgeRule) -> Int {
  self.priority
}

///|
pub fn ServiceBridgeRule::enabled(self : ServiceBridgeRule) -> Bool {
  self.enabled
}

///|
pub fn ServiceBridgeRule::tag(self : ServiceBridgeRule) -> String {
  self.tag
}

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

///|
pub fn ServiceBridgeRule::matches(
  self : ServiceBridgeRule,
  frame : Frame,
) -> Bool {
  self.enabled &&
  frame.unit_id == self.source_unit &&
  frame.pdu.function == self.function &&
  frame.pdu.data.length() >= 2 &&
  frame.pdu.data[0].to_int() * 256 + frame.pdu.data[1].to_int() >=
  self.source_start.to_int() &&
  frame.pdu.data[0].to_int() * 256 + frame.pdu.data[1].to_int() <
  self.source_start.to_int() + self.span
}

///|
pub fn ServiceBridgeRule::rewrite(
  self : ServiceBridgeRule,
  frame : Frame,
) -> Result[Frame, ModbusError] {
  if !self.matches(frame) {
    return Err(UnitMismatch)
  }
  if frame.pdu.data.length() < 2 {
    return Err(InvalidLength)
  }
  let address = frame.pdu.data[0].to_int() * 256 + frame.pdu.data[1].to_int()
  let translated = self.target_start.to_int() +
    address -
    self.source_start.to_int()
  let data = frame.pdu.data.copy()
  data[0] = (translated / 256).to_byte()
  data[1] = translated.to_byte()
  Ok(Frame::new(self.target_unit, frame.pdu.function, data))
}

///|
pub fn bridge_action_name(action : BridgeAction) -> String {
  match action {
    ForwardBridge => "forward"
    RewriteBridge => "rewrite"
    DenyBridge => "deny"
    AuditBridge => "audit"
    RateLimitBridge => "rate-limit"
  }
}

///|
/// Result of applying one gateway rule to a frame.
pub(all) struct BridgeDecision {
  rule_id : String?
  action : BridgeAction
  frame : Frame?
  reason : String
  matched : Bool
}

///|
pub fn BridgeDecision::new(
  action : BridgeAction,
  reason : String,
) -> BridgeDecision {
  { rule_id: None, action, frame: None, reason, matched: false }
}

///|
pub fn BridgeDecision::matched(
  rule : ServiceBridgeRule,
  action : BridgeAction,
  frame : Frame?,
  reason : String,
) -> BridgeDecision {
  { rule_id: Some(rule.id()), action, frame, reason, matched: true }
}

///|
pub fn BridgeDecision::rule_id(self : BridgeDecision) -> String? {
  self.rule_id
}

///|
pub fn BridgeDecision::action(self : BridgeDecision) -> BridgeAction {
  self.action
}

///|
pub fn BridgeDecision::frame(self : BridgeDecision) -> Frame? {
  self.frame
}

///|
pub fn BridgeDecision::reason(self : BridgeDecision) -> String {
  self.reason
}

///|
pub fn BridgeDecision::matched_rule(self : BridgeDecision) -> Bool {
  self.matched
}

///|
/// An ordered policy set for one gateway route.
pub(all) struct ServiceGatewayPolicy {
  id : String
  source : String
  destination : String
  rules : Array[ServiceBridgeRule]
  default_action : BridgeAction
  max_rules : Int
  mut revision : Int
}

///|
pub fn ServiceGatewayPolicy::new(
  id : String,
  source : String,
  destination : String,
  default_action? : BridgeAction = DenyBridge,
  max_rules? : Int = 256,
) -> Result[ServiceGatewayPolicy, ModbusError] {
  if id == "" ||
    source == "" ||
    destination == "" ||
    source == destination ||
    max_rules < 1 {
    return Err(InvalidData)
  }
  Ok({
    id,
    source,
    destination,
    rules: [],
    default_action,
    max_rules,
    revision: 0,
  })
}

///|
pub fn ServiceGatewayPolicy::id(self : ServiceGatewayPolicy) -> String {
  self.id
}

///|
pub fn ServiceGatewayPolicy::source(self : ServiceGatewayPolicy) -> String {
  self.source
}

///|
pub fn ServiceGatewayPolicy::destination(self : ServiceGatewayPolicy) -> String {
  self.destination
}

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

///|
pub fn ServiceGatewayPolicy::revision(self : ServiceGatewayPolicy) -> Int {
  self.revision
}

///|
pub fn ServiceGatewayPolicy::default_action(
  self : ServiceGatewayPolicy,
) -> BridgeAction {
  self.default_action
}

///|
pub fn ServiceGatewayPolicy::add_rule(
  self : ServiceGatewayPolicy,
  rule : ServiceBridgeRule,
) -> Result[Unit, ModbusError] {
  if self.rules.length() >= self.max_rules {
    return Err(CapacityExceeded)
  }
  for existing in self.rules {
    if existing.id() == rule.id() {
      return Err(InvalidData)
    }
  }
  self.rules.push(rule)
  self.revision += 1
  Ok(())
}

///|
pub fn ServiceGatewayPolicy::remove_rule(
  self : ServiceGatewayPolicy,
  id : String,
) -> Result[ServiceBridgeRule, ModbusError] {
  for i in 0.. ServiceBridgeRule? {
  for rule in self.rules {
    if rule.id() == id {
      return Some(rule)
    }
  }
  None
}

///|
/// Find the highest priority matching rule and execute its action.
pub fn ServiceGatewayPolicy::evaluate(
  self : ServiceGatewayPolicy,
  frame : Frame,
) -> Result[BridgeDecision, ModbusError] {
  let mut selected : ServiceBridgeRule? = None
  for rule in self.rules {
    if rule.matches(frame) {
      match selected {
        None => selected = Some(rule)
        Some(previous) =>
          if rule.priority() > previous.priority() {
            selected = Some(rule)
          }
      }
    }
  }
  match selected {
    None => Ok(BridgeDecision::new(self.default_action, "no-match"))
    Some(rule) =>
      match rule.action() {
        ForwardBridge | AuditBridge =>
          Ok(
            BridgeDecision::matched(rule, rule.action(), Some(frame), "matched"),
          )
        RewriteBridge => {
          let rewritten = match rule.rewrite(frame) {
            Ok(value) => value
            Err(error) => return Err(error)
          }
          Ok(
            BridgeDecision::matched(
              rule,
              RewriteBridge,
              Some(rewritten),
              "rewritten",
            ),
          )
        }
        DenyBridge =>
          Ok(BridgeDecision::matched(rule, DenyBridge, None, "denied"))
        RateLimitBridge =>
          Ok(
            BridgeDecision::matched(rule, RateLimitBridge, None, "rate-limited"),
          )
      }
  }
}

///|
/// Validate rule ordering and overlapping address windows.
pub fn ServiceGatewayPolicy::validate(
  self : ServiceGatewayPolicy,
) -> Array[ModbusError] {
  let errors : Array[ModbusError] = []
  for i in 0.. Array[String] {
  let lines : Array[String] = []
  for rule in self.rules {
    lines.push(
      rule.id() +
      " unit=" +
      rule.source_unit().to_int().to_string() +
      "->" +
      rule.target_unit().to_int().to_string() +
      " fn=" +
      rule.function().to_int().to_string() +
      " range=" +
      rule.source_start().to_int().to_string() +
      "+" +
      rule.span().to_string() +
      " action=" +
      bridge_action_name(rule.action()) +
      " priority=" +
      rule.priority().to_string(),
    )
  }
  lines
}

///|
/// Fleet-level route table for selecting one policy by endpoint pair.
pub(all) struct GatewayPolicyTable {
  policies : Array[ServiceGatewayPolicy]
  max_policies : Int
}

///|
pub fn GatewayPolicyTable::new(
  max_policies? : Int = 128,
) -> Result[GatewayPolicyTable, ModbusError] {
  if max_policies < 1 {
    Err(InvalidData)
  } else {
    Ok({ policies: [], max_policies })
  }
}

///|
pub fn GatewayPolicyTable::count(self : GatewayPolicyTable) -> Int {
  self.policies.length()
}

///|
pub fn GatewayPolicyTable::add(
  self : GatewayPolicyTable,
  policy : ServiceGatewayPolicy,
) -> Result[Unit, ModbusError] {
  if self.policies.length() >= self.max_policies {
    return Err(CapacityExceeded)
  }
  for existing in self.policies {
    if existing.id() == policy.id() {
      return Err(InvalidData)
    }
  }
  self.policies.push(policy)
  Ok(())
}

///|
pub fn GatewayPolicyTable::find(
  self : GatewayPolicyTable,
  source : String,
  destination : String,
) -> ServiceGatewayPolicy? {
  for policy in self.policies {
    if policy.source() == source && policy.destination() == destination {
      return Some(policy)
    }
  }
  None
}

///|
pub fn GatewayPolicyTable::evaluate(
  self : GatewayPolicyTable,
  source : String,
  destination : String,
  frame : Frame,
) -> Result[BridgeDecision, ModbusError] {
  match self.find(source, destination) {
    Some(policy) => policy.evaluate(frame)
    None => Ok(BridgeDecision::new(DenyBridge, "route-not-configured"))
  }
}

///|
pub fn GatewayPolicyTable::routes(self : GatewayPolicyTable) -> Array[String] {
  let result : Array[String] = []
  for policy in self.policies {
    result.push(policy.source() + "->" + policy.destination())
  }
  result
}