package subsync

import "math/bits"

// signal is "somebody is speaking" as one bit per time bin.
//
// A bitset rather than a []bool because the search is the whole cost of this feature: a
// two-hour film at 50ms is around 144,000 bins, and every one of ~2,400 shifts has to
// compare all of them against every stretch candidate. Packed into words, one comparison
// is an AND and a population count over 2,250 words instead of 144,000 byte reads, which
// is the difference between a button that answers while somebody is still looking at the
// menu and one they wait on. math/bits.OnesCount64 compiles to a single instruction on
// every architecture this runs on.
type signal struct {
	words []uint64
	bins  int
	on    int
}

func newSignal(bins int) *signal {
	if bins < 1 {
		bins = 1
	}
	return &signal{words: make([]uint64, (bins+63)/64), bins: bins}
}

// set marks the half-open bin range [from, to) as speech.
func (s *signal) set(from, to int) {
	if from < 0 {
		from = 0
	}
	if to > s.bins {
		to = s.bins
	}
	for i := from; i < to; i++ {
		word, bit := i/64, uint(i%64)
		if s.words[word]&(1<<bit) == 0 {
			s.words[word] |= 1 << bit
			s.on++
		}
	}
}

// overlap counts the bins where both signals are speaking, with other displaced by shift
// bins. A positive shift means other is read later — the value to add to this signal's
// times to bring them onto other's.
//
// Negative shifts are answered by swapping the two, which is the same count from the other
// side and saves writing the word arithmetic twice in mirror image.
func (s *signal) overlap(other *signal, shift int) int {
	if shift < 0 {
		return other.overlap(s, -shift)
	}
	wordShift, bitShift := shift/64, uint(shift%64)
	total := 0
	for i := range s.words {
		mine := s.words[i]
		if mine == 0 {
			continue
		}
		j := i + wordShift
		if j >= len(other.words) {
			break
		}
		theirs := other.words[j] >> bitShift
		// Go defines a shift of 64 or more as zero, so this term vanishes when bitShift
		// is zero rather than needing a branch of its own.
		if j+1 < len(other.words) {
			theirs |= other.words[j+1] << (64 - bitShift)
		}
		total += bits.OnesCount64(mine & theirs)
	}
	return total
}
