///|
/// Sequence numbers are modulo 32768 in IEC 104.
pub fn sequence_normalize(value : Int) -> Int {
  let mut result = value % 32768
  if result < 0 {
    result += 32768
  }
  result
}

///|
/// Distance from `start` to `end` on the 15-bit sequence ring.
pub fn sequence_distance(start : Int, end : Int) -> Int {
  sequence_normalize(end - start)
}

///|
/// Whether `candidate` is strictly before `limit` from `start`.
pub fn sequence_before(start : Int, candidate : Int, limit : Int) -> Bool {
  sequence_distance(start, candidate) < sequence_distance(start, limit)
}

///|
/// Whether a sequence acknowledgement is valid for a send cursor.
pub fn sequence_acknowledges(send_cursor : Int, acknowledgement : Int) -> Bool {
  sequence_distance(acknowledgement, send_cursor) < 16384
}

///|
/// Return the next sequence number on the IEC ring.
pub fn sequence_next(value : Int) -> Int {
  sequence_normalize(value + 1)
}

///|
/// Return the previous sequence number on the IEC ring.
pub fn sequence_previous(value : Int) -> Int {
  sequence_normalize(value - 1)
}

///|
/// A validated receive/send sequence window.
pub struct SequenceWindow {
  mut first_unacknowledged : Int
  mut next_send : Int
  mut next_receive : Int
  capacity : Int
} derive(Eq, Debug)

///|
pub fn SequenceWindow::new(capacity : Int) -> Result[SequenceWindow, String] {
  if capacity < 1 || capacity > 32767 {
    Err("sequence window capacity must be between 1 and 32767")
  } else {
    Ok({ first_unacknowledged: 0, next_send: 0, next_receive: 0, capacity })
  }
}

///|
pub fn SequenceWindow::pending(self : SequenceWindow) -> Int {
  sequence_distance(self.first_unacknowledged, self.next_send)
}

///|
pub fn SequenceWindow::available(self : SequenceWindow) -> Int {
  self.capacity - self.pending()
}

///|
pub fn SequenceWindow::next_send(self : SequenceWindow) -> Int {
  self.next_send
}

///|
pub fn SequenceWindow::next_receive(self : SequenceWindow) -> Int {
  self.next_receive
}

///|
pub fn SequenceWindow::can_send(self : SequenceWindow) -> Bool {
  self.pending() < self.capacity
}

///|
pub fn SequenceWindow::reserve_send(
  self : SequenceWindow,
) -> Result[Int, String] {
  if !self.can_send() {
    Err("sequence window is full")
  } else {
    let sequence = self.next_send
    self.next_send = sequence_next(self.next_send)
    Ok(sequence)
  }
}

///|
pub fn SequenceWindow::receive(
  self : SequenceWindow,
  sequence : Int,
) -> Result[Unit, String] {
  if sequence_normalize(sequence) != self.next_receive {
    Err("received sequence is outside the receive cursor")
  } else {
    self.next_receive = sequence_next(self.next_receive)
    Ok(())
  }
}

///|
pub fn SequenceWindow::acknowledge(
  self : SequenceWindow,
  sequence : Int,
) -> Result[Int, String] {
  let normalized = sequence_normalize(sequence)
  if !sequence_acknowledges(self.next_send, normalized) {
    Err("acknowledgement is ahead of the send cursor")
  } else if sequence_distance(self.first_unacknowledged, normalized) >
    self.pending() {
    Err("acknowledgement is outside the pending window")
  } else {
    let released = sequence_distance(self.first_unacknowledged, normalized)
    self.first_unacknowledged = normalized
    Ok(released)
  }
}

///|
/// Incremental APDU parsing result for TCP or serial gateway adapters.
pub enum ApduParseResult {
  NeedMore(Int)
  Complete(Frame, Int)
  Invalid(Diagnostic)
} derive(Debug)

///|
/// Parse one APDU from a byte view without requiring a socket implementation.
pub fn parse_apdu_prefix(data : Bytes) -> ApduParseResult {
  if data.length() < 2 {
    NeedMore(2 - data.length())
  } else if data[0] != b'\x68' {
    Invalid(
      Diagnostic::new(MalformedFrame, "APDU start byte is not 0x68", offset=0),
    )
  } else {
    let body_length = data[1].to_int()
    if body_length < 4 || body_length > 253 {
      Invalid(
        Diagnostic::new(
          MalformedFrame,
          "APDU length is outside 4..253",
          offset=1,
        ),
      )
    } else if data.length() < body_length + 2 {
      NeedMore(body_length + 2 - data.length())
    } else {
      let packet = data[:body_length + 2].to_owned()
      match decode_frame(packet) {
        Ok(frame) => Complete(frame, body_length + 2)
        Err(message) => Invalid(Diagnostic::new(MalformedFrame, message))
      }
    }
  }
}

///|
/// A portable byte accumulator for stream transports.
pub struct ApduStreamDecoder {
  mut buffer : Array[Byte]
  max_apdu : Int
} derive(Debug)

///|
pub fn ApduStreamDecoder::new(
  max_apdu? : Int = 255,
) -> Result[ApduStreamDecoder, String] {
  if max_apdu < 6 || max_apdu > 255 {
    Err("maximum APDU size must be between 6 and 255")
  } else {
    Ok({ buffer: [], max_apdu })
  }
}

///|
pub fn ApduStreamDecoder::buffered(self : ApduStreamDecoder) -> Int {
  self.buffer.length()
}

///|
pub fn ApduStreamDecoder::clear(self : ApduStreamDecoder) -> Unit {
  self.buffer.clear()
}

///|
pub fn ApduStreamDecoder::push(
  self : ApduStreamDecoder,
  data : Bytes,
) -> Result[Int, Diagnostic] {
  if self.buffer.length() + data.length() > self.max_apdu * 4 {
    Err(Diagnostic::new(MalformedFrame, "stream decoder buffer limit exceeded"))
  } else {
    for byte in data {
      self.buffer.push(byte)
    }
    Ok(data.length())
  }
}

///|
fn ApduStreamDecoder::decoder_buffer_bytes(self : ApduStreamDecoder) -> Bytes {
  Bytes::from_array(self.buffer)
}

///|
/// Parse and remove the first complete APDU, preserving partial data.
pub fn ApduStreamDecoder::next(self : ApduStreamDecoder) -> ApduParseResult {
  let result = parse_apdu_prefix(self.decoder_buffer_bytes())
  match result {
    Complete(frame, consumed) => {
      self.buffer = self.buffer[consumed:].to_owned()
      Complete(frame, consumed)
    }
    NeedMore(_) => result
    Invalid(error) => {
      if !self.buffer.is_empty() {
        ignore(self.buffer.remove(0))
      }
      Invalid(error)
    }
  }
}

///|
/// Number of complete frames available in a buffered stream.
pub fn ApduStreamDecoder::available_frames(self : ApduStreamDecoder) -> Int {
  let mut count = 0
  let mut offset = 0
  let snapshot = self.decoder_buffer_bytes()
  while offset < snapshot.length() {
    match parse_apdu_prefix(snapshot[offset:].to_owned()) {
      Complete(_, consumed) => {
        count += 1
        offset += consumed
      }
      _ => break
    }
  }
  count
}

///|
/// State of a timer that is driven by a host monotonic clock.
pub enum TimerState {
  Inactive
  Running(Int)
  Expired
} derive(Eq, Debug)

///|
pub struct ProtocolTimers {
  mut t0 : TimerState
  mut t1 : TimerState
  mut t2 : TimerState
  mut t3 : TimerState
} derive(Eq, Debug)

///|
pub fn ProtocolTimers::new() -> ProtocolTimers {
  { t0: Inactive, t1: Inactive, t2: Inactive, t3: Inactive }
}

///|
pub fn ProtocolTimers::start_t0(self : ProtocolTimers, deadline : Int) -> Unit {
  self.t0 = Running(deadline)
}

///|
pub fn ProtocolTimers::start_t1(self : ProtocolTimers, deadline : Int) -> Unit {
  self.t1 = Running(deadline)
}

///|
pub fn ProtocolTimers::start_t2(self : ProtocolTimers, deadline : Int) -> Unit {
  self.t2 = Running(deadline)
}

///|
pub fn ProtocolTimers::start_t3(self : ProtocolTimers, deadline : Int) -> Unit {
  self.t3 = Running(deadline)
}

///|
pub fn ProtocolTimers::stop_t0(self : ProtocolTimers) -> Unit {
  self.t0 = Inactive
}

///|
pub fn ProtocolTimers::stop_t1(self : ProtocolTimers) -> Unit {
  self.t1 = Inactive
}

///|
pub fn ProtocolTimers::stop_t2(self : ProtocolTimers) -> Unit {
  self.t2 = Inactive
}

///|
pub fn ProtocolTimers::stop_t3(self : ProtocolTimers) -> Unit {
  self.t3 = Inactive
}

///|
fn timer_due(timer : TimerState, now : Int) -> Bool {
  match timer {
    Running(deadline) => now >= deadline
    _ => false
  }
}

///|
pub fn ProtocolTimers::expired(
  self : ProtocolTimers,
  now : Int,
) -> Array[String] {
  let result : Array[String] = []
  if timer_due(self.t0, now) {
    result.push("t0")
  }
  if timer_due(self.t1, now) {
    result.push("t1")
  }
  if timer_due(self.t2, now) {
    result.push("t2")
  }
  if timer_due(self.t3, now) {
    result.push("t3")
  }
  result
}

///|
pub fn timer_state_examples() -> Array[TimerState] {
  [Inactive, Running(10), Expired]
}

///|
/// Host-visible session observation.
pub struct SessionSnapshot {
  state : LinkState
  send_sequence : Int
  receive_sequence : Int
  pending : Int
  window_size : Int
} derive(Eq, Debug)

///|
pub fn Session::snapshot(self : Session) -> SessionSnapshot {
  {
    state: self.state,
    send_sequence: self.send_sequence,
    receive_sequence: self.receive_sequence,
    pending: self.pending,
    window_size: self.window_size,
  }
}

///|
pub fn SessionSnapshot::is_started(self : SessionSnapshot) -> Bool {
  self.state == Started
}

///|
pub fn SessionSnapshot::available(self : SessionSnapshot) -> Int {
  self.window_size - self.pending
}

///|
pub fn SessionSnapshot::send_sequence(self : SessionSnapshot) -> Int {
  self.send_sequence
}

///|
pub fn SessionSnapshot::receive_sequence(self : SessionSnapshot) -> Int {
  self.receive_sequence
}

///|
pub fn SessionSnapshot::pending(self : SessionSnapshot) -> Int {
  self.pending
}

///|
/// A deterministic action emitted by a session driver.
pub enum SessionAction {
  SendStart
  SendStop
  SendTest
  SendSupervisory(Int)
  SendInformation(Int)
  DeliverInformation(Bytes)
  Acknowledge(Int)
  Report(Diagnostic)
  NoAction
} derive(Debug)

///|
pub fn session_action_examples() -> Array[SessionAction] {
  [
    SendStart,
    SendStop,
    SendTest,
    SendSupervisory(0),
    SendInformation(0),
    DeliverInformation(b""),
    Acknowledge(0),
    Report(Diagnostic::new(StateViolation, "example")),
    NoAction,
  ]
}

///|
/// Validate a frame against transport-level limits before sending it.
pub fn validate_transport_frame(frame : Frame) -> Result[Unit, Diagnostic] {
  match validate_frame(frame) {
    Ok(_) => Ok(())
    Err(message) => Err(Diagnostic::new(MalformedFrame, message))
  }
}

///|
/// Return the number of octets that an encoded frame will occupy.
pub fn encoded_frame_size(frame : Frame) -> Int {
  6 + frame.payload.length()
}

///|
/// Return a conservative APDU limit check for gateways.
pub fn frame_fits_apdu(frame : Frame, maximum : Int) -> Bool {
  maximum >= 6 && encoded_frame_size(frame) <= maximum
}

///|
pub fn transport_examples() -> Array[ApduParseResult] {
  [
    parse_apdu_prefix(b""),
    parse_apdu_prefix(b"\x68\x04\x07\x00"),
    parse_apdu_prefix(b"\x67\x04\x07\x00"),
  ]
}