// Connectivity + load balancing, the pure decision core the Channel drives. Each dialed
// address is a `SubConn` moving IDLE -> CONNECTING -> READY (dropping to TRANSIENT_FAILURE
// on error); a `ConnectionPool` holds them and picks a READY one per call under an
// `LbPolicy`. This walks a pool of three backends through connect/fail/idle events and
// shows `pick_first` vs `round_robin` choosing among the ready ones.
//
//     moon run src/examples/23-connectivity-lb

///|
fn show_pick(label : String, pick : String?) -> Unit {
  match pick {
    Some(addr) => println("\{label}: \{addr}")
    None => println("\{label}: (none ready)")
  }
}

///|
fn run() -> Unit {
  let backends = ["10.0.0.1:50051", "10.0.0.2:50051", "10.0.0.3:50051"]

  // A round-robin pool. Every sub-connection starts IDLE, so nothing is pickable yet.
  let pool = @moonrpc.ConnectionPool::new(backends, policy=RoundRobin)
  show_pick("pick before any connect", pool.pick())

  // Dial the first two to READY; the third fails and lands in TRANSIENT_FAILURE.
  for addr in [backends[0], backends[1]] {
    match pool.subconn(addr) {
      Some(sc) => {
        sc.connect() // IDLE -> CONNECTING
        sc.on_connected() // CONNECTING -> READY
      }
      None => ()
    }
  }
  match pool.subconn(backends[2]) {
    Some(sc) => {
      sc.connect()
      sc.on_failure() // CONNECTING -> TRANSIENT_FAILURE
    }
    None => ()
  }
  let ready = pool.ready_addresses()
  println("ready backends: \{ready.length()} of \{backends.length()}")
  for addr in ready {
    println("  \{addr}")
  }

  // round_robin cycles through just the READY ones.
  show_pick("round_robin pick #1", pool.pick())
  show_pick("round_robin pick #2", pool.pick())
  show_pick("round_robin pick #3", pool.pick())

  // A READY connection going idle drops out of the ready set.
  match pool.subconn(backends[0]) {
    Some(sc) => {
      sc.on_idle() // READY -> IDLE
      println("backend[0] went idle; is_ready = \{sc.is_ready()}")
    }
    None => ()
  }
  show_pick("round_robin after idle", pool.pick())

  // A standalone LoadBalancer over a fixed address list: pick_first always the head,
  // round_robin cycling.
  let first = @moonrpc.LoadBalancer::new(backends)
  let rr = @moonrpc.LoadBalancer::new(backends, policy=RoundRobin)
  println("load balancer over \{first.size()} addresses:")
  show_pick("  pick_first #1", first.pick())
  show_pick("  pick_first #2", first.pick())
  show_pick("  round_robin #1", rr.pick())
  show_pick("  round_robin #2", rr.pick())
  show_pick("  round_robin #3", rr.pick())
  show_pick("  round_robin #4", rr.pick())
}

///|
#coverage.skip
fn main {
  run()
}