package notifier

import (
	"sync"
	"testing"
	"time"

	"github.com/onsi/gomega"
)

func TestNotifierRegister(t *testing.T) {
	g := gomega.NewWithT(t)
	n := New()
	key := "testKey"
	id := "testID"

	ch := n.Register(key, id)
	g.Expect(ch).ShouldNot(gomega.BeNil())
	g.Expect(n.keyMap).Should(gomega.HaveKey(key))
	g.Expect(n.keyMap[key]).Should(gomega.HaveKey(id))

	id2 := "testID2"
	chID2 := n.Register(key, id2)
	g.Expect(chID2).ShouldNot(gomega.BeNil())
	g.Expect(n.keyMap[key]).Should(gomega.HaveKey(id))
	g.Expect(ch).ShouldNot(gomega.Equal(chID2))

	key2 := "testKey2"
	chKey2 := n.Register(key2, id)
	g.Expect(chID2).ShouldNot(gomega.BeNil())
	g.Expect(n.keyMap).Should(gomega.HaveKey(key2))
	g.Expect(n.keyMap[key2]).Should(gomega.HaveKey(id))
	g.Expect(ch).ShouldNot(gomega.Equal(chKey2))

	// duplicate Register by testKey and testID
	chDup := n.Register(key, id)
	g.Expect(chDup).ShouldNot(gomega.BeNil())
	g.Expect(ch).Should(gomega.Equal(chDup))
}

func TestNotifierUnregister(t *testing.T) {
	g := gomega.NewWithT(t)
	n := New()
	key := "testKey"
	id := "testID"

	n.Register(key, id)
	n.Unregister(key, id)
	g.Expect(n.keyMap[key]).ShouldNot(gomega.HaveKey(id))
	if _, ok := n.keyMap[key]; ok {
		g.Expect(len(n.keyMap[key])).Should(gomega.Equal(0))
	}

	// Unregister again (not exist)
	n.Unregister(key, id)
}

func TestNotifierSend(t *testing.T) {
	g := gomega.NewWithT(t)
	n := New()
	key := "testKey"
	id1, id2 := "testID1", "testID2"
	dataOld := "testDataOld"

	ch1 := n.Register(key, id1)
	ch2 := n.Register(key, id2)

	// Send before receive, not block
	n.Send(key, dataOld)

	dataNew := "testDataNew"

	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		select {
		case received := <-ch1:
			g.Expect(received).To(gomega.Equal(dataOld))
		default:
			g.Fail("dataOld was not received from ch1")
		}
		select {
		case received := <-ch1:
			g.Expect(received).To(gomega.Equal(dataNew))
		case <-time.After(2 * time.Second):
			g.Fail("dataNew was not received from ch1")
		}
	}()
	go func() {
		defer wg.Done()
		select {
		case received := <-ch2:
			g.Expect(received).To(gomega.Equal(dataOld))
		default:
			g.Fail("dataOld was not received from ch2")
		}
		select {
		case received := <-ch2:
			g.Expect(received).To(gomega.Equal(dataNew))
		case <-time.After(2 * time.Second):
			g.Fail("dataNew was not received from ch2")
		}
	}()

	n.Send(key, dataNew)
	wg.Wait()
}

func TestNotifierSendTooMuch(t *testing.T) {
	g := gomega.NewWithT(t)
	n := New()
	key := "testKey"
	id1, id2 := "testID1", "testID2"
	data := "testData"

	ch1 := n.Register(key, id1)
	ch2 := n.Register(key, id2)

	// send too much and will close channel
	for i := 0; i < MaxNotifierChannelSize*2; i++ {
		n.Send(key, data)
	}

	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		for i := 0; i < MaxNotifierChannelSize; i++ {
			select {
			case received := <-ch1:
				g.Expect(received).To(gomega.Equal(data))
			default:
				g.Fail("data was not received from ch1")
			}
		}
		select {
		case _, ok := <-ch1:
			g.Expect(ok).To(gomega.BeFalse())
		default:
			g.Fail("data was not received from ch1")
		}
	}()
	go func() {
		defer wg.Done()
		for i := 0; i < MaxNotifierChannelSize; i++ {
			select {
			case received := <-ch2:
				g.Expect(received).To(gomega.Equal(data))
			default:
				g.Fail("data was not received from ch2")
			}
		}
		select {
		case _, ok := <-ch2:
			g.Expect(ok).To(gomega.BeFalse())
		default:
			g.Fail("data was not received from ch2")
		}
	}()

	wg.Wait()
}
