// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package ice

import (
	
	
	
	

	
)

type udpMuxedConnState int

const (
	udpMuxedConnOpen udpMuxedConnState = iota
	udpMuxedConnWaiting
	udpMuxedConnClosed
)

type udpMuxedConnParams struct {
	Mux       *UDPMuxDefault
	AddrPool  *sync.Pool
	Key       string
	LocalAddr net.Addr
	Logger    logging.LeveledLogger
}

// udpMuxedConn represents a logical packet conn for a single remote as identified by ufrag.
type udpMuxedConn struct {
	params *udpMuxedConnParams
	// Remote addresses that we have sent to on this conn
	addresses []ipPort

	// FIFO queue holding incoming packets
	bufHead, bufTail *bufferHolder
	notify           chan struct{}
	closedChan       chan struct{}
	state            udpMuxedConnState
	mu               sync.Mutex
}

func newUDPMuxedConn( *udpMuxedConnParams) *udpMuxedConn {
	return &udpMuxedConn{
		params:     ,
		notify:     make(chan struct{}, 1),
		closedChan: make(chan struct{}),
	}
}

func ( *udpMuxedConn) ( []byte) ( int,  net.Addr,  error) {
	for {
		.mu.Lock()
		if .bufTail != nil {
			 := .bufTail
			.bufTail = .next

			if  == .bufHead {
				.bufHead = nil
			}
			.mu.Unlock()

			if len() < len(.buf) {
				 = io.ErrShortBuffer
			} else {
				 = copy(, .buf)
				 = .addr
			}

			.reset()
			.params.AddrPool.Put()

			return , , 
		}

		if .state == udpMuxedConnClosed {
			.mu.Unlock()

			return 0, nil, io.EOF
		}

		.state = udpMuxedConnWaiting
		.mu.Unlock()

		select {
		case <-.notify:
		case <-.closedChan:
			return 0, nil, io.EOF
		}
	}
}

func ( *udpMuxedConn) ( []byte,  net.Addr) ( int,  error) {
	if .isClosed() {
		return 0, io.ErrClosedPipe
	}
	// Each time we write to a new address, we'll register it with the mux
	,  := .(*net.UDPAddr)
	if ! {
		return 0, errFailedToCastUDPAddr
	}

	//nolint:gosec // TODO add port validation G115
	,  := newIPPort(.IP, .Zone, uint16(.Port))
	if  != nil {
		return 0, 
	}
	if !.containsAddress() {
		.addAddress()
	}

	return .params.Mux.writeTo(, )
}

func ( *udpMuxedConn) () net.Addr {
	return .params.LocalAddr
}

func ( *udpMuxedConn) (time.Time) error {
	return nil
}

func ( *udpMuxedConn) (time.Time) error {
	return nil
}

func ( *udpMuxedConn) (time.Time) error {
	return nil
}

func ( *udpMuxedConn) () <-chan struct{} {
	return .closedChan
}

func ( *udpMuxedConn) () error {
	.mu.Lock()
	defer .mu.Unlock()
	if .state != udpMuxedConnClosed {
		for  := .bufTail;  != nil; {
			 := .next

			.reset()
			.params.AddrPool.Put()

			 = 
		}
		.bufHead = nil
		.bufTail = nil

		.state = udpMuxedConnClosed
		close(.closedChan)
	}

	return nil
}

func ( *udpMuxedConn) () bool {
	.mu.Lock()
	defer .mu.Unlock()

	return .state == udpMuxedConnClosed
}

func ( *udpMuxedConn) () []ipPort {
	.mu.Lock()
	defer .mu.Unlock()
	 := make([]ipPort, len(.addresses))
	copy(, .addresses)

	return 
}

func ( *udpMuxedConn) ( ipPort) {
	.mu.Lock()
	.addresses = append(.addresses, )
	.mu.Unlock()

	// Map it on mux
	.params.Mux.registerConnForAddress(, )
}

func ( *udpMuxedConn) ( ipPort) {
	.mu.Lock()
	defer .mu.Unlock()

	 := make([]ipPort, 0, len(.addresses))
	for ,  := range .addresses {
		if  !=  {
			 = append(, )
		}
	}

	.addresses = 
}

func ( *udpMuxedConn) ( ipPort) bool {
	.mu.Lock()
	defer .mu.Unlock()
	for ,  := range .addresses {
		if  ==  {
			return true
		}
	}

	return false
}

func ( *udpMuxedConn) ( []byte,  *net.UDPAddr) error {
	 := .params.AddrPool.Get().(*bufferHolder) //nolint:forcetypeassert
	if cap(.buf) < len() {
		.params.AddrPool.Put()

		return io.ErrShortBuffer
	}

	.buf = append(.buf[:0], ...)
	.addr = 

	.mu.Lock()
	if .state == udpMuxedConnClosed {
		.mu.Unlock()

		.reset()
		.params.AddrPool.Put()

		return io.ErrClosedPipe
	}

	if .bufHead != nil {
		.bufHead.next = 
	}
	.bufHead = 

	if .bufTail == nil {
		.bufTail = 
	}

	 := .state
	.state = udpMuxedConnOpen
	.mu.Unlock()

	if  == udpMuxedConnWaiting {
		select {
		case .notify <- struct{}{}:
		default:
		}
	}

	return nil
}