// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package quic

import (
	"context"
	"fmt"
	"io"
	"math"
	"sync"
	"testing"
)

// BenchmarkThroughput is based on the crypto/tls benchmark of the same name.
func BenchmarkThroughput(b *testing.B) {
	for size := 1; size <= 64; size <<= 1 {
		name := fmt.Sprintf("%dMiB", size)
		b.Run(name, func(b *testing.B) {
			throughput(b, int64(size<<20))
		})
	}
}

func throughput(b *testing.B, totalBytes int64) {
	// Same buffer size as crypto/tls's BenchmarkThroughput, for consistency.
	const bufsize = 32 << 10

	cli, srv := newLocalConnPair(b, &Config{}, &Config{})

	var wg sync.WaitGroup
	defer wg.Wait()
	wg.Go(func() {
		buf := make([]byte, bufsize)
		for i := 0; i < b.N; i++ {
			sconn, err := srv.AcceptStream(context.Background())
			if err != nil {
				panic(fmt.Errorf("AcceptStream: %v", err))
			}
			if _, err := io.CopyBuffer(sconn, sconn, buf); err != nil {
				panic(fmt.Errorf("CopyBuffer: %v", err))
			}
			sconn.Close()
		}
	})

	b.SetBytes(totalBytes)
	buf := make([]byte, bufsize)
	chunks := int(math.Ceil(float64(totalBytes) / float64(len(buf))))
	for i := 0; i < b.N; i++ {
		cconn, err := cli.NewStream(context.Background())
		if err != nil {
			b.Fatalf("NewStream: %v", err)
		}
		closec := make(chan struct{})
		go func() {
			defer close(closec)
			buf := make([]byte, bufsize)
			if _, err := io.CopyBuffer(io.Discard, cconn, buf); err != nil {
				panic(fmt.Errorf("Discard: %v", err))
			}
		}()
		for j := 0; j < chunks; j++ {
			_, err := cconn.Write(buf)
			if err != nil {
				b.Fatalf("Write: %v", err)
			}
		}
		cconn.CloseWrite()
		<-closec
		cconn.Close()
	}
}

func BenchmarkReadByte(b *testing.B) {
	cli, srv := newLocalConnPair(b, &Config{}, &Config{})

	var wg sync.WaitGroup
	defer wg.Wait()

	wg.Add(1)
	go func() {
		defer wg.Done()
		buf := make([]byte, 1<<20)
		sconn, err := srv.AcceptStream(context.Background())
		if err != nil {
			panic(fmt.Errorf("AcceptStream: %v", err))
		}
		for {
			if _, err := sconn.Write(buf); err != nil {
				break
			}
			sconn.Flush()
		}
	}()

	b.SetBytes(1)
	cconn, err := cli.NewStream(context.Background())
	if err != nil {
		b.Fatalf("NewStream: %v", err)
	}
	cconn.Flush()
	for i := 0; i < b.N; i++ {
		_, err := cconn.ReadByte()
		if err != nil {
			b.Fatalf("ReadByte: %v", err)
		}
	}
	cconn.Close()
}

func BenchmarkWriteByte(b *testing.B) {
	cli, srv := newLocalConnPair(b, &Config{}, &Config{})

	var wg sync.WaitGroup
	defer wg.Wait()

	wg.Add(1)
	go func() {
		defer wg.Done()
		sconn, err := srv.AcceptStream(context.Background())
		if err != nil {
			panic(fmt.Errorf("AcceptStream: %v", err))
		}
		n, err := io.Copy(io.Discard, sconn)
		if n != int64(b.N) || err != nil {
			b.Errorf("server io.Copy() = %v, %v; want %v, nil", n, err, b.N)
		}
	}()

	b.SetBytes(1)
	cconn, err := cli.NewStream(context.Background())
	if err != nil {
		b.Fatalf("NewStream: %v", err)
	}
	cconn.Flush()
	for i := 0; i < b.N; i++ {
		if err := cconn.WriteByte(0); err != nil {
			b.Fatalf("WriteByte: %v", err)
		}
	}
	cconn.Close()
}

func BenchmarkStreamCreation(b *testing.B) {
	cli, srv := newLocalConnPair(b, &Config{}, &Config{})

	var wg sync.WaitGroup
	defer wg.Wait()
	wg.Go(func() {
		for i := 0; i < b.N; i++ {
			sconn, err := srv.AcceptStream(context.Background())
			if err != nil {
				panic(fmt.Errorf("AcceptStream: %v", err))
			}
			sconn.Close()
		}
	})

	buf := make([]byte, 1)
	for i := 0; i < b.N; i++ {
		cconn, err := cli.NewStream(context.Background())
		if err != nil {
			b.Fatalf("NewStream: %v", err)
		}
		cconn.Write(buf)
		cconn.Flush()
		cconn.Read(buf)
		cconn.Close()
	}
}
