package alert

import (
	"errors"
	"log"
	"sync"
	"testing"
	"time"
)

func TestWait(t *testing.T) {
	wait := Notice{}
	var wg sync.WaitGroup

	// standard alert
	wg.Add(100)
	for x := 0; x < 100; x++ {
		go func() {
			w := wait.Wait(nil)
			wg.Done()
			if <-w {
				log.Fatal("incorrect routine wait response for alert expecting false")
			}
			wg.Done()
		}()
	}

	wg.Wait()
	wg.Add(100)
	isLeaky(t, &wait, nil)
	wait.Alert()
	wg.Wait()
	isLeaky(t, &wait, nil)

	// use kick
	ch := make(chan struct{})
	wg.Add(100)
	for x := 0; x < 100; x++ {
		go func() {
			w := wait.Wait(ch)
			wg.Done()
			if !<-w {
				log.Fatal("incorrect routine wait response for kick expecting true")
			}
			wg.Done()
		}()
	}
	wg.Wait()
	wg.Add(100)
	isLeaky(t, &wait, ch)
	close(ch)
	wg.Wait()
	ch = make(chan struct{})
	isLeaky(t, &wait, ch)

	// late receivers
	wg.Add(100)
	for x := 0; x < 100; x++ {
		go func(x int) {
			bb := wait.Wait(ch)
			wg.Done()
			if x%2 == 0 {
				time.Sleep(time.Millisecond * 5)
			}
			b := <-bb
			if b {
				log.Fatal("incorrect routine wait response since we call alert below; expecting false")
			}
			wg.Done()
		}(x)
	}
	wg.Wait()
	wg.Add(100)
	isLeaky(t, &wait, ch)
	wait.Alert()
	wg.Wait()
	isLeaky(t, &wait, ch)
}

// isLeaky tests to see if the wait functionality is returning an abnormal
// channel that is operational when it shouldn't be.
func isLeaky(t *testing.T, a *Notice, ch chan struct{}) {
	t.Helper()
	check := a.Wait(ch)
	time.Sleep(time.Millisecond * 5) // When we call wait a routine for hold is
	// spawned, so for a test we need to add in a time for goschedular to allow
	// routine to actually wait on the forAlert and kick channels
	select {
	case <-check:
		t.Fatal("leaky waiter")
	default:
	}
}

// 120801772	         9.334 ns/op	       0 B/op	       0 allocs/op // PREV
// 146173060	         9.154 ns/op	       0 B/op	       0 allocs/op // CURRENT
func BenchmarkAlert(b *testing.B) {
	n := Notice{}
	for x := 0; x < b.N; x++ {
		n.Alert()
	}
}

// BenchmarkWait benchmark
//
// 150352	      9916 ns/op	     681 B/op	       4 allocs/op // PREV
// 87436	     14724 ns/op	     682 B/op	       4 allocs/op // CURRENT
func BenchmarkWait(b *testing.B) {
	n := Notice{}
	for x := 0; x < b.N; x++ {
		n.Wait(nil)
	}
}

// getSize checks the buffer size for testing purposes
func getSize() int {
	mu.RLock()
	defer mu.RUnlock()
	return preAllocBufferSize
}

func TestSetPreAllocationCommsBuffer(t *testing.T) {
	t.Parallel()
	err := SetPreAllocationCommsBuffer(-1)
	if !errors.Is(err, errInvalidBufferSize) {
		t.Fatalf("received: '%v' but expected '%v'", err, errInvalidBufferSize)
	}

	if getSize() != 5 {
		t.Fatal("unexpected amount")
	}

	err = SetPreAllocationCommsBuffer(7)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected '%v'", err, nil)
	}

	if getSize() != 7 {
		t.Fatal("unexpected amount")
	}

	SetDefaultPreAllocationCommsBuffer()

	if getSize() != PreAllocCommsDefaultBuffer {
		t.Fatal("unexpected amount")
	}
}
