package winpwn

import "bytes"

const (
	cyclicAlphabet = "abcdefghijklmnopqrstuvwxyz"
	cyclicDefaultN = 4
)

// Cyclic returns the first length bytes of a de Bruijn sequence over the
// lowercase alphabet with subsequence length 4 (pwntools' default), the Go
// analogue of pwnlib.util.cyclic.cyclic -- a buffer where every 4-byte
// window is unique, so a crash address/register read back out of it tells
// you exactly how far into the buffer it pointed.
func Cyclic(length int) []byte {
	return CyclicN(length, cyclicDefaultN)
}

// CyclicN is Cyclic with an explicit subsequence length n instead of the
// default 4 (e.g. 8 to locate an offset into a 64-bit pointer-sized
// overwrite).
func CyclicN(length, n int) []byte {
	if length <= 0 {
		return []byte{}
	}
	out := make([]byte, 0, length)
	deBruijnEach(cyclicAlphabet, n, func(b byte) bool {
		out = append(out, b)
		return len(out) < length
	})
	if len(out) >= length || len(out) == 0 {
		return out[:length]
	}
	// The sequence's period (len(alphabet)^n) was shorter than the
	// requested length -- only possible for a tiny alphabet/n combination,
	// never in practice with the default 26-letter alphabet. Wrap.
	full := make([]byte, length)
	for i := range full {
		full[i] = out[i%len(out)]
	}
	return full
}

// CyclicFind returns the offset of subseq within the default (alphabet,
// n=4) de Bruijn sequence, or -1 if it can't appear in it -- the analogue
// of pwnlib.util.cyclic.cyclic_find. subseq is typically the 4 bytes read
// back from a crashed return address/register.
func CyclicFind(subseq []byte) int {
	return CyclicFindN(subseq, cyclicDefaultN)
}

// CyclicFindN is CyclicFind with an explicit subsequence length n, matching
// whatever n the buffer was generated with via CyclicN. Stops generating as
// soon as a match is found, so this stays fast even for n=8 (where the
// theoretical period, 26^8, is far too large to ever materialize) as long
// as subseq actually came from a real CyclicN(_, n) buffer, which is the
// only case this is ever used for.
func CyclicFindN(subseq []byte, n int) int {
	m := len(subseq)
	if m == 0 {
		return -1
	}

	window := make([]byte, 0, m)
	total := 0
	found := -1

	deBruijnEach(cyclicAlphabet, n, func(b byte) bool {
		if len(window) < m {
			window = append(window, b)
		} else {
			copy(window, window[1:])
			window[m-1] = b
		}
		total++
		if len(window) == m && bytes.Equal(window, subseq) {
			found = total - m
			return false
		}
		return true
	})

	return found
}

// deBruijnEach generates the de Bruijn sequence B(k, n) over alphabet (k =
// len(alphabet)) one byte at a time via yield, stopping as soon as yield
// returns false -- the Go equivalent of pwntools' de_bruijn() being a
// Python generator. This early-exit property is the entire point: B(k, n)'s
// period is k^n, which is astronomical for n=8 even with a small alphabet
// (26^8 ~ 2*10^11) and must never be materialized in full. Callers only
// ever need a bounded prefix (CyclicN) or an early match (CyclicFindN), and
// this lets both stop without walking the rest of the sequence. Uses the
// same Fredricksen-Kessler-Maiorana algorithm pwntools' cyclic() does.
func deBruijnEach(alphabet string, n int, yield func(byte) bool) {
	k := len(alphabet)
	a := make([]int, k*n)
	stop := false

	var db func(t, p int)
	db = func(t, p int) {
		if stop {
			return
		}
		if t > n {
			if n%p == 0 {
				for _, idx := range a[1 : p+1] {
					if !yield(alphabet[idx]) {
						stop = true
						return
					}
				}
			}
			return
		}
		a[t] = a[t-p]
		db(t+1, p)
		for j := a[t-p] + 1; j < k && !stop; j++ {
			a[t] = j
			db(t+1, t)
		}
	}
	db(1, 1)
}
