package congestion

import (
	

	
	
	
	
	
)

const (
	// maxDatagramSize is the default maximum packet size used in the Linux TCP implementation.
	// Used in QUIC for congestion window computations in bytes.
	initialMaxDatagramSize     = protocol.ByteCount(protocol.InitialPacketSize)
	maxBurstPackets            = 3
	renoBeta                   = 0.7 // Reno backoff factor.
	minCongestionWindowPackets = 2
	initialCongestionWindow    = 32
)

type cubicSender struct {
	hybridSlowStart HybridSlowStart
	rttStats        *utils.RTTStats
	connStats       *utils.ConnectionStats
	cubic           *Cubic
	pacer           *pacer
	clock           Clock

	reno bool

	// Track the largest packet that has been sent.
	largestSentPacketNumber protocol.PacketNumber

	// Track the largest packet that has been acked.
	largestAckedPacketNumber protocol.PacketNumber

	// Track the largest packet number outstanding when a CWND cutback occurs.
	largestSentAtLastCutback protocol.PacketNumber

	// Whether the last loss event caused us to exit slowstart.
	// Used for stats collection of slowstartPacketsLost
	lastCutbackExitedSlowstart bool

	// Congestion window in bytes.
	congestionWindow protocol.ByteCount

	// Slow start congestion window in bytes, aka ssthresh.
	slowStartThreshold protocol.ByteCount

	// ACK counter for the Reno implementation.
	numAckedPackets uint64

	initialCongestionWindow    protocol.ByteCount
	initialMaxCongestionWindow protocol.ByteCount

	maxDatagramSize protocol.ByteCount

	lastState qlog.CongestionState
	qlogger   qlogwriter.Recorder
}

var (
	_ SendAlgorithm               = &cubicSender{}
	_ SendAlgorithmWithDebugInfos = &cubicSender{}
)

// NewCubicSender makes a new cubic sender
func (
	 Clock,
	 *utils.RTTStats,
	 *utils.ConnectionStats,
	 protocol.ByteCount,
	 bool,
	 qlogwriter.Recorder,
) *cubicSender {
	return newCubicSender(
		,
		,
		,
		,
		,
		initialCongestionWindow*,
		protocol.MaxCongestionWindowPackets*,
		,
	)
}

func newCubicSender(
	 Clock,
	 *utils.RTTStats,
	 *utils.ConnectionStats,
	 bool,
	,
	,
	 protocol.ByteCount,
	 qlogwriter.Recorder,
) *cubicSender {
	 := &cubicSender{
		rttStats:                   ,
		connStats:                  ,
		largestSentPacketNumber:    protocol.InvalidPacketNumber,
		largestAckedPacketNumber:   protocol.InvalidPacketNumber,
		largestSentAtLastCutback:   protocol.InvalidPacketNumber,
		initialCongestionWindow:    ,
		initialMaxCongestionWindow: ,
		congestionWindow:           ,
		slowStartThreshold:         protocol.MaxByteCount,
		cubic:                      NewCubic(),
		clock:                      ,
		reno:                       ,
		qlogger:                    ,
		maxDatagramSize:            ,
	}
	.pacer = newPacer(.BandwidthEstimate)
	if .qlogger != nil {
		.lastState = qlog.CongestionStateSlowStart
		.qlogger.RecordEvent(qlog.CongestionStateUpdated{
			State: qlog.CongestionStateSlowStart,
		})
	}
	return 
}

// TimeUntilSend returns when the next packet should be sent.
func ( *cubicSender) ( protocol.ByteCount) monotime.Time {
	return .pacer.TimeUntilSend()
}

func ( *cubicSender) ( monotime.Time) bool {
	return .pacer.Budget() >= .maxDatagramSize
}

func ( *cubicSender) () protocol.ByteCount {
	return .maxDatagramSize * protocol.MaxCongestionWindowPackets
}

func ( *cubicSender) () protocol.ByteCount {
	return .maxDatagramSize * minCongestionWindowPackets
}

func ( *cubicSender) (
	 monotime.Time,
	 protocol.ByteCount,
	 protocol.PacketNumber,
	 protocol.ByteCount,
	 bool,
) {
	.pacer.SentPacket(, )
	if ! {
		return
	}
	.largestSentPacketNumber = 
	.hybridSlowStart.OnPacketSent()
}

func ( *cubicSender) ( protocol.ByteCount) bool {
	return  < .GetCongestionWindow()
}

func ( *cubicSender) () bool {
	return .largestAckedPacketNumber != protocol.InvalidPacketNumber && .largestAckedPacketNumber <= .largestSentAtLastCutback
}

func ( *cubicSender) () bool {
	return .GetCongestionWindow() < .slowStartThreshold
}

func ( *cubicSender) () protocol.ByteCount {
	return .congestionWindow
}

func ( *cubicSender) () {
	if .InSlowStart() &&
		.hybridSlowStart.ShouldExitSlowStart(.rttStats.LatestRTT(), .rttStats.MinRTT(), .GetCongestionWindow()/.maxDatagramSize) {
		// exit slow start
		.slowStartThreshold = .congestionWindow
		.maybeQlogStateChange(qlog.CongestionStateCongestionAvoidance)
	}
}

func ( *cubicSender) (
	 protocol.PacketNumber,
	 protocol.ByteCount,
	 protocol.ByteCount,
	 monotime.Time,
) {
	.largestAckedPacketNumber = max(, .largestAckedPacketNumber)
	if .InRecovery() {
		return
	}
	.maybeIncreaseCwnd(, , , )
	if .InSlowStart() {
		.hybridSlowStart.OnPacketAcked()
	}
}

func ( *cubicSender) ( protocol.PacketNumber, ,  protocol.ByteCount) {
	.connStats.PacketsLost.Add(1)
	.connStats.BytesLost.Add(uint64())

	// TCP NewReno (RFC6582) says that once a loss occurs, any losses in packets
	// already sent should be treated as a single loss event, since it's expected.
	if  <= .largestSentAtLastCutback {
		return
	}
	.lastCutbackExitedSlowstart = .InSlowStart()
	.maybeQlogStateChange(qlog.CongestionStateRecovery)

	if .reno {
		.congestionWindow = protocol.ByteCount(float64(.congestionWindow) * renoBeta)
	} else {
		.congestionWindow = .cubic.CongestionWindowAfterPacketLoss(.congestionWindow)
	}
	if  := .minCongestionWindow(); .congestionWindow <  {
		.congestionWindow = 
	}
	.slowStartThreshold = .congestionWindow
	.largestSentAtLastCutback = .largestSentPacketNumber
	// reset packet count from congestion avoidance mode. We start
	// counting again when we're out of recovery.
	.numAckedPackets = 0
}

// Called when we receive an ack. Normal TCP tracks how many packets one ack
// represents, but quic has a separate ack for each packet.
func ( *cubicSender) (
	 protocol.PacketNumber,
	 protocol.ByteCount,
	 protocol.ByteCount,
	 monotime.Time,
) {
	// Do not increase the congestion window unless the sender is close to using
	// the current window.
	if !.isCwndLimited() {
		.cubic.OnApplicationLimited()
		.maybeQlogStateChange(qlog.CongestionStateApplicationLimited)
		return
	}
	if .congestionWindow >= .maxCongestionWindow() {
		return
	}
	if .InSlowStart() {
		// TCP slow start, exponential growth, increase by one for each ACK.
		.congestionWindow += .maxDatagramSize
		.maybeQlogStateChange(qlog.CongestionStateSlowStart)
		return
	}
	// Congestion avoidance
	.maybeQlogStateChange(qlog.CongestionStateCongestionAvoidance)
	if .reno {
		// Classic Reno congestion avoidance.
		.numAckedPackets++
		if .numAckedPackets >= uint64(.congestionWindow/.maxDatagramSize) {
			.congestionWindow += .maxDatagramSize
			.numAckedPackets = 0
		}
	} else {
		.congestionWindow = min(
			.maxCongestionWindow(),
			.cubic.CongestionWindowAfterAck(, .congestionWindow, .rttStats.MinRTT(), ),
		)
	}
}

func ( *cubicSender) ( protocol.ByteCount) bool {
	 := .GetCongestionWindow()
	if  >=  {
		return true
	}
	 :=  - 
	 := .InSlowStart() &&  > /2
	return  ||  <= maxBurstPackets*.maxDatagramSize
}

// BandwidthEstimate returns the current bandwidth estimate
func ( *cubicSender) () Bandwidth {
	 := .rttStats.SmoothedRTT()
	if  == 0 {
		// This should never happen, but if it does, avoid division by zero.
		 = protocol.TimerGranularity
	}
	return BandwidthFromDelta(.GetCongestionWindow(), )
}

// OnRetransmissionTimeout is called on an retransmission timeout
func ( *cubicSender) ( bool) {
	.largestSentAtLastCutback = protocol.InvalidPacketNumber
	if ! {
		return
	}
	.hybridSlowStart.Restart()
	.cubic.Reset()
	.slowStartThreshold = .congestionWindow / 2
	.congestionWindow = .minCongestionWindow()
}

// OnConnectionMigration is called when the connection is migrated (?)
func ( *cubicSender) () {
	.hybridSlowStart.Restart()
	.largestSentPacketNumber = protocol.InvalidPacketNumber
	.largestAckedPacketNumber = protocol.InvalidPacketNumber
	.largestSentAtLastCutback = protocol.InvalidPacketNumber
	.lastCutbackExitedSlowstart = false
	.cubic.Reset()
	.numAckedPackets = 0
	.congestionWindow = .initialCongestionWindow
	.slowStartThreshold = .initialMaxCongestionWindow
}

func ( *cubicSender) ( qlog.CongestionState) {
	if .qlogger == nil ||  == .lastState {
		return
	}
	.qlogger.RecordEvent(qlog.CongestionStateUpdated{State: })
	.lastState = 
}

func ( *cubicSender) ( protocol.ByteCount) {
	if  < .maxDatagramSize {
		panic(fmt.Sprintf("congestion BUG: decreased max datagram size from %d to %d", .maxDatagramSize, ))
	}
	 := .congestionWindow == .minCongestionWindow()
	.maxDatagramSize = 
	if  {
		.congestionWindow = .minCongestionWindow()
	}
	.pacer.SetMaxDatagramSize()
}