package pool

import (
	
	
	
	
	
	

	
)

var (
	// ErrClosed performs any operation on the closed client will return this error.
	ErrClosed = errors.New("redis: client is closed")

	// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
	ErrPoolTimeout = errors.New("redis: connection pool timeout")
)

var timers = sync.Pool{
	New: func() interface{} {
		 := time.NewTimer(time.Hour)
		.Stop()
		return 
	},
}

// Stats contains pool state information and accumulated stats.
type Stats struct {
	Hits     uint32 // number of times free connection was found in the pool
	Misses   uint32 // number of times free connection was NOT found in the pool
	Timeouts uint32 // number of times a wait timeout occurred

	TotalConns uint32 // number of total connections in the pool
	IdleConns  uint32 // number of idle connections in the pool
	StaleConns uint32 // number of stale connections removed from the pool
}

type Pooler interface {
	NewConn(context.Context) (*Conn, error)
	CloseConn(*Conn) error

	Get(context.Context) (*Conn, error)
	Put(context.Context, *Conn)
	Remove(context.Context, *Conn, error)

	Len() int
	IdleLen() int
	Stats() *Stats

	Close() error
}

type Options struct {
	Dialer func(context.Context) (net.Conn, error)

	PoolFIFO        bool
	PoolSize        int
	PoolTimeout     time.Duration
	MinIdleConns    int
	MaxIdleConns    int
	ConnMaxIdleTime time.Duration
	ConnMaxLifetime time.Duration
}

type lastDialErrorWrap struct {
	err error
}

type ConnPool struct {
	cfg *Options

	dialErrorsNum uint32 // atomic
	lastDialError atomic.Value

	queue chan struct{}

	connsMu   sync.Mutex
	conns     []*Conn
	idleConns []*Conn

	poolSize     int
	idleConnsLen int

	stats Stats

	_closed uint32 // atomic
}

var _ Pooler = (*ConnPool)(nil)

func ( *Options) *ConnPool {
	 := &ConnPool{
		cfg: ,

		queue:     make(chan struct{}, .PoolSize),
		conns:     make([]*Conn, 0, .PoolSize),
		idleConns: make([]*Conn, 0, .PoolSize),
	}

	.connsMu.Lock()
	.checkMinIdleConns()
	.connsMu.Unlock()

	return 
}

func ( *ConnPool) () {
	if .cfg.MinIdleConns == 0 {
		return
	}
	for .poolSize < .cfg.PoolSize && .idleConnsLen < .cfg.MinIdleConns {
		select {
		case .queue <- struct{}{}:
			.poolSize++
			.idleConnsLen++

			go func() {
				 := .addIdleConn()
				if  != nil &&  != ErrClosed {
					.connsMu.Lock()
					.poolSize--
					.idleConnsLen--
					.connsMu.Unlock()
				}

				.freeTurn()
			}()
		default:
			return
		}
	}
}

func ( *ConnPool) () error {
	,  := .dialConn(context.TODO(), true)
	if  != nil {
		return 
	}

	.connsMu.Lock()
	defer .connsMu.Unlock()

	// It is not allowed to add new connections to the closed connection pool.
	if .closed() {
		_ = .Close()
		return ErrClosed
	}

	.conns = append(.conns, )
	.idleConns = append(.idleConns, )
	return nil
}

func ( *ConnPool) ( context.Context) (*Conn, error) {
	return .newConn(, false)
}

func ( *ConnPool) ( context.Context,  bool) (*Conn, error) {
	,  := .dialConn(, )
	if  != nil {
		return nil, 
	}

	.connsMu.Lock()
	defer .connsMu.Unlock()

	// It is not allowed to add new connections to the closed connection pool.
	if .closed() {
		_ = .Close()
		return nil, ErrClosed
	}

	.conns = append(.conns, )
	if  {
		// If pool is full remove the cn on next Put.
		if .poolSize >= .cfg.PoolSize {
			.pooled = false
		} else {
			.poolSize++
		}
	}

	return , nil
}

func ( *ConnPool) ( context.Context,  bool) (*Conn, error) {
	if .closed() {
		return nil, ErrClosed
	}

	if atomic.LoadUint32(&.dialErrorsNum) >= uint32(.cfg.PoolSize) {
		return nil, .getLastDialError()
	}

	,  := .cfg.Dialer()
	if  != nil {
		.setLastDialError()
		if atomic.AddUint32(&.dialErrorsNum, 1) == uint32(.cfg.PoolSize) {
			go .tryDial()
		}
		return nil, 
	}

	 := NewConn()
	.pooled = 
	return , nil
}

func ( *ConnPool) () {
	for {
		if .closed() {
			return
		}

		,  := .cfg.Dialer(context.Background())
		if  != nil {
			.setLastDialError()
			time.Sleep(time.Second)
			continue
		}

		atomic.StoreUint32(&.dialErrorsNum, 0)
		_ = .Close()
		return
	}
}

func ( *ConnPool) ( error) {
	.lastDialError.Store(&lastDialErrorWrap{err: })
}

func ( *ConnPool) () error {
	,  := .lastDialError.Load().(*lastDialErrorWrap)
	if  != nil {
		return .err
	}
	return nil
}

// Get returns existed connection from the pool or creates a new one.
func ( *ConnPool) ( context.Context) (*Conn, error) {
	if .closed() {
		return nil, ErrClosed
	}

	if  := .waitTurn();  != nil {
		return nil, 
	}

	for {
		.connsMu.Lock()
		,  := .popIdle()
		.connsMu.Unlock()

		if  != nil {
			return nil, 
		}

		if  == nil {
			break
		}

		if !.isHealthyConn() {
			_ = .CloseConn()
			continue
		}

		atomic.AddUint32(&.stats.Hits, 1)
		return , nil
	}

	atomic.AddUint32(&.stats.Misses, 1)

	,  := .newConn(, true)
	if  != nil {
		.freeTurn()
		return nil, 
	}

	return , nil
}

func ( *ConnPool) ( context.Context) error {
	select {
	case <-.Done():
		return .Err()
	default:
	}

	select {
	case .queue <- struct{}{}:
		return nil
	default:
	}

	 := timers.Get().(*time.Timer)
	.Reset(.cfg.PoolTimeout)

	select {
	case <-.Done():
		if !.Stop() {
			<-.C
		}
		timers.Put()
		return .Err()
	case .queue <- struct{}{}:
		if !.Stop() {
			<-.C
		}
		timers.Put()
		return nil
	case <-.C:
		timers.Put()
		atomic.AddUint32(&.stats.Timeouts, 1)
		return ErrPoolTimeout
	}
}

func ( *ConnPool) () {
	<-.queue
}

func ( *ConnPool) () (*Conn, error) {
	if .closed() {
		return nil, ErrClosed
	}
	 := len(.idleConns)
	if  == 0 {
		return nil, nil
	}

	var  *Conn
	if .cfg.PoolFIFO {
		 = .idleConns[0]
		copy(.idleConns, .idleConns[1:])
		.idleConns = .idleConns[:-1]
	} else {
		 :=  - 1
		 = .idleConns[]
		.idleConns = .idleConns[:]
	}
	.idleConnsLen--
	.checkMinIdleConns()
	return , nil
}

func ( *ConnPool) ( context.Context,  *Conn) {
	if .rd.Buffered() > 0 {
		internal.Logger.Printf(, "Conn has unread data")
		.Remove(, , BadConnError{})
		return
	}

	if !.pooled {
		.Remove(, , nil)
		return
	}

	var  bool

	.connsMu.Lock()

	if .cfg.MaxIdleConns == 0 || .idleConnsLen < .cfg.MaxIdleConns {
		.idleConns = append(.idleConns, )
		.idleConnsLen++
	} else {
		.removeConn()
		 = true
	}

	.connsMu.Unlock()

	.freeTurn()

	if  {
		_ = .closeConn()
	}
}

func ( *ConnPool) ( context.Context,  *Conn,  error) {
	.removeConnWithLock()
	.freeTurn()
	_ = .closeConn()
}

func ( *ConnPool) ( *Conn) error {
	.removeConnWithLock()
	return .closeConn()
}

func ( *ConnPool) ( *Conn) {
	.connsMu.Lock()
	defer .connsMu.Unlock()
	.removeConn()
}

func ( *ConnPool) ( *Conn) {
	for ,  := range .conns {
		if  ==  {
			.conns = append(.conns[:], .conns[+1:]...)
			if .pooled {
				.poolSize--
				.checkMinIdleConns()
			}
			break
		}
	}
	atomic.AddUint32(&.stats.StaleConns, 1)
}

func ( *ConnPool) ( *Conn) error {
	return .Close()
}

// Len returns total number of connections.
func ( *ConnPool) () int {
	.connsMu.Lock()
	 := len(.conns)
	.connsMu.Unlock()
	return 
}

// IdleLen returns number of idle connections.
func ( *ConnPool) () int {
	.connsMu.Lock()
	 := .idleConnsLen
	.connsMu.Unlock()
	return 
}

func ( *ConnPool) () *Stats {
	return &Stats{
		Hits:     atomic.LoadUint32(&.stats.Hits),
		Misses:   atomic.LoadUint32(&.stats.Misses),
		Timeouts: atomic.LoadUint32(&.stats.Timeouts),

		TotalConns: uint32(.Len()),
		IdleConns:  uint32(.IdleLen()),
		StaleConns: atomic.LoadUint32(&.stats.StaleConns),
	}
}

func ( *ConnPool) () bool {
	return atomic.LoadUint32(&._closed) == 1
}

func ( *ConnPool) ( func(*Conn) bool) error {
	.connsMu.Lock()
	defer .connsMu.Unlock()

	var  error
	for ,  := range .conns {
		if () {
			if  := .closeConn();  != nil &&  == nil {
				 = 
			}
		}
	}
	return 
}

func ( *ConnPool) () error {
	if !atomic.CompareAndSwapUint32(&._closed, 0, 1) {
		return ErrClosed
	}

	var  error
	.connsMu.Lock()
	for ,  := range .conns {
		if  := .closeConn();  != nil &&  == nil {
			 = 
		}
	}
	.conns = nil
	.poolSize = 0
	.idleConns = nil
	.idleConnsLen = 0
	.connsMu.Unlock()

	return 
}

func ( *ConnPool) ( *Conn) bool {
	 := time.Now()

	if .cfg.ConnMaxLifetime > 0 && .Sub(.createdAt) >= .cfg.ConnMaxLifetime {
		return false
	}
	if .cfg.ConnMaxIdleTime > 0 && .Sub(.UsedAt()) >= .cfg.ConnMaxIdleTime {
		return false
	}

	if connCheck(.netConn) != nil {
		return false
	}

	.SetUsedAt()
	return true
}