package yamux

import (
	"fmt"
	"io"
	"testing"
)

func BenchmarkPing(b *testing.B) {
	client, _ := testClientServer(b)

	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rtt, err := client.Ping()
		if err != nil {
			b.Fatalf("err: %v", err)
		}
		if rtt == 0 {
			b.Fatalf("bad: %v", rtt)
		}
	}
}

func BenchmarkAccept(b *testing.B) {
	client, server := testClientServer(b)

	doneCh := make(chan struct{})
	b.ReportAllocs()
	b.ResetTimer()

	go func() {
		defer close(doneCh)

		for i := 0; i < b.N; i++ {
			stream, err := server.AcceptStream()
			if err != nil {
				return
			}
			stream.Close()
		}
	}()

	for i := 0; i < b.N; i++ {
		stream, err := client.Open()
		if err != nil {
			b.Fatalf("err: %v", err)
		}
		stream.Close()
	}
	<-doneCh
}

func BenchmarkSendRecv32(b *testing.B) {
	const payloadSize = 32
	benchmarkSendRecv(b, payloadSize, payloadSize)
}

func BenchmarkSendRecv64(b *testing.B) {
	const payloadSize = 64
	benchmarkSendRecv(b, payloadSize, payloadSize)
}

func BenchmarkSendRecv128(b *testing.B) {
	const payloadSize = 128
	benchmarkSendRecv(b, payloadSize, payloadSize)
}

func BenchmarkSendRecv256(b *testing.B) {
	const payloadSize = 256
	benchmarkSendRecv(b, payloadSize, payloadSize)
}

func BenchmarkSendRecv512(b *testing.B) {
	const payloadSize = 512
	benchmarkSendRecv(b, payloadSize, payloadSize)
}

func BenchmarkSendRecv1024(b *testing.B) {
	const payloadSize = 1024
	benchmarkSendRecv(b, payloadSize, payloadSize)
}

func BenchmarkSendRecv2048(b *testing.B) {
	const payloadSize = 2048
	benchmarkSendRecv(b, payloadSize, payloadSize)
}

func BenchmarkSendRecv4096(b *testing.B) {
	const payloadSize = 4096
	benchmarkSendRecv(b, payloadSize, payloadSize)
}

func BenchmarkSendRecvLarge(b *testing.B) {
	const sendSize = 512 * 1024 * 1024 //512 MB
	const recvSize = 4 * 1024          //4 KB
	benchmarkSendRecv(b, sendSize, recvSize)
}

func benchmarkSendRecv(b *testing.B, sendSize, recvSize int) {
	client, server := testClientServer(b)

	sendBuf := make([]byte, sendSize)
	recvBuf := make([]byte, recvSize)
	errCh := make(chan error, 1)

	b.SetBytes(int64(sendSize))
	b.ReportAllocs()
	b.ResetTimer()

	go func() {
		stream, err := server.AcceptStream()
		if err != nil {
			errCh <- err
			return
		}
		defer stream.Close()

		switch {
		case sendSize == recvSize:
			for i := 0; i < b.N; i++ {
				if _, err := stream.Read(recvBuf); err != nil {
					errCh <- err
					return
				}
			}

		case recvSize > sendSize:
			errCh <- fmt.Errorf("bad test case; recvSize was: %d and sendSize was: %d, but recvSize must be <= sendSize!", recvSize, sendSize)
			return

		default:
			chunks := sendSize / recvSize
			for i := 0; i < b.N; i++ {
				for j := 0; j < chunks; j++ {
					if _, err := stream.Read(recvBuf); err != nil {
						errCh <- err
						return
					}
				}
			}
		}
		errCh <- nil
	}()

	stream, err := client.Open()
	if err != nil {
		b.Fatalf("err: %v", err)
	}
	defer stream.Close()

	for i := 0; i < b.N; i++ {
		if _, err := stream.Write(sendBuf); err != nil {
			b.Fatalf("err: %v", err)
		}
	}

	drainErrorsUntil(b, errCh, 1, 0, "")
}

func BenchmarkSendRecvParallel32(b *testing.B) {
	const payloadSize = 32
	benchmarkSendRecvParallel(b, payloadSize)
}

func BenchmarkSendRecvParallel64(b *testing.B) {
	const payloadSize = 64
	benchmarkSendRecvParallel(b, payloadSize)
}

func BenchmarkSendRecvParallel128(b *testing.B) {
	const payloadSize = 128
	benchmarkSendRecvParallel(b, payloadSize)
}

func BenchmarkSendRecvParallel256(b *testing.B) {
	const payloadSize = 256
	benchmarkSendRecvParallel(b, payloadSize)
}

func BenchmarkSendRecvParallel512(b *testing.B) {
	const payloadSize = 512
	benchmarkSendRecvParallel(b, payloadSize)
}

func BenchmarkSendRecvParallel1024(b *testing.B) {
	const payloadSize = 1024
	benchmarkSendRecvParallel(b, payloadSize)
}

func BenchmarkSendRecvParallel2048(b *testing.B) {
	const payloadSize = 2048
	benchmarkSendRecvParallel(b, payloadSize)
}

func BenchmarkSendRecvParallel4096(b *testing.B) {
	const payloadSize = 4096
	benchmarkSendRecvParallel(b, payloadSize)
}

func benchmarkSendRecvParallel(b *testing.B, sendSize int) {
	client, server := testClientServer(b)

	sendBuf := make([]byte, sendSize)
	discarder := io.Discard.(io.ReaderFrom)
	b.SetBytes(int64(sendSize))
	b.ReportAllocs()
	b.ResetTimer()

	b.RunParallel(func(pb *testing.PB) {
		errCh := make(chan error, 1)
		go func() {
			stream, err := server.AcceptStream()
			if err != nil {
				errCh <- err
				return
			}
			defer stream.Close()

			if _, err := discarder.ReadFrom(stream); err != nil {
				errCh <- err
				return
			}
			errCh <- nil
		}()

		stream, err := client.Open()
		if err != nil {
			b.Fatalf("err: %v", err)
		}

		for pb.Next() {
			if _, err := stream.Write(sendBuf); err != nil {
				b.Fatalf("err: %v", err)
			}
		}

		stream.Close()

		drainErrorsUntil(b, errCh, 1, 0, "")
	})
}
