package relay

import (
	
	
	
	

	asnutil 
	

	ma 
	manet 
)

var (
	errTooManyReservations       = errors.New("too many reservations")
	errTooManyReservationsForIP  = errors.New("too many peers for IP address")
	errTooManyReservationsForASN = errors.New("too many peers for ASN")
)

type peerWithExpiry struct {
	Expiry time.Time
	Peer   peer.ID
}

// constraints implements various reservation constraints
type constraints struct {
	rc *Resources

	mutex sync.Mutex
	total []peerWithExpiry
	ips   map[string][]peerWithExpiry
	asns  map[uint32][]peerWithExpiry
}

// newConstraints creates a new constraints object.
// The methods are *not* thread-safe; an external lock must be held if synchronization
// is required.
func newConstraints( *Resources) *constraints {
	return &constraints{
		rc:   ,
		ips:  make(map[string][]peerWithExpiry),
		asns: make(map[uint32][]peerWithExpiry),
	}
}

// Reserve adds a reservation for a given peer with a given multiaddr.
// If adding this reservation violates IP, ASN, or total reservation constraints, an error is returned.
func ( *constraints) ( peer.ID,  ma.Multiaddr,  time.Time) error {
	.mutex.Lock()
	defer .mutex.Unlock()

	 := time.Now()
	.cleanup()
	// To handle refreshes correctly, remove the existing reservation for the peer.
	.cleanupPeer()

	if len(.total) >= .rc.MaxReservations {
		return errTooManyReservations
	}

	,  := manet.ToIP()
	if  != nil {
		return errors.New("no IP address associated with peer")
	}

	 := .ips[.String()]
	if len() >= .rc.MaxReservationsPerIP {
		return errTooManyReservationsForIP
	}

	var  []peerWithExpiry
	var  uint32
	if .To4() == nil {
		 = asnutil.AsnForIPv6()
		if  != 0 {
			 = .asns[]
			if len() >= .rc.MaxReservationsPerASN {
				return errTooManyReservationsForASN
			}
		}
	}

	.total = append(.total, peerWithExpiry{Expiry: , Peer: })

	 = append(, peerWithExpiry{Expiry: , Peer: })
	.ips[.String()] = 

	if  != 0 {
		 = append(, peerWithExpiry{Expiry: , Peer: })
		.asns[] = 
	}
	return nil
}

func ( *constraints) ( time.Time) {
	 := func( peerWithExpiry) bool {
		return .Expiry.Before()
	}
	.total = slices.DeleteFunc(.total, )
	for ,  := range .ips {
		.ips[] = slices.DeleteFunc(, )
		if len(.ips[]) == 0 {
			delete(.ips, )
		}
	}
	for ,  := range .asns {
		.asns[] = slices.DeleteFunc(, )
		if len(.asns[]) == 0 {
			delete(.asns, )
		}
	}
}

func ( *constraints) ( peer.ID) {
	 := func( peerWithExpiry) bool {
		return .Peer == 
	}
	.total = slices.DeleteFunc(.total, )
	for ,  := range .ips {
		.ips[] = slices.DeleteFunc(, )
		if len(.ips[]) == 0 {
			delete(.ips, )
		}
	}
	for ,  := range .asns {
		.asns[] = slices.DeleteFunc(, )
		if len(.asns[]) == 0 {
			delete(.asns, )
		}
	}
}