package kcp

import (
	"sync"
)

type SendingWindow struct {
	start uint32
	cap   uint32
	len   uint32
	last  uint32

	data  []DataSegment
	inuse []bool
	prev  []uint32
	next  []uint32

	totalInFlightSize uint32
	writer            SegmentWriter
	onPacketLoss      func(uint32)
}

func NewSendingWindow(size uint32, writer SegmentWriter, onPacketLoss func(uint32)) *SendingWindow {
	window := &SendingWindow{
		start:        0,
		cap:          size,
		len:          0,
		last:         0,
		data:         make([]DataSegment, size),
		prev:         make([]uint32, size),
		next:         make([]uint32, size),
		inuse:        make([]bool, size),
		writer:       writer,
		onPacketLoss: onPacketLoss,
	}
	return window
}

func (v *SendingWindow) Release() {
	if v == nil {
		return
	}
	v.len = 0
	for _, seg := range v.data {
		seg.Release()
	}
}

func (v *SendingWindow) Len() int {
	return int(v.len)
}

func (v *SendingWindow) IsEmpty() bool {
	return v.len == 0
}

func (v *SendingWindow) Size() uint32 {
	return v.cap
}

func (v *SendingWindow) IsFull() bool {
	return v.len == v.cap
}

func (v *SendingWindow) Push(number uint32, data []byte) {
	pos := (v.start + v.len) % v.cap
	v.data[pos].SetData(data)
	v.data[pos].Number = number
	v.data[pos].timeout = 0
	v.data[pos].transmit = 0
	v.inuse[pos] = true
	if v.len > 0 {
		v.next[v.last] = pos
		v.prev[pos] = v.last
	}
	v.last = pos
	v.len++
}

func (v *SendingWindow) FirstNumber() uint32 {
	return v.data[v.start].Number
}

func (v *SendingWindow) Clear(una uint32) {
	for !v.IsEmpty() && v.data[v.start].Number < una {
		v.Remove(0)
	}
}

func (v *SendingWindow) Remove(idx uint32) bool {
	if v.IsEmpty() {
		return false
	}

	pos := (v.start + idx) % v.cap
	if !v.inuse[pos] {
		return false
	}
	v.inuse[pos] = false
	v.totalInFlightSize--
	if pos == v.start && pos == v.last {
		v.len = 0
		v.start = 0
		v.last = 0
	} else if pos == v.start {
		delta := v.next[pos] - v.start
		if v.next[pos] < v.start {
			delta = v.next[pos] + v.cap - v.start
		}
		v.start = v.next[pos]
		v.len -= delta
	} else if pos == v.last {
		v.last = v.prev[pos]
	} else {
		v.next[v.prev[pos]] = v.next[pos]
		v.prev[v.next[pos]] = v.prev[pos]
	}
	return true
}

func (v *SendingWindow) HandleFastAck(number uint32, rto uint32) {
	if v.IsEmpty() {
		return
	}

	v.Visit(func(seg *DataSegment) bool {
		if number == seg.Number || number-seg.Number > 0x7FFFFFFF {
			return false
		}

		if seg.transmit > 0 && seg.timeout > rto/3 {
			seg.timeout -= rto / 3
		}
		return true
	})
}

func (v *SendingWindow) Visit(visitor func(seg *DataSegment) bool) {
	if v.IsEmpty() {
		return
	}

	for i := v.start; ; i = v.next[i] {
		if !visitor(&v.data[i]) || i == v.last {
			break
		}
	}
}

func (v *SendingWindow) Flush(current uint32, rto uint32, maxInFlightSize uint32) {
	if v.IsEmpty() {
		return
	}

	var lost uint32
	var inFlightSize uint32

	v.Visit(func(segment *DataSegment) bool {
		if current-segment.timeout >= 0x7FFFFFFF {
			return true
		}
		if segment.transmit == 0 {
			// First time
			v.totalInFlightSize++
		} else {
			lost++
		}
		segment.timeout = current + rto

		segment.Timestamp = current
		segment.transmit++
		v.writer.Write(segment)
		inFlightSize++
		if inFlightSize >= maxInFlightSize {
			return false
		}
		return true
	})

	if v.onPacketLoss != nil && inFlightSize > 0 && v.totalInFlightSize != 0 {
		rate := lost * 100 / v.totalInFlightSize
		v.onPacketLoss(rate)
	}
}

type SendingWorker struct {
	sync.RWMutex
	conn                       *Connection
	window                     *SendingWindow
	firstUnacknowledged        uint32
	firstUnacknowledgedUpdated bool
	nextNumber                 uint32
	remoteNextNumber           uint32
	controlWindow              uint32
	fastResend                 uint32
}

func NewSendingWorker(kcp *Connection) *SendingWorker {
	worker := &SendingWorker{
		conn:             kcp,
		fastResend:       2,
		remoteNextNumber: 32,
		controlWindow:    kcp.Config.GetSendingInFlightSize(),
	}
	worker.window = NewSendingWindow(kcp.Config.GetSendingBufferSize(), worker, worker.OnPacketLoss)
	return worker
}

func (v *SendingWorker) Release() {
	v.Lock()
	v.window.Release()
	v.Unlock()
}

func (v *SendingWorker) ProcessReceivingNext(nextNumber uint32) {
	v.Lock()
	defer v.Unlock()

	v.ProcessReceivingNextWithoutLock(nextNumber)
}

func (v *SendingWorker) ProcessReceivingNextWithoutLock(nextNumber uint32) {
	v.window.Clear(nextNumber)
	v.FindFirstUnacknowledged()
}

// Private: Visible for testing.
func (v *SendingWorker) FindFirstUnacknowledged() {
	first := v.firstUnacknowledged
	if !v.window.IsEmpty() {
		v.firstUnacknowledged = v.window.FirstNumber()
	} else {
		v.firstUnacknowledged = v.nextNumber
	}
	if first != v.firstUnacknowledged {
		v.firstUnacknowledgedUpdated = true
	}
}

func (v *SendingWorker) processAck(number uint32) bool {
	// number < v.firstUnacknowledged || number >= v.nextNumber
	if number-v.firstUnacknowledged > 0x7FFFFFFF || number-v.nextNumber < 0x7FFFFFFF {
		return false
	}

	removed := v.window.Remove(number - v.firstUnacknowledged)
	if removed {
		v.FindFirstUnacknowledged()
	}
	return removed
}

func (v *SendingWorker) ProcessSegment(current uint32, seg *AckSegment, rto uint32) {
	defer seg.Release()

	v.Lock()
	defer v.Unlock()

	if v.remoteNextNumber < seg.ReceivingWindow {
		v.remoteNextNumber = seg.ReceivingWindow
	}
	v.ProcessReceivingNextWithoutLock(seg.ReceivingNext)

	if seg.IsEmpty() {
		return
	}

	var maxack uint32
	var maxackRemoved bool
	for _, number := range seg.NumberList {
		removed := v.processAck(number)
		if maxack < number {
			maxack = number
			maxackRemoved = removed
		}
	}

	if maxackRemoved {
		v.window.HandleFastAck(maxack, rto)
		if current-seg.Timestamp < 10000 {
			v.conn.roundTrip.Update(current-seg.Timestamp, current)
		}
	}
}

func (v *SendingWorker) Push(b []byte) int {
	nBytes := 0
	v.Lock()
	defer v.Unlock()

	for len(b) > 0 && !v.window.IsFull() {
		var size int
		if len(b) > int(v.conn.mss) {
			size = int(v.conn.mss)
		} else {
			size = len(b)
		}
		v.window.Push(v.nextNumber, b[:size])
		v.nextNumber++
		b = b[size:]
		nBytes += size
	}
	return nBytes
}

// Private: Visible for testing.
func (v *SendingWorker) Write(seg Segment) error {
	dataSeg := seg.(*DataSegment)

	dataSeg.Conv = v.conn.conv
	dataSeg.SendingNext = v.firstUnacknowledged
	dataSeg.Option = 0
	if v.conn.State() == StateReadyToClose {
		dataSeg.Option = SegmentOptionClose
	}

	return v.conn.output.Write(dataSeg)
}

func (v *SendingWorker) OnPacketLoss(lossRate uint32) {
	if !v.conn.Config.Congestion || v.conn.roundTrip.Timeout() == 0 {
		return
	}

	if lossRate >= 15 {
		v.controlWindow = 3 * v.controlWindow / 4
	} else if lossRate <= 5 {
		v.controlWindow += v.controlWindow / 4
	}
	if v.controlWindow < 16 {
		v.controlWindow = 16
	}
	if v.controlWindow > 2*v.conn.Config.GetSendingInFlightSize() {
		v.controlWindow = 2 * v.conn.Config.GetSendingInFlightSize()
	}
}

func (v *SendingWorker) Flush(current uint32) {
	v.Lock()

	cwnd := v.firstUnacknowledged + v.conn.Config.GetSendingInFlightSize()
	if cwnd > v.remoteNextNumber {
		cwnd = v.remoteNextNumber
	}
	if v.conn.Config.Congestion && cwnd > v.firstUnacknowledged+v.controlWindow {
		cwnd = v.firstUnacknowledged + v.controlWindow
	}

	if !v.window.IsEmpty() {
		v.window.Flush(current, v.conn.roundTrip.Timeout(), cwnd)
		v.firstUnacknowledgedUpdated = false
	}

	updated := v.firstUnacknowledgedUpdated
	v.firstUnacknowledgedUpdated = false

	v.Unlock()

	if updated {
		v.conn.Ping(current, CommandPing)
	}
}

func (v *SendingWorker) CloseWrite() {
	v.Lock()
	defer v.Unlock()

	v.window.Clear(0xFFFFFFFF)
}

func (v *SendingWorker) IsEmpty() bool {
	v.RLock()
	defer v.RUnlock()

	return v.window.IsEmpty()
}

func (v *SendingWorker) UpdateNecessary() bool {
	return !v.IsEmpty()
}

func (w *SendingWorker) FirstUnacknowledged() uint32 {
	w.RLock()
	defer w.RUnlock()

	return w.firstUnacknowledged
}
