package swarm

import (
	
	

	ma 
	manet 
)

type BlackHoleState int

const (
	blackHoleStateProbing BlackHoleState = iota
	blackHoleStateAllowed
	blackHoleStateBlocked
)

func ( BlackHoleState) () string {
	switch  {
	case blackHoleStateProbing:
		return "Probing"
	case blackHoleStateAllowed:
		return "Allowed"
	case blackHoleStateBlocked:
		return "Blocked"
	default:
		return fmt.Sprintf("Unknown %d", )
	}
}

// BlackHoleSuccessCounter provides black hole filtering for dials. This filter should be used in concert
// with a UDP or IPv6 address filter to detect UDP or IPv6 black hole. In a black holed environment,
// dial requests are refused Requests are blocked if the number of successes in the last N dials is
// less than MinSuccesses.
// If a request succeeds in Blocked state, the filter state is reset and N subsequent requests are
// allowed before reevaluating black hole state. Dials cancelled when some other concurrent dial
// succeeded are counted as failures. A sufficiently large N prevents false negatives in such cases.
type BlackHoleSuccessCounter struct {
	// N is
	// 1. The minimum number of completed dials required before evaluating black hole state
	// 2. the minimum number of requests after which we probe the state of the black hole in
	// blocked state
	N int
	// MinSuccesses is the minimum number of Success required in the last n dials
	// to consider we are not blocked.
	MinSuccesses int
	// Name for the detector.
	Name string

	mu sync.Mutex
	// requests counts number of dial requests to peers. We handle request at a peer
	// level and record results at individual address dial level.
	requests int
	// dialResults of the last `n` dials. A successful dial is true.
	dialResults []bool
	// successes is the count of successful dials in outcomes
	successes int
	// state is the current state of the detector
	state BlackHoleState
}

// RecordResult records the outcome of a dial. A successful dial in Blocked state will change the
// state of the filter to Probing. A failed dial only blocks subsequent requests if the success
// fraction over the last n outcomes is less than the minSuccessFraction of the filter.
func ( *BlackHoleSuccessCounter) ( bool) {
	.mu.Lock()
	defer .mu.Unlock()

	if .state == blackHoleStateBlocked &&  {
		// If the call succeeds in a blocked state we reset to allowed.
		// This is better than slowly accumulating values till we cross the minSuccessFraction
		// threshold since a black hole is a binary property.
		.reset()
		return
	}

	if  {
		.successes++
	}
	.dialResults = append(.dialResults, )

	if len(.dialResults) > .N {
		if .dialResults[0] {
			.successes--
		}
		.dialResults = .dialResults[1:]
	}

	.updateState()
}

// HandleRequest returns the result of applying the black hole filter for the request.
func ( *BlackHoleSuccessCounter) () BlackHoleState {
	.mu.Lock()
	defer .mu.Unlock()

	.requests++

	if .state == blackHoleStateAllowed {
		return blackHoleStateAllowed
	} else if .state == blackHoleStateProbing || .requests%.N == 0 {
		return blackHoleStateProbing
	} else {
		return blackHoleStateBlocked
	}
}

func ( *BlackHoleSuccessCounter) () {
	.successes = 0
	.dialResults = .dialResults[:0]
	.requests = 0
	.updateState()
}

func ( *BlackHoleSuccessCounter) () {
	 := .state

	if len(.dialResults) < .N {
		.state = blackHoleStateProbing
	} else if .successes >= .MinSuccesses {
		.state = blackHoleStateAllowed
	} else {
		.state = blackHoleStateBlocked
	}

	if  != .state {
		log.Debugf("%s blackHoleDetector state changed from %s to %s", .Name, , .state)
	}
}

func ( *BlackHoleSuccessCounter) () BlackHoleState {
	.mu.Lock()
	defer .mu.Unlock()

	return .state
}

type blackHoleInfo struct {
	name            string
	state           BlackHoleState
	nextProbeAfter  int
	successFraction float64
}

func ( *BlackHoleSuccessCounter) () blackHoleInfo {
	.mu.Lock()
	defer .mu.Unlock()

	 := 0
	if .state == blackHoleStateBlocked {
		 = .N - (.requests % .N)
	}

	 := 0.0
	if len(.dialResults) > 0 {
		 = float64(.successes) / float64(len(.dialResults))
	}

	return blackHoleInfo{
		name:            .Name,
		state:           .state,
		nextProbeAfter:  ,
		successFraction: ,
	}
}

// blackHoleDetector provides UDP and IPv6 black hole detection using a `BlackHoleSuccessCounter` for each.
// For details of the black hole detection logic see `BlackHoleSuccessCounter`.
// In Read Only mode, detector doesn't update the state of underlying filters and refuses requests
// when black hole state is unknown. This is useful for Swarms made specifically for services like
// AutoNAT where we care about accurately reporting the reachability of a peer.
//
// Black hole filtering is done at a peer dial level to ensure that periodic probes to detect change
// of the black hole state are actually dialed and are not skipped because of dial prioritisation
// logic.
type blackHoleDetector struct {
	udp, ipv6 *BlackHoleSuccessCounter
	mt        MetricsTracer
	readOnly  bool
}

// FilterAddrs filters the peer's addresses removing black holed addresses
func ( *blackHoleDetector) ( []ma.Multiaddr) ( []ma.Multiaddr,  []ma.Multiaddr) {
	,  := false, false
	for ,  := range  {
		if !manet.IsPublicAddr() {
			continue
		}
		if isProtocolAddr(, ma.P_UDP) {
			 = true
		}
		if isProtocolAddr(, ma.P_IP6) {
			 = true
		}
	}

	 := blackHoleStateAllowed
	if .udp != nil &&  {
		 = .getFilterState(.udp)
		.trackMetrics(.udp)
	}

	 := blackHoleStateAllowed
	if .ipv6 != nil &&  {
		 = .getFilterState(.ipv6)
		.trackMetrics(.ipv6)
	}

	 = make([]ma.Multiaddr, 0, len())
	return ma.FilterAddrs(
		,
		func( ma.Multiaddr) bool {
			if !manet.IsPublicAddr() {
				return true
			}
			// allow all UDP addresses while probing irrespective of IPv6 black hole state
			if  == blackHoleStateProbing && isProtocolAddr(, ma.P_UDP) {
				return true
			}
			// allow all IPv6 addresses while probing irrespective of UDP black hole state
			if  == blackHoleStateProbing && isProtocolAddr(, ma.P_IP6) {
				return true
			}

			if  == blackHoleStateBlocked && isProtocolAddr(, ma.P_UDP) {
				 = append(, )
				return false
			}
			if  == blackHoleStateBlocked && isProtocolAddr(, ma.P_IP6) {
				 = append(, )
				return false
			}
			return true
		},
	), 
}

// RecordResult updates the state of the relevant BlackHoleSuccessCounters for addr
func ( *blackHoleDetector) ( ma.Multiaddr,  bool) {
	if .readOnly || !manet.IsPublicAddr() {
		return
	}
	if .udp != nil && isProtocolAddr(, ma.P_UDP) {
		.udp.RecordResult()
		.trackMetrics(.udp)
	}
	if .ipv6 != nil && isProtocolAddr(, ma.P_IP6) {
		.ipv6.RecordResult()
		.trackMetrics(.ipv6)
	}
}

func ( *blackHoleDetector) ( *BlackHoleSuccessCounter) BlackHoleState {
	if .readOnly {
		if .State() != blackHoleStateAllowed {
			return blackHoleStateBlocked
		}
		return blackHoleStateAllowed
	}
	return .HandleRequest()
}

func ( *blackHoleDetector) ( *BlackHoleSuccessCounter) {
	if .readOnly || .mt == nil {
		return
	}
	// Track metrics only in non readOnly state
	 := .info()
	.mt.UpdatedBlackHoleSuccessCounter(.name, .state, .nextProbeAfter, .successFraction)
}