package main

import (
	"sync"
	"testing"
)

// A writer with no stream goroutine: enqueue() runs its supersede policy
// and the queue just accumulates, which is all these tests observe.
func testFrameWriter() *frameWriter {
	writer := &frameWriter{ack: func(int) {}, aborted: make(map[uint32]bool)}
	writer.cond = sync.NewCond(&writer.mu)
	return writer
}

// The relay hands a frame over as several 16 KiB fragments.
func fragments(frame uint32, count int) []frameMsg {
	out := make([]frameMsg, 0, count)
	for index := 0; index < count; index++ {
		out = append(out, frameMsg{data: make([]byte, 16), frame: frame,
			newFrame: index == 0, last: index == count-1})
	}
	return out
}

// The pressure report compares queue drops with opened FRAMES, so the drop
// counter must count frames too. Counted per fragment, two dropped frames
// (8 entries) out of thirteen offered read as 8 of 21 and tripped the
// overload test on a path delivering 85% of frames.
func TestQueueDropsCountFramesNotFragments(t *testing.T) {
	writer := testFrameWriter()
	for _, frame := range []uint32{1, 2} {
		for _, msg := range fragments(frame, 4) {
			writer.enqueue(msg)
		}
	}
	writer.enqueue(fragments(3, 4)[0])

	if writer.queueDropped != 2 {
		t.Fatalf("queueDropped=%d, want 2 frames", writer.queueDropped)
	}
	if len(writer.queue) != 1 || writer.queue[0].frame != 3 {
		t.Fatalf("queue=%+v, want only frame 3", writer.queue)
	}
	for _, frame := range []uint32{1, 2} {
		if !writer.aborted[frame] {
			t.Fatalf("frame %d not marked aborted", frame)
		}
	}
}

// The relay hears about drops the moment they happen, as the lifetime
// total, once per supersede pass.
func TestQueueDropsAreReportedAtOnce(t *testing.T) {
	writer := testFrameWriter()
	var reported []uint64
	writer.onDrop = func(total uint64) { reported = append(reported, total) }
	for _, msg := range fragments(1, 3) {
		writer.enqueue(msg)
	}
	if len(reported) != 0 {
		t.Fatalf("nothing dropped yet, reported %v", reported)
	}
	// Frame 2 supersedes the unstarted frame 1 (no writer goroutine here, so
	// nothing is ever "current"): one report, total 1. Its later fragments
	// are not a supersede pass and report nothing.
	for _, msg := range fragments(2, 3) {
		writer.enqueue(msg)
	}
	if len(reported) != 1 || reported[0] != 1 {
		t.Fatalf("reported %v, want [1]", reported)
	}
	writer.enqueue(fragments(3, 3)[0])
	if len(reported) != 2 || reported[1] != 2 {
		t.Fatalf("reported %v, want [1 2]", reported)
	}
}

// With a delivered rate known, a short backlog is carried, not superseded:
// three 16-byte fragments per frame at 10 Mbps are microseconds of
// delivery time. Only a backlog past the time budget goes latest-wins.
func TestShortBacklogIsCarriedAtTheDeliveredRate(t *testing.T) {
	writer := testFrameWriter()
	rate := int64(10_000_000)
	writer.deliveredBps = func() int64 { return rate }
	for _, frame := range []uint32{1, 2, 3} {
		for _, msg := range fragments(frame, 3) {
			writer.enqueue(msg)
		}
	}
	if writer.queueDropped != 0 || len(writer.queue) != 9 {
		t.Fatalf("short backlog superseded: dropped=%d queue=%d",
			writer.queueDropped, len(writer.queue))
	}
	if writer.queuedBytes != 9*16 {
		t.Fatalf("queuedBytes=%d, want %d", writer.queuedBytes, 9*16)
	}
	// The same backlog at 1 kbit/s is over a second of delivery: frame 4
	// supersedes every unstarted frame before it.
	rate = 1000
	writer.enqueue(fragments(4, 3)[0])
	if writer.queueDropped != 3 || len(writer.queue) != 1 {
		t.Fatalf("long backlog kept: dropped=%d queue=%d",
			writer.queueDropped, len(writer.queue))
	}
	if writer.queuedBytes != 16 {
		t.Fatalf("queuedBytes=%d after the supersede, want 16",
			writer.queuedBytes)
	}
}

// Reliable frames (keyframes, anchors) and the frame currently on the wire
// are never dropped by a newer arrival.
func TestQueueDropsSpareReliableAndCurrentFrames(t *testing.T) {
	writer := testFrameWriter()
	writer.curFrame = 5
	writer.enqueue(frameMsg{data: make([]byte, 16), frame: 4, reliable: true,
		newFrame: true, last: true})
	writer.enqueue(fragments(5, 2)[1])
	writer.enqueue(fragments(6, 1)[0])

	if writer.queueDropped != 0 {
		t.Fatalf("queueDropped=%d, want 0", writer.queueDropped)
	}
	if len(writer.queue) != 3 {
		t.Fatalf("queue has %d entries, want 3", len(writer.queue))
	}
}
