package winpwn

// SprayResult is one labeled sample produced by a single spray attempt --
// ID is caller-defined (a protocol id, an attempt index, whatever the
// target's own response associates with the sample) and Key is whatever
// comparable, measurable value the attempt produced, almost always a
// leaked heap/pointer address.
type SprayResult[K any] struct {
	ID  int
	Key K
}

// SprayAndFind is the one grooming-loop shape behind both
// examples/heap_lfh and examples/heap_segment, pulled out into the library
// after writing near-identical versions of it twice by hand for those two
// tasks -- exactly the kind of repeated pattern worth a real primitive
// instead of a third copy-paste for the next heap task.
//
// It repeats spray up to maxAttempts times. After each new sample, it
// checks that sample against every sample collected so far (seed, plus
// every prior spray result) via match(older, newer); the first pair match
// reports true for is returned immediately, without spending the remaining
// attempts. Two distinct grooming shapes fall out of how seed/match are
// used:
//
//   - "Does a spray ever land on this one known target?" (examples/heap_lfh's
//     UAF: spray same-size replacements until one reuses the freed victim's
//     slot) -- pass seed as the single already-known sample (e.g. the freed
//     victim's leaked address) and match as plain equality. Every spray
//     attempt is then checked against that one fixed target.
//   - "Do any two sprayed samples satisfy a relation to each other?"
//     (examples/heap_segment's adjacent-chunk overflow: find two allocations
//     exactly sizeof(struct) apart) -- pass seed as nil/empty and match as
//     the relation itself (e.g. "exactly N bytes apart"). Every new sample
//     is checked against everything sprayed before it.
//
// Returns the matching (older, newer) pair, the attempt count spray reached
// before finding it, and ok=false if maxAttempts was exhausted with no
// match -- the caller decides whether that's worth retrying with a bigger
// spray (both example solve scripts just log.Fatal on it, since their
// USAGE.md walkthroughs already establish what spray size is reliable on a
// given machine/OS build; that reliability number is empirical, not a
// constant this function can know).
func SprayAndFind[K any](seed []SprayResult[K], maxAttempts int, spray func(attempt int) (SprayResult[K], error), match func(older, newer K) bool) (older, newer SprayResult[K], attempts int, ok bool, err error) {
	samples := make([]SprayResult[K], len(seed))
	copy(samples, seed)

	for attempt := 1; attempt <= maxAttempts; attempt++ {
		s, serr := spray(attempt)
		if serr != nil {
			return SprayResult[K]{}, SprayResult[K]{}, attempt, false, serr
		}

		for _, prev := range samples {
			if match(prev.Key, s.Key) {
				return prev, s, attempt, true, nil
			}
		}

		samples = append(samples, s)
	}

	return SprayResult[K]{}, SprayResult[K]{}, maxAttempts, false, nil
}
