package connmgr

import (
	
	
	
	

	
	

	
)

// DefaultResolution is the default resolution of the decay tracker.
var DefaultResolution = 1 * time.Minute

// bumpCmd represents a bump command.
type bumpCmd struct {
	peer  peer.ID
	tag   *decayingTag
	delta int
}

// removeCmd represents a tag removal command.
type removeCmd struct {
	peer peer.ID
	tag  *decayingTag
}

// decayer tracks and manages all decaying tags and their values.
type decayer struct {
	cfg   *DecayerCfg
	mgr   *BasicConnMgr
	clock clock.Clock // for testing.

	tagsMu    sync.Mutex
	knownTags map[string]*decayingTag

	// lastTick stores the last time the decayer ticked. Guarded by atomic.
	lastTick atomic.Pointer[time.Time]

	// bumpTagCh queues bump commands to be processed by the loop.
	bumpTagCh   chan bumpCmd
	removeTagCh chan removeCmd
	closeTagCh  chan *decayingTag

	// closure thingies.
	closeCh chan struct{}
	doneCh  chan struct{}
	err     error
}

var _ connmgr.Decayer = (*decayer)(nil)

// DecayerCfg is the configuration object for the Decayer.
type DecayerCfg struct {
	Resolution time.Duration
	Clock      clock.Clock
}

// WithDefaults writes the default values on this DecayerConfig instance,
// and returns itself for chainability.
//
//	cfg := (&DecayerCfg{}).WithDefaults()
//	cfg.Resolution = 30 * time.Second
//	t := NewDecayer(cfg, cm)
func ( *DecayerCfg) () *DecayerCfg {
	.Resolution = DefaultResolution
	return 
}

// NewDecayer creates a new decaying tag registry.
func ( *DecayerCfg,  *BasicConnMgr) (*decayer, error) {
	// use real time if the Clock in the config is nil.
	if .Clock == nil {
		.Clock = clock.New()
	}

	 := &decayer{
		cfg:         ,
		mgr:         ,
		clock:       .Clock,
		knownTags:   make(map[string]*decayingTag),
		bumpTagCh:   make(chan bumpCmd, 128),
		removeTagCh: make(chan removeCmd, 128),
		closeTagCh:  make(chan *decayingTag, 128),
		closeCh:     make(chan struct{}),
		doneCh:      make(chan struct{}),
	}

	 := .clock.Now()
	.lastTick.Store(&)

	// kick things off.
	go .process()

	return , nil
}

func ( *decayer) ( string,  time.Duration,  connmgr.DecayFn,  connmgr.BumpFn) (connmgr.DecayingTag, error) {
	.tagsMu.Lock()
	defer .tagsMu.Unlock()

	if ,  := .knownTags[];  {
		return nil, fmt.Errorf("decaying tag with name %s already exists", )
	}

	if  < .cfg.Resolution {
		log.Warnf("decay interval for %s (%s) was lower than tracker's resolution (%s); overridden to resolution",
			, , .cfg.Resolution)
		 = .cfg.Resolution
	}

	if %.cfg.Resolution != 0 {
		log.Warnf("decay interval for tag %s (%s) is not a multiple of tracker's resolution (%s); "+
			"some precision may be lost", , , .cfg.Resolution)
	}

	 := .lastTick.Load()
	 := &decayingTag{
		trkr:     ,
		name:     ,
		interval: ,
		nextTick: .Add(),
		decayFn:  ,
		bumpFn:   ,
	}

	.knownTags[] = 
	return , nil
}

// Close closes the Decayer. It is idempotent.
func ( *decayer) () error {
	select {
	case <-.doneCh:
		return .err
	default:
	}

	close(.closeCh)
	<-.doneCh
	return .err
}

// process is the heart of the tracker. It performs the following duties:
//
//  1. Manages decay.
//  2. Applies score bumps.
//  3. Yields when closed.
func ( *decayer) () {
	defer close(.doneCh)

	 := .clock.Ticker(.cfg.Resolution)
	defer .Stop()

	var (
		   bumpCmd
		 = make(map[*decayingTag]struct{})
	)

	for {
		select {
		case <-.C:
			 := .clock.Now()
			.lastTick.Store(&)

			.tagsMu.Lock()
			for ,  := range .knownTags {
				if .nextTick.After() {
					// skip the tag.
					continue
				}
				// Mark the tag to be updated in this round.
				[] = struct{}{}
			}
			.tagsMu.Unlock()

			// Visit each peer, and decay tags that need to be decayed.
			for ,  := range .mgr.segments.buckets {
				.Lock()

				// Entered a segment that contains peers. Process each peer.
				for ,  := range .peers {
					for ,  := range .decaying {
						if ,  := []; ! {
							// skip this tag.
							continue
						}

						// ~ this value needs to be visited. ~
						var  int
						if ,  := .decayFn(*);  {
							// delete the value and move on to the next tag.
							 -= .Value
							delete(.decaying, )
						} else {
							// accumulate the delta, and apply the changes.
							 +=  - .Value
							.Value, .LastVisit = , 
						}
						.value += 
					}
				}

				.Unlock()
			}

			// Reset each tag's next visit round, and clear the visited set.
			for  := range  {
				.nextTick = .nextTick.Add(.interval)
				delete(, )
			}

		case  = <-.bumpTagCh:
			var (
				       = .clock.Now()
				,  = .peer, .tag
			)

			 := .mgr.segments.get()
			.Lock()

			 := .tagInfoFor(, .clock.Now())
			,  := .decaying[]
			if ! {
				 = &connmgr.DecayingValue{
					Tag:       ,
					Peer:      ,
					LastVisit: ,
					Added:     ,
					Value:     0,
				}
				.decaying[] = 
			}

			 := .Value
			.Value, .LastVisit = .Tag.(*decayingTag).bumpFn(*, .delta), 
			.value += .Value - 

			.Unlock()

		case  := <-.removeTagCh:
			 := .mgr.segments.get(.peer)
			.Lock()

			 := .tagInfoFor(.peer, .clock.Now())
			,  := .decaying[.tag]
			if ! {
				.Unlock()
				continue
			}
			.value -= .Value
			delete(.decaying, .tag)
			.Unlock()

		case  := <-.closeTagCh:
			// Stop tracking the tag.
			.tagsMu.Lock()
			delete(.knownTags, .name)
			.tagsMu.Unlock()

			// Remove the tag from all peers that had it in the connmgr.
			for ,  := range .mgr.segments.buckets {
				// visit all segments, and attempt to remove the tag from all the peers it stores.
				.Lock()
				for ,  := range .peers {
					if ,  := .decaying[];  {
						// decrease the value of the tagInfo, and delete the tag.
						.value -= .Value
						delete(.decaying, )
					}
				}
				.Unlock()
			}

		case <-.closeCh:
			return
		}
	}
}

// decayingTag represents a decaying tag, with an associated decay interval, a
// decay function, and a bump function.
type decayingTag struct {
	trkr     *decayer
	name     string
	interval time.Duration
	nextTick time.Time
	decayFn  connmgr.DecayFn
	bumpFn   connmgr.BumpFn

	// closed marks this tag as closed, so that if it's bumped after being
	// closed, we can return an error.
	closed atomic.Bool
}

var _ connmgr.DecayingTag = (*decayingTag)(nil)

func ( *decayingTag) () string {
	return .name
}

func ( *decayingTag) () time.Duration {
	return .interval
}

// Bump bumps a tag for this peer.
func ( *decayingTag) ( peer.ID,  int) error {
	if .closed.Load() {
		return fmt.Errorf("decaying tag %s had been closed; no further bumps are accepted", .name)
	}

	 := bumpCmd{peer: , tag: , delta: }

	select {
	case .trkr.bumpTagCh <- :
		return nil
	default:
		return fmt.Errorf(
			"unable to bump decaying tag for peer %s, tag %s, delta %d; queue full (len=%d)",
			, .name, , len(.trkr.bumpTagCh))
	}
}

func ( *decayingTag) ( peer.ID) error {
	if .closed.Load() {
		return fmt.Errorf("decaying tag %s had been closed; no further removals are accepted", .name)
	}

	 := removeCmd{peer: , tag: }

	select {
	case .trkr.removeTagCh <- :
		return nil
	default:
		return fmt.Errorf(
			"unable to remove decaying tag for peer %s, tag %s; queue full (len=%d)",
			, .name, len(.trkr.removeTagCh))
	}
}

func ( *decayingTag) () error {
	if !.closed.CompareAndSwap(false, true) {
		log.Warnf("duplicate decaying tag closure: %s; skipping", .name)
		return nil
	}

	select {
	case .trkr.closeTagCh <- :
		return nil
	default:
		return fmt.Errorf("unable to close decaying tag %s; queue full (len=%d)", .name, len(.trkr.closeTagCh))
	}
}