package pipebench

import (
	"io"
	"sync"
	"testing"
)

// Run with:
//   go test -bench . -benchmem -count 10 | tee results.txt
//   benchstat results.txt   # go install golang.org/x/perf/cmd/benchstat@latest

type pipe interface {
	io.ReadWriteCloser
}

var impls = []struct {
	name string
	new  func(bufCap int) pipe
}{
	{"Cond", func(c int) pipe { return newCondPipe(c) }},
	{"Chan", func(c int) pipe { return newChanPipe(c) }},
}

// drain reads until EOF.
func drain(p pipe, buf []byte) {
	for {
		if _, err := p.Read(buf); err == io.EOF {
			return
		}
	}
}

// writeN writes data n times from another goroutine, then closes.
func writeN(p pipe, data []byte, n int) {
	go func() {
		for i := 0; i < n; i++ {
			p.Write(data)
		}
		p.Close()
	}()
}

func BenchmarkSingleWriteRead(b *testing.B) {
	data, readBuf := make([]byte, 2048), make([]byte, 2048)
	for _, im := range impls {
		b.Run(im.name, func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				p := im.new(4096)
				writeN(p, data, 1)
				drain(p, readBuf)
			}
		})
	}
}

func BenchmarkTwentyWrites(b *testing.B) {
	data, readBuf := make([]byte, 512), make([]byte, 512)
	for _, im := range impls {
		b.Run(im.name, func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				p := im.new(8192)
				writeN(p, data, 20)
				drain(p, readBuf)
			}
		})
	}
}

// Small writes into a small buffer: reader and writer contend constantly.
func BenchmarkContended(b *testing.B) {
	data, readBuf := make([]byte, 64), make([]byte, 64)
	for _, im := range impls {
		b.Run(im.name, func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				p := im.new(1024)
				writeN(p, data, 100)
				drain(p, readBuf)
			}
		})
	}
}

func BenchmarkEightPipes(b *testing.B) {
	data := make([]byte, 4096)
	for _, im := range impls {
		b.Run(im.name, func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				var wg sync.WaitGroup
				for j := 0; j < 8; j++ {
					wg.Add(1)
					go func() {
						defer wg.Done()
						p := im.new(8192)
						writeN(p, data, 10)
						drain(p, make([]byte, 4096))
					}()
				}
				wg.Wait()
			}
		})
	}
}
