///|
/// 一条确定性链路的参数。时间单位是逻辑帧(60fps 下 1 帧 ≈ 16.7ms),
/// `loss` / `reorder` 是百分比。
pub struct LinkSpec {
  delay : Int32
  jitter : Int32
  loss : Int32
  reorder : Int32
}

///|
pub fn LinkSpec::new(
  delay : Int32,
  jitter : Int32,
  loss : Int32,
  reorder : Int32,
) -> LinkSpec {
  { delay, jitter, loss, reorder, }
}

///|
/// 一条链路的运行结果。
pub struct LinkStats {
  /// 两端各自的确认帧。
  mut p0_confirmed : Int32
  mut p1_confirmed : Int32
  /// 两端各自发生的回滚次数。
  mut p0_rollbacks : Int32
  mut p1_rollbacks : Int32
  /// 收到的 `Checksum` 帧里与本端历史哈希不一致的次数。
  mut mismatches : Int32
  /// 首个「两端都已确认、但哈希不同」的帧;0 表示没有分叉。
  mut first_divergence : Int32
  /// 分叉帧上两端各自的状态哈希,方便对照排查。
  mut hash0 : UInt64
  mut hash1 : UInt64
  /// P0 累计重放过的帧数。
  mut replayed_p0 : Int64
  /// 单次回滚的最大深度。
  mut max_depth : Int32
  /// 链路丢掉的包数。
  mut dropped : Int32
  /// 两端在共同确认帧上的状态是否一致。
  mut synced : Bool
}

///|
/// 把一条链路上的字节解成消息并投递,返回本次发现的校验失配数。
pub fn apply(payload : Bytes, s : @session.Session[Scene]) -> Int32 {
  let mut bad = 0
  for msg in @protocol.decode_batch(payload) {
    match msg {
      @protocol.Input(x) => {
        let mut i = 0
        for bits in x.inputs {
          s.receive_from(x.player + i, x.frame, bits)
          i = i + 1
        }
      }
      // 只在**本端也已确认**的帧上比对:尚未确认的帧里还混着预测输入,
      // 和对方的真实输入本来就不同,比了只会得到假分叉。
      @protocol.Checksum(c) =>
        if c.frame <= s.confirmed() {
          match s.hash_at(c.frame) {
            Some(mine) => if mine != c.hash { bad = bad + 1 }
            None => ()
          }
        }
      @protocol.Active(a) => ignore(s.set_player_active(a.player, a.active))
      _ => ()
    }
  }
  bad
}

///|
/// 每帧的包:[最近 `resend` 帧输入] + [确认帧] + [校验和帧]。
fn pack(
  s : @session.Session[Scene],
  player : Int32,
  upto : Int32,
  resend : Int32,
  bad_frame : Int32,
) -> Bytes {
  let msgs : Array[@protocol.Message] = []
  let mut m = upto - resend + 1
  if m < 1 {
    m = 1
  }
  while m <= upto {
    let mut input = scripted_input(m, player)
    if m == bad_frame {
      // 真实事故:本地执行了 A,网线上发出去的却是 B(序列化 / 版本 bug)。
      // 这种偏差不会被本端回滚洗掉,只能靠两端的确认帧哈希发现。
      input = input ^ 1UL
    }
    msgs.push(
      @protocol.Message::Input(
        @protocol.InputFrame::new_from(player, m, [input]),
      ),
    )
    m = m + 1
  }
  msgs.push(
    @protocol.Message::Confirm(@protocol.ConfirmFrame::new(s.confirmed())),
  )
  match s.hash_at(s.confirmed()) {
    Some(h) =>
      msgs.push(
        @protocol.Message::Checksum(
          @protocol.ChecksumFrame::new(s.confirmed(), h),
        ),
      )
    None => ()
  }
  @protocol.encode_batch(msgs)
}

///|
/// 跑一局两个对等体的对局,返回统计结果。
///
/// `corrupt_frame` 不为 0 时,P1 在那一帧「本地执行 A、发出去的是 B」——
/// 用来验证分叉检测确实能在正确的帧上报出来。
///
/// 之所以不去直接改内存里的状态:回滚会从快照恢复并重放,任何没有被
/// 记录进输入流的改动都会被洗掉;只有输入流本身出错才会留下痕迹。
pub fn run_link(
  spec : LinkSpec,
  frames : Int32,
  corrupt_frame : Int32,
) -> LinkStats {
  let pa = @session.Session::with_window(Scene::new(), 0, 128)
  let pb = @session.Session::with_window(Scene::new(), 1, 128)
  let net_ab = @sim_net.NetSim::new(
    @sim_net.Link::new(
      spec.delay,
      spec.jitter,
      spec.loss,
      spec.reorder,
      20261028UL,
    ),
  )
  let net_ba = @sim_net.NetSim::new(
    @sim_net.Link::new(spec.delay, spec.jitter, spec.loss, spec.reorder, 777UL),
  )
  let stats : LinkStats = {
    p0_confirmed: 0,
    p1_confirmed: 0,
    p0_rollbacks: 0,
    p1_rollbacks: 0,
    mismatches: 0,
    first_divergence: 0,
    hash0: 0UL,
    hash1: 0UL,
    replayed_p0: 0L,
    max_depth: 0,
    dropped: 0,
    synced: false,
  }
  let mut checked = 0
  let mut f = 0
  while f < frames {
    f = f + 1
    ignore(pa.advance(scripted_input(f, 0)))
    ignore(pb.advance(scripted_input(f, 1)))
    net_ab.send(f, pack(pa, 0, f, 8, 0))
    net_ba.send(f, pack(pb, 1, f, 8, corrupt_frame))
    for payload in net_ab.recv(f) {
      stats.mismatches = stats.mismatches + apply(payload, pb)
    }
    for payload in net_ba.recv(f) {
      stats.mismatches = stats.mismatches + apply(payload, pa)
    }
    check_confirmed(pa, pb, stats, checked)
    checked = min_confirm(pa, pb)
  }
  // 收尾:最后一轮包还在路上。
  let mut t = frames
  while t < frames + 80 {
    t = t + 1
    for payload in net_ab.recv(t) {
      stats.mismatches = stats.mismatches + apply(payload, pb)
    }
    for payload in net_ba.recv(t) {
      stats.mismatches = stats.mismatches + apply(payload, pa)
    }
    check_confirmed(pa, pb, stats, checked)
    checked = min_confirm(pa, pb)
  }
  stats.p0_confirmed = pa.confirmed()
  stats.p1_confirmed = pb.confirmed()
  stats.p0_rollbacks = pa.rollbacks()
  stats.p1_rollbacks = pb.rollbacks()
  stats.replayed_p0 = pa.replayed()
  stats.max_depth = pa.max_rollback_depth()
  stats.dropped = net_ab.dropped() + net_ba.dropped()
  stats.synced = stats.first_divergence == 0
  stats
}

///|
fn min_confirm(
  pa : @session.Session[Scene],
  pb : @session.Session[Scene],
) -> Int32 {
  let a = pa.confirmed()
  let b = pb.confirmed()
  if a < b {
    a
  } else {
    b
  }
}

///|
/// 比对两端在共同确认帧上的历史哈希,记录首个分叉帧。
fn check_confirmed(
  pa : @session.Session[Scene],
  pb : @session.Session[Scene],
  stats : LinkStats,
  checked : Int32,
) -> Unit {
  let c = min_confirm(pa, pb)
  let mut f = checked + 1
  while f <= c {
    match (pa.hash_at(f), pb.hash_at(f)) {
      (Some(x), Some(y)) =>
        if x != y && stats.first_divergence == 0 {
          stats.first_divergence = f
          stats.hash0 = x
          stats.hash1 = y
        }
      _ => ()
    }
    f = f + 1
  }
}