package connpool

import "time"

// LoopPools is K independent pools, one per event loop. There is NO
// shared lock across loops: pool i is only ever touched by the
// goroutine(s) bound to loop i, so checkout/return is uncontended and a
// request never has to take a connection owned by another loop (no
// cross-thread handoff). This is the specific change the Netflix talk
// highlights.
//
// The trade-off: each loop warms its own connections, so the minimum
// connection count rises ~K×. Subsetting (see ring.go) is the companion
// fix that keeps the total in check.
type LoopPools struct {
	pools []*Pool
}

func NewLoopPools(k int, dial Dialer, maxIdle int, idleTTL time.Duration) *LoopPools {
	lp := &LoopPools{pools: make([]*Pool, k)}
	for i := range lp.pools {
		lp.pools[i] = NewPool(dial, maxIdle, idleTTL)
	}
	return lp
}

// For returns the pool owned by event loop loopID. The caller is that
// loop's goroutine, so the pool's lock is effectively uncontended.
func (lp *LoopPools) For(loopID int) *Pool { return lp.pools[loopID%len(lp.pools)] }

// Loops returns the number of per-loop pools.
func (lp *LoopPools) Loops() int { return len(lp.pools) }

// Totals aggregates churn across all loops.
func (lp *LoopPools) Totals() (created, reused, closed int64) {
	for _, p := range lp.pools {
		created += p.Created.Load()
		reused += p.Reused.Load()
		closed += p.Closed.Load()
	}
	return
}

// IdleTotal sums idle connections across all loop pools.
func (lp *LoopPools) IdleTotal() int {
	n := 0
	for _, p := range lp.pools {
		n += p.IdleCount()
	}
	return n
}
