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

package allocation

import (
	
	
	
	

	
)

// ManagerConfig a bag of config params for Manager.
type ManagerConfig struct {
	LeveledLogger      logging.LeveledLogger
	AllocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error)
	AllocateConn       func(network string, requestedPort int) (net.Conn, net.Addr, error)
	PermissionHandler  func(sourceAddr net.Addr, peerIP net.IP) bool
}

type reservation struct {
	token string
	port  int
}

// Manager is used to hold active allocations.
type Manager struct {
	lock sync.RWMutex
	log  logging.LeveledLogger

	allocations  map[FiveTupleFingerprint]*Allocation
	reservations []*reservation

	allocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error)
	allocateConn       func(network string, requestedPort int) (net.Conn, net.Addr, error)
	permissionHandler  func(sourceAddr net.Addr, peerIP net.IP) bool
}

// NewManager creates a new instance of Manager.
func ( ManagerConfig) (*Manager, error) {
	switch {
	case .AllocatePacketConn == nil:
		return nil, errAllocatePacketConnMustBeSet
	case .AllocateConn == nil:
		return nil, errAllocateConnMustBeSet
	case .LeveledLogger == nil:
		return nil, errLeveledLoggerMustBeSet
	}

	return &Manager{
		log:                .LeveledLogger,
		allocations:        make(map[FiveTupleFingerprint]*Allocation, 64),
		allocatePacketConn: .AllocatePacketConn,
		allocateConn:       .AllocateConn,
		permissionHandler:  .PermissionHandler,
	}, nil
}

// GetAllocation fetches the allocation matching the passed FiveTuple.
func ( *Manager) ( *FiveTuple) *Allocation {
	.lock.RLock()
	defer .lock.RUnlock()

	return .allocations[.Fingerprint()]
}

// AllocationCount returns the number of existing allocations.
func ( *Manager) () int {
	.lock.RLock()
	defer .lock.RUnlock()

	return len(.allocations)
}

// Close closes the manager and closes all allocations it manages.
func ( *Manager) () error {
	.lock.Lock()
	defer .lock.Unlock()

	for ,  := range .allocations {
		if  := .Close();  != nil {
			return 
		}
	}

	return nil
}

// CreateAllocation creates a new allocation and starts relaying.
func ( *Manager) (
	 *FiveTuple,
	 net.PacketConn,
	 int,
	 time.Duration,
) (*Allocation, error) {
	switch {
	case  == nil:
		return nil, errNilFiveTuple
	case .SrcAddr == nil:
		return nil, errNilFiveTupleSrcAddr
	case .DstAddr == nil:
		return nil, errNilFiveTupleDstAddr
	case  == nil:
		return nil, errNilTurnSocket
	case  == 0:
		return nil, errLifetimeZero
	}

	if  := .GetAllocation();  != nil {
		return nil, fmt.Errorf("%w: %v", errDupeFiveTuple, )
	}
	 := NewAllocation(, , .log)

	, ,  := .allocatePacketConn("udp4", )
	if  != nil {
		return nil, 
	}

	.RelaySocket = 
	.RelayAddr = 

	.log.Debugf("Listening on relay address: %s", .RelayAddr)

	.lifetimeTimer = time.AfterFunc(, func() {
		.DeleteAllocation(.fiveTuple)
	})

	.lock.Lock()
	.allocations[.Fingerprint()] = 
	.lock.Unlock()

	go .packetHandler()

	return , nil
}

// DeleteAllocation removes an allocation.
func ( *Manager) ( *FiveTuple) {
	 := .Fingerprint()

	.lock.Lock()
	 := .allocations[]
	delete(.allocations, )
	.lock.Unlock()

	if  == nil {
		return
	}

	if  := .Close();  != nil {
		.log.Errorf("Failed to close allocation: %v", )
	}
}

// CreateReservation stores the reservation for the token+port.
func ( *Manager) ( string,  int) {
	time.AfterFunc(30*time.Second, func() {
		.lock.Lock()
		defer .lock.Unlock()
		for  := len(.reservations) - 1;  >= 0; -- {
			if .reservations[].token ==  {
				.reservations = append(.reservations[:], .reservations[+1:]...)

				return
			}
		}
	})

	.lock.Lock()
	.reservations = append(.reservations, &reservation{
		token: ,
		port:  ,
	})
	.lock.Unlock()
}

// GetReservation returns the port for a given reservation if it exists.
func ( *Manager) ( string) (int, bool) {
	.lock.RLock()
	defer .lock.RUnlock()

	for ,  := range .reservations {
		if .token ==  {
			return .port, true
		}
	}

	return 0, false
}

// GetRandomEvenPort returns a random un-allocated udp4 port.
func ( *Manager) () (int, error) {
	for  := 0;  < 128; ++ {
		, ,  := .allocatePacketConn("udp4", 0)
		if  != nil {
			return 0, 
		}
		,  := .(*net.UDPAddr)
		 = .Close()
		if  != nil {
			return 0, 
		}

		if ! {
			return 0, errFailedToCastUDPAddr
		}
		if .Port%2 == 0 {
			return .Port, nil
		}
	}

	return 0, errFailedToAllocateEvenPort
}

// GrantPermission handles permission requests by calling the permission handler callback
// associated with the TURN server listener socket.
func ( *Manager) ( net.Addr,  net.IP) error {
	// No permission handler: open
	if .permissionHandler == nil {
		return nil
	}

	if .permissionHandler(, ) {
		return nil
	}

	return errAdminProhibited
}