package peer

import (
	"io"
	"net"
	"net/netip"
	"sync"
	"sync/atomic"
	"time"
)

const (
	version = 1

	bufferSize = 8192 // Enough for data packets and encryption buffers.

	if_mtu       = 1200
	if_queue_len = 2048

	controlCipherOverhead = 16
	dataCipherOverhead    = 16
	signingOverhead       = 64

	pingInterval                  = 8 * time.Second
	timeoutInterval               = 30 * time.Second
	broadcastInterval             = 16 * time.Second
	broadcastErrorTimeoutInterval = 8 * time.Second
)

var multicastAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(
	netip.AddrFrom4([4]byte{224, 0, 0, 157}),
	4560))

// ----------------------------------------------------------------------------

type Globals struct {
	LocalConfig // Embed, immutable.

	// The number of startups
	StartupCount uint16

	// Local public address (if available). Immutable.
	LocalAddr netip.AddrPort

	// True if local public address is valid. Immutable.
	LocalAddrValid bool

	// All remote peers by VPN IP.
	RemotePeers [256]*atomic.Pointer[Remote]

	// Discovered public addresses.
	PubAddrs *pubAddrStore

	// Attempts to ensure that we have a relay available.
	RelayHandler *relayHandler

	// Send UDP - Global function to write UDP packets.
	SendUDP func(b []byte, addr netip.AddrPort) (n int, err error)

	// Global TUN interface.
	IFace io.ReadWriteCloser

	// For trace ID.
	NewTraceID func() uint64
}

func NewGlobals(
	localConfig LocalConfig,
	startupCount startupCount,
	localAddr netip.AddrPort,
	conn *net.UDPConn,
	iface io.ReadWriteCloser,
) (g Globals) {
	g.LocalConfig = localConfig
	g.StartupCount = startupCount.Count

	g.LocalAddr = localAddr
	g.LocalAddrValid = localAddr.IsValid()

	g.PubAddrs = newPubAddrStore(localAddr)

	g.RelayHandler = newRelayHandler()

	// Use a lock here avoids starvation, at least on my Linux machine.
	sendLock := sync.Mutex{}
	g.SendUDP = func(b []byte, addr netip.AddrPort) (int, error) {
		sendLock.Lock()
		n, err := conn.WriteToUDPAddrPort(b, addr)
		sendLock.Unlock()
		return n, err
	}

	g.IFace = iface

	traceID := (uint64(g.StartupCount) << 48) + 1
	g.NewTraceID = func() uint64 {
		return atomic.AddUint64(&traceID, 1)
	}

	for i := range g.RemotePeers {
		g.RemotePeers[i] = &atomic.Pointer[Remote]{}
	}

	for i := range g.RemotePeers {
		g.RemotePeers[i].Store(newRemote(g, byte(i)))
	}

	return g
}
