/*
 * Copyright 2022 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package grpc

import (
	"testing"
	"time"

	"github.com/cloudwego/kitex/internal/test"
)

func TestBdp(t *testing.T) {
	oribdpPingInterval := bdpPingInterval
	defer func() { bdpPingInterval = oribdpPingInterval }()
	bdpPingInterval = 10 * time.Millisecond // shorter time for testing

	// init bdp estimator
	bdpEst := &bdpEstimator{
		bdp:               initialWindowSize,
		updateFlowControl: func(n uint32) {},
	}
	size := 100

	// mock data frame receive
	sent := bdpEst.add(uint32(size))
	test.Assert(t, sent)
	bdpEst.timesnap(bdpPing.data)

	// receive normal ping
	bdpEst.timesnap([8]byte{0, 0, 0, 0, 0, 0, 0, 0})

	// mock receiving data while bdp ping haven't been acked
	for i := 0; i < 3; i++ {
		sent = bdpEst.add(uint32(size))
		test.Assert(t, !sent)
	}

	// now bdp estimator receive ping ack and do calculation
	bdpEst.calculate(bdpPing.data)

	// receive normal ping
	bdpEst.calculate([8]byte{0, 0, 0, 0, 0, 0, 0, 0})

	test.Assert(t, false == bdpEst.add(0)) // due to bdpPingInterval
	time.Sleep(2 * bdpPingInterval)

	size = 10000
	// calculate 15 times
	for c := 0; c < 15; c++ {
		sent = bdpEst.add(uint32(size))
		test.Assert(t, sent, c)
		bdpEst.timesnap(bdpPing.data)

		// mock the situation that network delay is very long and data is very big
		for i := 0; i < 15; i++ {
			sent = bdpEst.add(uint32(size))
			test.Assert(t, !sent)
		}

		// receive bdp ack and calculate again
		bdpEst.calculate(bdpPing.data)
		bdpEst.sentAt = time.Time{} // reset for bdpPingInterval
	}
}
