///|
/// bandwidth:同样一局,四种发包策略的线上字节数对比。
///
///   moon run examples/bandwidth
///
/// 回滚同步的「输入冗余」是带宽的主要开销:为了扛住丢包,每帧都要把最近
/// 若干帧的输入重发一遍。这里的五种策略做的是同一件事,只是包装方式不同:
///
/// 1. `Legacy`  :每帧一条 `Input`,固定重发最近 16 帧(V0.7 之前的做法)
/// 2. `Span`    :把同一玩家的 16 帧压成一条 `InputSpan`,但每帧的值照给
/// 3. `Masked`  :`InputSpan` + 变化掩码,只给与前一帧不同的值
/// 4. `Adaptive`:不再盲发 16 帧,而是从「对端已确认的那一帧 + 1」开始发
///
/// 前三种的区间长度一样(都是 16 帧),差别只在编码;第四种连长度都跟着
/// 链路变——延迟小就只发几帧,丢包时水位停住、区间自己变长;第五种再把
/// 每帧一次的校验和降到每 8 帧一次。
const PLAYERS : Int32 = 4

///|
const TOTAL : Int32 = 600

///|
/// 固定策略的重发帧数(`Legacy` / `Span` / `Masked` 的区间长度)。
///
/// 取 16 而不是 8,是为了模拟「为了保险把冗余配大」的常见做法——
/// 自适应策略的价值正是把配大了的冗余自己收回来。
const RESEND : Int32 = 16

///|
/// 自适应策略的区间上限:再长就该拆包或者放弃回滚了。
const SPAN_MAX : Int32 = 32

///|
/// 发包策略。
pub(all) enum Packing {
  Legacy
  Span
  Masked
  Adaptive
  Lean
} derive(Eq)

///|
pub extend Packing with Eq::{not_equal, equal}

///|
pub fn Packing::label(self : Packing) -> String {
  match self {
    Legacy => "每帧一条 Input,固定 16 帧"
    Span => "一条 InputSpan,固定 16 帧"
    Masked => "InputSpan + 变化掩码"
    Adaptive => "InputSpan + 掩码 + 对端水位"
    Lean => "以上 + 校验和每 8 帧"
  }
}

///|
/// 一局跑完的统计。字节数按「所有单向链路累加」计,与人数无关地可比。
pub(all) struct PackStats {
  /// 发出的总字节数。
  bytes : Int64
  /// 发出的总包数。
  packets : Int64
  /// 所有包里携带的输入帧数之和(含重发)。
  frames_carried : Int64
  /// 每个玩家的确认帧。
  confirmed : Array[Int32]
  /// 所有玩家回滚次数之和。
  rollbacks : Int64
  /// 所有玩家重放帧数之和。
  replayed : Int64
  /// 校验和帧比对失败次数。
  mismatches : Int32
  /// 首个分叉帧;-1 表示全程一致。
  first_divergence : Int32
}

///|
pub fn PackStats::avg_bytes_per_packet(self : PackStats) -> Int32 {
  if self.packets == 0L {
    return 0
  }
  (self.bytes / self.packets).to_int()
}

///|
pub fn PackStats::avg_frames_per_packet(self : PackStats) -> Int32 {
  if self.packets == 0L {
    return 0
  }
  (self.frames_carried / self.packets).to_int()
}

///|
/// 打一个上行包,返回(字节,包内携带的输入帧数)。
///
/// `peer` 只在自适应策略里用到:它是这个包要发给谁,决定了
/// 「从哪一帧开始重发」。
fn outbound(
  s : @session.Session[@scene.Scene],
  me : Int32,
  peer : Int32,
  f : Int32,
  packing : Packing,
) -> (Bytes, Int32) {
  let msgs : Array[@protocol.Message] = []
  let adaptive = packing == Adaptive || packing == Lean
  // 校验和是每帧 10 字节的大头:掉到每 8 帧一次,只延迟分叉检测,不影响正确性。
  let sync_every = if packing == Lean { 8 } else { 1 }
  let mut back = f - RESEND + 1
  if back < 1 {
    back = 1
  }
  if adaptive {
    // 对端已经确认过的帧不用再发;丢包时它的水位停住,区间自动变长。
    let hint = s.peer_confirmed(peer) + 1
    if hint > back {
      back = hint
    }
    if back > f {
      back = f
    }
    if f - back + 1 > SPAN_MAX {
      back = f - SPAN_MAX + 1
    }
  }
  let mut carried = 0
  if packing == Legacy {
    let mut m = back
    while m <= f {
      match s.player_input(me, m) {
        Some(bits) => {
          msgs.push(
            @protocol.Message::Input(
              @protocol.InputFrame::new_from(me, m, [bits]),
            ),
          )
          carried = carried + 1
        }
        None => ()
      }
      m = m + 1
    }
  } else {
    let frames : Array[UInt64] = []
    let mut m = back
    while m <= f {
      frames.push(
        match s.player_input(me, m) {
          Some(v) => v
          None => 0UL
        },
      )
      m = m + 1
    }
    if frames.length() > 0 {
      let span = if packing == Span {
        @protocol.InputSpanFrame::raw(me, back, frames)
      } else {
        @protocol.InputSpanFrame::of(me, back, frames)
      }
      msgs.push(@protocol.Message::Span(span))
      carried = frames.length()
    }
  }
  let c = s.confirmed()
  msgs.push(@protocol.Message::Confirm(@protocol.ConfirmFrame::new(c)))
  if f % sync_every == 0 {
    match s.hash_at(c) {
      Some(h) =>
        msgs.push(
          @protocol.Message::Checksum(@protocol.ChecksumFrame::new(c, h)),
        )
      None => ()
    }
  }
  (@protocol.encode_batch(msgs), carried)
}

///|
/// 把一条链路上收到的字节投递进会话,返回本次发现的校验失配数。
///
/// `src` 是发送方:`Confirm` 帧里不带玩家索引(它是全房水位),
/// 发送方身份由连接本身给出。
fn deliver(
  payload : Bytes,
  s : @session.Session[@scene.Scene],
  src : Int32,
) -> 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.Span(x) => {
        let mut i = 0
        for bits in x.expand() {
          s.receive_from(x.player, x.start + i, bits)
          i = i + 1
        }
      }
      @protocol.Confirm(c) => s.receive_confirm(src, c.frame)
      @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(_) => ()
      // 带宽示例不做重同步:收到状态帧只当没看见
      @protocol.Snap(_) => ()
      @protocol.SnapRequest(_) => ()
    }
  }
  bad
}

///|
/// 找首个分叉帧:从 1 扫到所有玩家的公共确认帧,逐帧比对历史哈希。
fn scan_divergence(sessions : Array[@session.Session[@scene.Scene]]) -> Int32 {
  let mut c = sessions[0].confirmed()
  let mut i = 1
  while i < sessions.length() {
    if sessions[i].confirmed() < c {
      c = sessions[i].confirmed()
    }
    i = i + 1
  }
  let mut f = 1
  while f <= c {
    let base = sessions[0].hash_at(f)
    let mut j = 1
    while j < sessions.length() {
      if sessions[j].hash_at(f) != base {
        return f
      }
      j = j + 1
    }
    f = f + 1
  }
  -1
}

///|
/// 跑一局 4 人对局,`packing` 决定发包策略。
pub fn run(
  packing : Packing,
  spec : @scene.LinkSpec,
  total : Int32,
) -> PackStats {
  let sessions : Array[@session.Session[@scene.Scene]] = []
  let mut i = 0
  while i < PLAYERS {
    sessions.push(
      @session.Session::with_players(@scene.Scene::new(), PLAYERS, i, 128),
    )
    i = i + 1
  }
  let nets : Array[Array[@sim_net.NetSim]] = []
  let mut from = 0
  while from < PLAYERS {
    let row : Array[@sim_net.NetSim] = []
    let mut to = 0
    while to < PLAYERS {
      row.push(
        @sim_net.NetSim::new(
          @sim_net.Link::new(
            spec.delay,
            spec.jitter,
            spec.loss,
            spec.reorder,
            ((from * 131 + to).to_uint64() + 1UL) * 2654435761UL,
          ),
        ),
      )
      to = to + 1
    }
    nets.push(row)
    from = from + 1
  }
  let mut bytes = 0L
  let mut packets = 0L
  let mut carried = 0L
  let mut mismatches = 0
  let mut f = 0
  while f < total {
    f = f + 1
    let mut p = 0
    while p < PLAYERS {
      ignore(sessions[p].advance(@scene.scripted_input(f, p)))
      p = p + 1
    }
    let mut sender = 0
    while sender < PLAYERS {
      let mut target = 0
      while target < PLAYERS {
        if target != sender {
          let (payload, frames) = outbound(
            sessions[sender],
            sender,
            target,
            f,
            packing,
          )
          bytes = bytes + payload.length().to_int64()
          packets = packets + 1L
          carried = carried + frames.to_int64()
          nets[sender][target].send(f, payload)
        }
        target = target + 1
      }
      sender = sender + 1
    }
    let mut target = 0
    while target < PLAYERS {
      let mut src = 0
      while src < PLAYERS {
        if src != target {
          for payload in nets[src][target].recv(f) {
            mismatches = mismatches + deliver(payload, sessions[target], src)
          }
        }
        src = src + 1
      }
      target = target + 1
    }
  }
  // 收尾:不再推进新帧,但继续重发最后一段——否则丢掉的包永远没有第二次机会,
  // 确认帧就停在那儿了。
  let mut t = total
  let mut drained = false
  while t < total + 120 && !drained {
    t = t + 1
    let mut sender = 0
    while sender < PLAYERS {
      let mut target = 0
      while target < PLAYERS {
        if target != sender {
          let (payload, frames) = outbound(
            sessions[sender],
            sender,
            target,
            total,
            packing,
          )
          bytes = bytes + payload.length().to_int64()
          packets = packets + 1L
          carried = carried + frames.to_int64()
          nets[sender][target].send(t, payload)
        }
        target = target + 1
      }
      sender = sender + 1
    }
    let mut target = 0
    while target < PLAYERS {
      let mut src = 0
      while src < PLAYERS {
        if src != target {
          for payload in nets[src][target].recv(t) {
            mismatches = mismatches + deliver(payload, sessions[target], src)
          }
        }
        src = src + 1
      }
      target = target + 1
    }
    let mut all = true
    let mut k = 0
    while k < PLAYERS {
      if sessions[k].confirmed() < total {
        all = false
      }
      k = k + 1
    }
    drained = all
  }
  let confirmed : Array[Int32] = []
  let mut rollbacks = 0L
  let mut replayed = 0L
  let mut k = 0
  while k < PLAYERS {
    confirmed.push(sessions[k].confirmed())
    rollbacks = rollbacks + sessions[k].rollbacks().to_int64()
    replayed = replayed + sessions[k].replayed()
    k = k + 1
  }
  {
    bytes,
    packets,
    frames_carried: carried,
    confirmed,
    rollbacks,
    replayed,
    mismatches,
    first_divergence: scan_divergence(sessions),
  }
}

///|
fn main {
  let spec = @scene.LinkSpec::new(3, 1, 5, 10)
  let modes : Array[Packing] = [Legacy, Span, Masked, Adaptive, Lean]
  let stats : Array[PackStats] = []
  for m in modes {
    stats.push(run(m, spec, TOTAL))
  }
  let base = stats[0].bytes
  println("")
  println(
    "4 人全互联(12 条链路),\{TOTAL} 帧,3 帧延迟 + 1 帧抖动 + 5% 丢包 + 10% 乱序:",
  )
  println("")
  println(
    "| 策略 | 发送字节 | 相对第一种 | 平均字节/包 | 平均携带帧数 | 确认帧 | 回滚 | 重放帧数 | 一致 |",
  )
  println("| --- | --- | --- | --- | --- | --- | --- | --- | --- |")
  let mut i = 0
  while i < modes.length() {
    println(row(modes[i], stats[i], base))
    i = i + 1
  }
  let mut ok = true
  for st in stats {
    if st.first_divergence != -1 {
      ok = false
    }
  }
  println("")
  println(
    "第一种每帧固定重发 16 帧,每个包 16 条 `Input`;第二种换成一条区间帧,头开销从 16 份降到 1 份;",
  )
  println(
    "第三种只发变化的值(脚本每 6 帧才换一次按键);第四种连区间都跟着链路走——",
  )
  println(
    "对端确认到哪就从哪之后开始发,于是「配大了的冗余」被自己收回,丢包时水位停住、区间自己变长。",
  )
  println("")
  // 高丢包 + 长延迟:自适应模式的区间会自己撑开,但收敛性不能打折。
  let hard = @scene.LinkSpec::new(10, 2, 25, 15)
  let legacy = run(Legacy, hard, TOTAL)
  let adaptive = run(Lean, hard, TOTAL)
  println(
    "10 帧延迟 + 2 帧抖动 + 25% 丢包 + 15% 乱序(链路比冗余还差):",
  )
  println("")
  println(
    "| 策略 | 发送字节 | 平均携带帧数 | 确认帧 | 回滚 | 重放帧数 | 一致 |",
  )
  println("| --- | --- | --- | --- | --- | --- | --- |")
  println(row_short("每帧一条 Input,固定 16 帧", legacy))
  println(row_short("InputSpan + 掩码 + 自适应 + 校验和/8", adaptive))
  println("")
  if ok && legacy.first_divergence == -1 && adaptive.first_divergence == -1 {
    println(
      "结论:五种策略都收敛,回滚与重放次数逐项相等——省的是字节,不是正确性。",
    )
  } else {
    println(
      "警告:出现了分叉或未收敛的会话,说明打包/投递有 bug。",
    )
  }
  println("")
}

///|
fn row(p : Packing, st : PackStats, base : Int64) -> String {
  let ratio = if base == 0L { 100 } else { (st.bytes * 100L / base).to_int() }
  "| " +
  p.label() +
  " | " +
  st.bytes.to_string() +
  " | " +
  ratio.to_string() +
  "% | " +
  st.avg_bytes_per_packet().to_string() +
  " | " +
  st.avg_frames_per_packet().to_string() +
  " | " +
  st.confirmed[0].to_string() +
  "/" +
  st.confirmed[1].to_string() +
  "/" +
  st.confirmed[2].to_string() +
  "/" +
  st.confirmed[3].to_string() +
  " | " +
  st.rollbacks.to_string() +
  " | " +
  st.replayed.to_string() +
  " | " +
  (if st.first_divergence == -1 { "yes" } else { "NO" }) +
  " |"
}

///|
fn row_short(name : String, st : PackStats) -> String {
  "| " +
  name +
  " | " +
  st.bytes.to_string() +
  " | " +
  st.avg_frames_per_packet().to_string() +
  " | " +
  st.confirmed[0].to_string() +
  "/" +
  st.confirmed[1].to_string() +
  "/" +
  st.confirmed[2].to_string() +
  "/" +
  st.confirmed[3].to_string() +
  " | " +
  st.rollbacks.to_string() +
  " | " +
  st.replayed.to_string() +
  " | " +
  (if st.first_divergence == -1 { "yes" } else { "NO" }) +
  " |"
}