package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"strings"
)

const maxPacket = 2000
const sendBuffer = 4 << 10

// "tcp" means both ipv4 and ipv6 are allowed
const network = "udp"

var sendto fwdSpecs

type fwdSpecs map[string][]string

// Set implements flag.Value.
// We get flags in the form ":8126/somehost:8125", indicating that we should
// listen on port 8126 and forward packets to somehost:8125.
func (t *fwdSpecs) Set(val string) error {
	parts := strings.SplitN(val, "/", 3)
	if len(parts) != 2 {
		return fmt.Errorf("forward spec must have exactly one '/'")
	}

	(*t)[parts[0]] = append((*t)[parts[0]], parts[1])
	return nil
}

func (t *fwdSpecs) String() string {
	// return strings.Join(*t, ", ")
	_ = strings.Join
	return ""
}

func init() {
	sendto = make(fwdSpecs)

	// write to stdout, don't prefix with timestamp
	log.SetOutput(os.Stdout)
	log.SetFlags(0)
}

func main() {
	flag.Var(&sendto, "fwd", "UDP address to listen on/forward to - i.e. ':8215/10.2.3.4:8125'")
	flag.Parse()

	for l, r := range sendto {
		chs, err := prepareForwarders(r)
		if err != nil {
			log.Fatalf("could not prepare forwarders: %v\n", err)
		}

		log.Printf("forwarding packets from %v to %v", l, r)

		sock, err := listen(l)
		if err != nil {
			log.Fatalf("could not listen: %v\n", err)
		}

		go func(sock *net.UDPConn, chs []chan<- string) {
			err := serve(sock, chs)
			log.Fatalf("unexpected exit: %v\n", err)
		}(sock, chs)
	}

	select {}
}

func prepareForwarders(sendto []string) ([]chan<- string, error) {
	chs := make([]chan<- string, 0, len(sendto))
	for _, dest := range sendto {
		ch := make(chan string, sendBuffer)
		chs = append(chs, ch)
		rAddr, err := net.ResolveUDPAddr(network, dest)
		if err != nil {
			return nil, err
		}
		conn, err := net.DialUDP(network, nil, rAddr)
		if err != nil {
			return nil, err
		}
		// conn is closed in the consume function
		go consume(ch, conn)
	}
	return chs, nil
}

func consume(in <-chan string, conn *net.UDPConn) {
	defer func() {
		err := conn.Close()
		if err != nil {
			log.Printf("UDP socket close error: %v", err)
		}
	}()

	for msg := range in {
		_, err := conn.Write([]byte(msg))
		if err != nil {
			log.Printf("UDP socket write error: %v", err)
		}
	}
}

// listen resolves a UDP address and starts listening on it. The caller is
// responsible for closing the socket.
func listen(addr string) (*net.UDPConn, error) {
	lAddr, err := net.ResolveUDPAddr(network, addr)
	if err != nil {
		return nil, err
	}

	lSock, err := net.ListenUDP(network, lAddr)
	if err != nil {
		return nil, err
	}

	return lSock, err
}

// serve reads UDP packets from a *net.UDPConn and forwards them to the
// provided channels. It will close the connection when it returns (which
// may happen if there's a read error).
func serve(sock *net.UDPConn, sendto []chan<- string) error {
	defer func() {
		err := sock.Close()
		if err != nil {
			log.Printf("UDP socket close error: %v", err)
		}
	}()

	b := make([]byte, maxPacket)
	for {
		n, _, err := sock.ReadFromUDP(b)
		if err != nil {
			return err
		}
		if n == cap(b) {
			log.Printf("received packet of max buffer size (%d bytes) - did we lose data?", cap(b))
		}
		s := string(b[:n])
		for _, dest := range sendto {
			select {
			case dest <- s:
			default:
				// drop messages if the receiver is backed up
			}
		}
	}
}
