// Package server4 is a basic, extensible DHCPv4 server.
//
// To use the DHCPv4 server code you have to call NewServer with two arguments:
//  - an interface to listen on,
//  - an address to listen on, and
//  - a handler function, that will be called every time a valid DHCPv4 packet is
//    received.
//
// The address to listen on is used to know IP address, port and optionally the
// scope to create and UDP socket to listen on for DHCPv4 traffic.
//
// The handler is a function that takes as input a packet connection, that can
// be used to reply to the client; a peer address, that identifies the client
// sending the request, and the DHCPv4 packet itself. Just implement your
// custom logic in the handler.
//
// Optionally, NewServer can receive options that will modify the server
// object. Some options already exist, for example WithConn. If this option is
// passed with a valid connection, the listening address argument is ignored.
//
// Example program:
//
//	package main
//
//	import (
//		"log"
//		"net"
//
//		"github.com/insomniacslk/dhcp/dhcpv4"
//		"github.com/insomniacslk/dhcp/dhcpv4/server4"
//	)
//
//	func handler(conn net.PacketConn, peer net.Addr, m *dhcpv4.DHCPv4) {
//		// this function will just print the received DHCPv4 message, without replying
//		log.Print(m.Summary())
//	}
//
//	func main() {
//		laddr := net.UDPAddr{
//			IP:   net.ParseIP("0.0.0.0"),
//			Port: 67,
//		}
//		server, err := server4.NewServer("eth0", &laddr, handler)
//		if err != nil {
//			log.Fatal(err)
//		}
//
//		// This never returns. If you want to do other stuff, dump it into a
//		// goroutine.
//		server.Serve()
//	}
//
package server4

import (
	"log"
	"net"
	"os"

	"github.com/insomniacslk/dhcp/dhcpv4"
)

// Handler is a type that defines the handler function to be called every time a
// valid DHCPv4 message is received
type Handler func(conn net.PacketConn, peer net.Addr, m *dhcpv4.DHCPv4)

// Server represents a DHCPv4 server object
type Server struct {
	conn    net.PacketConn
	Handler Handler
	logger  Logger
}

// Serve serves requests.
func (s *Server) Serve() error {
	s.logger.Printf("Server listening on %s", s.conn.LocalAddr())
	s.logger.Printf("Ready to handle requests")

	defer s.Close()
	for {
		rbuf := make([]byte, 4096) // FIXME this is bad
		n, peer, err := s.conn.ReadFrom(rbuf)
		if err != nil {
			s.logger.Printf("Error reading from packet conn: %v", err)
			return err
		}
		s.logger.Printf("Handling request from %v", peer)

		m, err := dhcpv4.FromBytes(rbuf[:n])
		if err != nil {
			s.logger.Printf("Error parsing DHCPv4 request: %v", err)
			continue
		}

		upeer, ok := peer.(*net.UDPAddr)
		if !ok {
			s.logger.Printf("Not a UDP connection? Peer is %s", peer)
			continue
		}
		// Set peer to broadcast if the client did not have an IP.
		if upeer.IP == nil || upeer.IP.To4().Equal(net.IPv4zero) {
			upeer = &net.UDPAddr{
				IP:   net.IPv4bcast,
				Port: upeer.Port,
			}
		}
		go s.Handler(s.conn, upeer, m)
	}
}

// Close sends a termination request to the server, and closes the UDP listener.
func (s *Server) Close() error {
	return s.conn.Close()
}

// ServerOpt adds optional configuration to a server.
type ServerOpt func(s *Server)

// WithConn configures the server with the given connection.
func WithConn(c net.PacketConn) ServerOpt {
	return func(s *Server) {
		s.conn = c
	}
}

// NewServer initializes and returns a new Server object
func NewServer(ifname string, addr *net.UDPAddr, handler Handler, opt ...ServerOpt) (*Server, error) {
	s := &Server{
		Handler: handler,
		logger:  EmptyLogger{},
	}

	for _, o := range opt {
		o(s)
	}
	if s.conn == nil {
		var err error
		conn, err := NewIPv4UDPConn(ifname, addr)
		if err != nil {
			return nil, err
		}
		s.conn = conn
	}
	return s, nil
}

// WithSummaryLogger logs one-line DHCPv4 message summaries when sent & received.
func WithSummaryLogger() ServerOpt {
	return func(s *Server) {
		s.logger = ShortSummaryLogger{
			Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
		}
	}
}

// WithDebugLogger logs multi-line full DHCPv4 messages when sent & received.
func WithDebugLogger() ServerOpt {
	return func(s *Server) {
		s.logger = DebugLogger{
			Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
		}
	}
}

// WithLogger set the logger (see interface Logger).
func WithLogger(newLogger Logger) ServerOpt {
	return func(s *Server) {
		s.logger = newLogger
	}
}
