package web

import (
	"sync"
	"testing"
)

func TestHub_DeliversToSubscribers(t *testing.T) {
	h := newGamesHub()
	ch, done := h.subscribe("t1")
	defer done()

	h.publish("t1", hubFrame{Version: 3, Data: []byte("hi")})
	f := <-ch
	if f.Version != 3 || string(f.Data) != "hi" {
		t.Fatalf("got %+v", f)
	}
}

func TestHub_OnlyToTheRightTable(t *testing.T) {
	h := newGamesHub()
	ch1, d1 := h.subscribe("t1")
	defer d1()
	ch2, d2 := h.subscribe("t2")
	defer d2()

	h.publish("t1", hubFrame{Version: 1})
	select {
	case <-ch2:
		t.Fatal("t2 should not have received t1's frame")
	default:
	}
	if f := <-ch1; f.Version != 1 {
		t.Fatalf("t1 got %+v", f)
	}
}

// TestHub_PublishNeverBlocks is the load-bearing property: a subscriber that
// never reads must not be able to hold up a publish, because publish happens
// under the table lock and a blocked publish stalls the turn clock for everyone.
func TestHub_PublishNeverBlocks(t *testing.T) {
	h := newGamesHub()
	_, done := h.subscribe("t1") // never read from
	defer done()

	// Far more than the buffer. If any of these blocked, the test would hang.
	blocked := make(chan struct{})
	go func() {
		for i := 0; i < subChanBuffer*10; i++ {
			h.publish("t1", hubFrame{Version: int64(i)})
		}
		close(blocked)
	}()
	<-blocked
}

func TestHub_UnsubscribeStopsDelivery(t *testing.T) {
	h := newGamesHub()
	ch, done := h.subscribe("t1")
	done()

	if h.watchers("t1") != 0 {
		t.Fatalf("watchers should be 0 after unsubscribe, got %d", h.watchers("t1"))
	}
	h.publish("t1", hubFrame{Version: 1})
	select {
	case _, ok := <-ch:
		if ok {
			t.Fatal("a frame arrived after unsubscribe")
		}
	default:
	}
}

func TestHub_ConcurrentSubscribers(t *testing.T) {
	h := newGamesHub()
	var wg sync.WaitGroup
	for i := 0; i < 50; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			ch, done := h.subscribe("t1")
			defer done()
			<-ch
		}()
	}
	// Let them all register, then flood so every one of them reads at least one.
	for h.watchers("t1") < 50 {
	}
	for i := 0; i < subChanBuffer; i++ {
		h.publish("t1", hubFrame{Version: int64(i)})
	}
	wg.Wait()
}
