package simd

import (
	"fmt"
	"runtime"
	"sort"
	"sync"
)

// Search finds the key using the naive way
func Naive(xs []uint64, k uint64) int16 {
	var i int
	for i = 0; i < len(xs); i += 2 {
		x := xs[i]
		if x >= k {
			return int16(i / 2)
		}
	}
	return int16(i / 2)
}

func Clever(xs []uint64, k uint64) int16 {
	if len(xs) < 8 {
		return Naive(xs, k)
	}
	var twos, pk [4]uint64
	pk[0] = k
	pk[1] = k
	pk[2] = k
	pk[3] = k
	for i := 0; i < len(xs); i += 8 {
		twos[0] = xs[i]
		twos[1] = xs[i+2]
		twos[2] = xs[i+4]
		twos[3] = xs[i+6]
		if twos[0] >= pk[0] {
			return int16(i / 2)
		}
		if twos[1] >= pk[1] {
			return int16((i + 2) / 2)
		}
		if twos[2] >= pk[2] {
			return int16((i + 4) / 2)
		}
		if twos[3] >= pk[3] {
			return int16((i + 6) / 2)
		}

	}
	return int16(len(xs) / 2)
}

func Parallel(xs []uint64, k uint64) int16 {
	cpus := runtime.NumCPU()
	if cpus%2 != 0 {
		panic(fmt.Sprintf("odd number of CPUs %v", cpus))
	}
	sz := len(xs)/cpus + 1
	var wg sync.WaitGroup
	retChan := make(chan int16, cpus)
	for i := 0; i < len(xs); i += sz {
		end := i + sz
		if end >= len(xs) {
			end = len(xs)
		}
		chunk := xs[i:end]
		wg.Add(1)
		go func(hd int16, xs []uint64, k uint64, wg *sync.WaitGroup, ch chan int16) {
			for i := 0; i < len(xs); i += 2 {
				if xs[i] >= k {
					ch <- (int16(i) + hd) / 2
					break
				}
			}
			wg.Done()
		}(int16(i), chunk, k, &wg, retChan)
	}
	wg.Wait()
	close(retChan)
	var min int16 = (1 << 15) - 1
	for i := range retChan {
		if i < min {
			min = i
		}
	}
	if min == (1<<15)-1 {
		return int16(len(xs) / 2)
	}
	return min
}

func Binary(keys []uint64, key uint64) int16 {
	return int16(sort.Search(len(keys), func(i int) bool {
		if i*2 >= len(keys) {
			return true
		}
		return keys[i*2] >= key
	}))
}

func cmp2_native(twos, pk [2]uint64) int16 {
	if twos[0] == pk[0] {
		return 0
	}
	if twos[1] == pk[1] {
		return 1
	}
	return 2
}

func cmp4_native(fours, pk [4]uint64) int16 {
	for i := range fours {
		if fours[i] >= pk[i] {
			return int16(i)
		}
	}
	return 4
}

func cmp8_native(a [8]uint64, pk [4]uint64) int16 {
	for i := range a {
		if a[i] >= pk[0] {
			return int16(i)
		}
	}
	return 8
}
